-- Transport Mechanism Registration Server - Operations on entries in a registry.

-- [Indigo]<Grapevine>MS>RegServerImpl.mesa

-- Randy Gobbel,	19-May-81 18:51:53 
-- J. Dion,		September 8, 1979
-- Andrew Birrell,	27-Oct-82 15:46:02 

DIRECTORY
BodyDefs	USING [maxRNameLength, oldestTime, RName, 
		       RNameSize, Timestamp],
HeapDefs	USING [GetWriterOffset, HeapAbandonWrite, HeapEndWrite,
		       HeapEndRead,  HeapStartRead, HeapStartWrite, HeapWriteData,
		       HeapWriteRName, ObjectNumber, ObjectOffset,
		       objectStart, ReaderHandle,
		       SetReaderOffset, SetWriterOffset,
		       WriterHandle],
LogDefs		USING[ WriteLogEntry, WriteString ],
NameInfoDefs	USING[ Close, Enumerate, GetMembers, MemberInfo ],
ObjectDirDefs	USING [FreeObject, UseObject],
ProtocolDefs,
RegAccessDefs	USING[ Abandon, Insert, Lookup, NameState ],
RegBTreeDefs	USING[ EnumerateTree, RegistryObject ],
RegCacheDefs	USING[ AddConnect, ReadConnect ],
RegServerDefs	USING[ MailUpdate, Membership, MembershipAcl,
		       MembershipGrade, MembershipLevel ],
RegistryDefs	USING[ AddName, CheckStampList, CompareTimestamps,
		       Comparison, Copy, EnumerateRList, FilterStampList,
		       MakeTimestamp, MergeLists, ReadConnect,
		       ReadPassword, ReadPrefix, RemoveName, Skip,
		       SkipIfEmpty, UpdateInfo, WriteConnect, WriteList,
		       WritePassword, WritePrefix],
String		USING [AppendString, EquivalentString,
		       EquivalentSubStrings, LowerCase, SubStringDescriptor],
Time		USING[ Current, Packed ];

RegServerImpl: MONITOR
   IMPORTS BodyDefs, HeapDefs, LogDefs, NameInfoDefs,
           ObjectDirDefs, ProtocolDefs, RegAccessDefs, RegBTreeDefs, RegCacheDefs,
           RegistryDefs, RegServerDefs, String, Time
   EXPORTS RegServerDefs =

BEGIN

OPEN ProtocolDefs;

-- This module supports complete database entries, but without the
-- concept of "Registry"



-- ================ Layout of entries ================ --

Position:	TYPE = MACHINE DEPENDENT { -- components of an entry --
		--dead-- prefixD(1), endD(2),
		--individual-- prefixI(11),
			password(12),
			connect(13),
			forward(14), --(15,16,17)--
			sites(18), --(19,20,21)--
			endI(22),
		--group-- prefixG(31),
			remark(32),
			members(33), --(34,35,36)--
			owners(37), --(38,39,40)--
			friends(41), --(42,43,44)--
			endG(45) };

Start: PROC[type: ProtocolDefs.RNameType] RETURNS[Position] = INLINE
   BEGIN
   RETURN[ SELECT type FROM
             dead => prefixD,
             individual => prefixI,
             group => prefixG,
           ENDCASE => ERROR ]
   END;

End: PROC[type: ProtocolDefs.RNameType] RETURNS[Position] = INLINE
   BEGIN
   RETURN[ SELECT type FROM
             dead => endD,
             individual => endI,
             group => endG,
           ENDCASE => ERROR ]
   END;

Copy:		PROC[reader: HeapDefs.ReaderHandle,
		     writer: HeapDefs.WriterHandle,
		     from, to: Position] =
   BEGIN
   -- copies parts of an entry; "from" is position that has just been read
   -- "to" indicates what will be available to read after the copying --
   -- Copy[x,y, prefixG, endG] copies a group entry except the prefix --
   DO from ← SUCC[from]; IF from = to THEN EXIT;
      RegistryDefs.Copy[reader, writer];
   ENDLOOP;
   END;

Skip:		PROC[reader: HeapDefs.ReaderHandle,
		     from, to: Position] =
   BEGIN
   -- moves the reader efficiently; "from" is current position --
   -- "to" indicates what will be available to read after the skipping --
   -- Skip[x, prefixI, y] will skip from initial position to read "y" --
   DO IF from = to THEN EXIT;
      RegistryDefs.Skip[reader]; from ← SUCC[from];
   ENDLOOP;
   END;



-- ================ Pseudo-names ================ --
  
EnumeratedName: PROCEDURE[name: BodyDefs.RName]
                  RETURNS[enumType: ProtocolDefs.RNameType ] =
   BEGIN
   -- checks for correct syntax, with special case if
   -- no registry is given.
   sn: BodyDefs.RName = [BodyDefs.maxRNameLength];
   index: CARDINAL;
   FOR index DECREASING IN [0..name.length)
   DO IF name[index] = '. THEN EXIT;
      REPEAT
   FINISHED => index ← name.length
   ENDLOOP;
   String.AppendString[sn, name];
   sn.length ← index; -- "index" is also used below --
   IF sn.length > 0 AND sn[sn.length-1] = '↑ THEN sn.length ← sn.length-1;
   enumType ← IF String.EquivalentString[sn, "Individuals"L]
              THEN individual
              ELSE IF String.EquivalentString[sn, "Groups"L]
                   THEN group
                   ELSE IF String.EquivalentString[sn, "Dead"L]
                        THEN dead
                        ELSE notFound;
   END;

EnumeratedMembers: PUBLIC PROCEDURE [name: BodyDefs.RName,
                              type: ProtocolDefs.RNameType]
                   RETURNS[reader: HeapDefs.ReaderHandle] =
   BEGIN
   registry:    CARDINAL;
   writer:      HeapDefs.WriterHandle = HeapDefs.HeapStartWrite[temp];
   memberWords: CARDINAL ← 0;
   members:     CARDINAL ← 0;
   Action: PROCEDURE[ found: BodyDefs.RName ] =
      BEGIN
      index: CARDINAL;
      IF found.length >= name.length - registry
      THEN FOR index IN (0..name.length-registry]
           DO IF String.LowerCase[found[found.length-index]] #
                   --already l.c.-- name[name.length-index]
              THEN EXIT;
              REPEAT
           FINISHED =>
              BEGIN
              HeapDefs.HeapWriteRName[writer, found]; members ← members+1;
              memberWords ← memberWords + BodyDefs.RNameSize[found];
              END
           ENDLOOP;
      END;
   GetReader: PROCEDURE[ obj: HeapDefs.ObjectNumber ] =
      { reader ← HeapDefs.HeapStartRead[obj] };
   WriteComponentLength: PROCEDURE[length: CARDINAL] =
      { HeapDefs.HeapWriteData[writer, [@length,SIZE[CARDINAL]] ] };
   BEGIN -- parse name to find registry --
      FOR registry DECREASING IN [0..name.length)
      DO IF name[registry] = '. THEN EXIT;
         name[registry] ← String.LowerCase[name[registry]];
         REPEAT
      FINISHED => registry ← name.length
      ENDLOOP;
   END;
   BEGIN -- members
      lengthPos: HeapDefs.ObjectOffset = HeapDefs.GetWriterOffset[writer];
      memberStampPos: HeapDefs.ObjectOffset;
      WriteComponentLength[0]--place holder--;
      RegBTreeDefs.EnumerateTree[type, Action];
      memberStampPos ← HeapDefs.GetWriterOffset[writer];
      HeapDefs.SetWriterOffset[writer, lengthPos];
      WriteComponentLength[memberWords];
   END;
   HeapDefs.HeapEndWrite[writer, GetReader];
   END;


ImpliedOwners: PROC[entry: BodyDefs.RName,
                    stamp: POINTER TO BodyDefs.Timestamp,
                    nameObj: POINTER TO RegAccessDefs.NameState] 
            RETURNS[reader: HeapDefs.ReaderHandle, rc: ReturnCode] =
  BEGIN
  -- ReadMembers for names of the form "Owners-foo↑.pa" --
  IF CheckForOwners[entry]
  THEN BEGIN
       IF nameObj.reader # NIL THEN HeapDefs.HeapEndRead[nameObj.reader];
       [reader, rc] ← ReadOwners[entry, stamp];
       IF rc.code = done AND RegistryDefs.SkipIfEmpty[reader]
       THEN BEGIN -- owner list is empty, so use registry --
            HeapDefs.HeapEndRead[reader];
            [reader, rc] ← ReadRegistryFriends[entry, stamp];
            END;
       -- special case of Owners-foo.pa where foo.pa is individual: --
       -- must adjust return code to avoid confusing client of Expand --
       IF rc = [BadRName,individual] THEN rc ← [BadRName,notFound];
       END
  ELSE { rc ← RegAccessDefs.Abandon[entry, nameObj]; reader ← NIL };
  END;

CheckForOwners: PROC[entry: BodyDefs.RName] RETURNS[ BOOLEAN ] =
   BEGIN
   owner: STRING = "Owner-"L;
   owners: STRING = "Owners-"L;
   begin: STRING;
   IF BeginsWith[entry, (begin←owner)]
   OR BeginsWith[entry, (begin←owners)]
   THEN BEGIN
        FOR i: CARDINAL IN [begin.length..entry.length)
        DO entry[i-begin.length] ← entry[i] ENDLOOP;
        entry.length ← entry.length - begin.length;
        RETURN[TRUE]
        END
   ELSE RETURN[FALSE]
   END;



-- ================ Access to entries ================ --

CheckRName: PUBLIC PROCEDURE [entry: BodyDefs.RName,
                              stamp: POINTER TO BodyDefs.Timestamp]
                     RETURNS [rc: ReturnCode] =
  BEGIN
  nameObj: RegAccessDefs.NameState ← RegAccessDefs.Lookup[entry, readNone];
  SELECT TRUE FROM
    nameObj.type = notFound AND nameObj.regState = yes =>
       BEGIN
       IF EnumeratedName[entry] # notFound
       THEN { rc ← [done, group]; stamp↑ ← RegistryDefs.MakeTimestamp[] }
       ELSE IF CheckForOwners[entry] -- updates "entry" if successful --
            THEN BEGIN
                 rc ← CheckRName[entry, stamp];
                 IF rc.type = individual THEN rc ← [BadRName,notFound];
                 END
            ELSE rc ← RegAccessDefs.Abandon[entry, @nameObj];
       END;
    nameObj.type = notFound OR nameObj.type = dead =>
       rc ← RegAccessDefs.Abandon[entry, @nameObj];
  ENDCASE =>
    IF nameObj.stamp # stamp↑
    THEN { stamp↑ ← nameObj.stamp; rc ← [done, nameObj.type] }
    ELSE rc ← [noChange, nameObj.type];
  END;

Read: PUBLIC PROCEDURE[entry: BodyDefs.RName]
              RETURNS [reader: HeapDefs.ReaderHandle, rc: ReturnCode,
                       components, password: CARDINAL] =
  BEGIN
  nameObj: RegAccessDefs.NameState ← RegAccessDefs.Lookup[entry, readAny];
  reader ← nameObj.reader;
  rc ← [done, nameObj.type];
  SELECT nameObj.type FROM
    individual =>
      BEGIN
      components ← LOOPHOLE[Position[endI],CARDINAL]
                 - LOOPHOLE[Position[prefixI],CARDINAL];
      password ← LOOPHOLE[Position[password],CARDINAL]
               - LOOPHOLE[Position[prefixI],CARDINAL];
      END;
    group =>
      BEGIN
      components ← LOOPHOLE[Position[endG],CARDINAL]
                 - LOOPHOLE[Position[prefixG],CARDINAL];
      password ← LAST[CARDINAL];
      END;
    dead =>
      BEGIN
      components ← LOOPHOLE[Position[endD],CARDINAL]
                 - LOOPHOLE[Position[prefixD],CARDINAL];
      password ← LAST[CARDINAL];
      END;
  ENDCASE => rc ← RegAccessDefs.Abandon[entry, @nameObj];
  END;

ReadPassword: PUBLIC PROCEDURE [entry: BodyDefs.RName]
                       RETURNS [pw:Password, rc: ReturnCode] =
  BEGIN
  nameObj: RegAccessDefs.NameState ←
               RegAccessDefs.Lookup[entry, readIndividual];
  IF nameObj.type # individual
  THEN rc ← RegAccessDefs.Abandon[entry, @nameObj]
  ELSE BEGIN
       stamp: BodyDefs.Timestamp;
       Skip[nameObj.reader, prefixI, password];
       [pw, stamp] ← RegistryDefs.ReadPassword[nameObj.reader];
       HeapDefs.HeapEndRead [nameObj.reader];
       rc ← [done, nameObj.type];
       END;
  END;

ReadConnect: PUBLIC PROCEDURE [entry: BodyDefs.RName, connect: Connect]
                      RETURNS [rc: ReturnCode] =
  BEGIN
  nameObj: RegAccessDefs.NameState;
  IF RegCacheDefs.ReadConnect[entry, connect]
  THEN RETURN[ [done,individual] ];
  nameObj ← RegAccessDefs.Lookup[entry, readIndividual];
  IF nameObj.type # individual
  THEN rc ← RegAccessDefs.Abandon[entry, @nameObj]
  ELSE BEGIN
       stamp: BodyDefs.Timestamp;
       Skip[nameObj.reader, prefixI, connect];
       stamp ← RegistryDefs.ReadConnect[nameObj.reader, connect];
       HeapDefs.HeapEndRead [nameObj.reader];
       RegCacheDefs.AddConnect[entry, nameObj.stamp, connect];
       rc ← [done, nameObj.type];
       END;
  END;

ReadRemark: PUBLIC PROCEDURE [entry: BodyDefs.RName, remark: Remark]
                      RETURNS [rc: ReturnCode] =
  BEGIN
  nameObj: RegAccessDefs.NameState ←
              RegAccessDefs.Lookup[entry, readGroup];
  IF nameObj.type # group
  THEN rc ← RegAccessDefs.Abandon[entry, @nameObj]
  ELSE BEGIN
       stamp: BodyDefs.Timestamp;
       Skip[nameObj.reader, prefixG, remark];
       stamp ← RegistryDefs.ReadConnect[nameObj.reader, remark];
       HeapDefs.HeapEndRead [nameObj.reader];
       rc ← [done, nameObj.type];
       END;
  END;

Expand: PUBLIC PROCEDURE [entry: BodyDefs.RName,
                          stamp: POINTER TO BodyDefs.Timestamp]
                 RETURNS [reader: HeapDefs.ReaderHandle, rc: ReturnCode] =
  BEGIN
  -- don't allow delivery to enumerated groups --
  nameObj: RegAccessDefs.NameState ← 
              RegAccessDefs.Lookup[entry, readEither];
  reader ← nameObj.reader;
  SELECT nameObj.type FROM
    individual =>
      IF (rc ← PositionReader[@nameObj, forward, stamp]).code = done
      THEN IF RegistryDefs.SkipIfEmpty[reader]
           THEN Skip[reader, SUCC[forward], sites]
           ELSE rc ← [done, group]
      ELSE NULL;
    group => rc ← PositionReader [@nameObj, members, stamp];
  ENDCASE =>
    [reader, rc] ← ImpliedOwners[entry, stamp, @nameObj];
  END;

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

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;

ReadSites: PUBLIC PROCEDURE [entry: BodyDefs.RName,
                             stamp: POINTER TO BodyDefs.Timestamp] 
               RETURNS [reader: HeapDefs.ReaderHandle, rc: ReturnCode] =
  BEGIN
  nameObj: RegAccessDefs.NameState ←
              RegAccessDefs.Lookup[entry, readIndividual];
  reader ← nameObj.reader;
  IF nameObj.type = individual
  THEN rc ← PositionReader[@nameObj, sites, stamp]
  ELSE rc ← RegAccessDefs.Abandon[entry, @nameObj];
  END;

ReadMembers: PUBLIC PROCEDURE [entry: BodyDefs.RName,
                               stamp: POINTER TO BodyDefs.Timestamp] 
               RETURNS [reader: HeapDefs.ReaderHandle, rc: ReturnCode] =
  BEGIN
  nameObj: RegAccessDefs.NameState ← 
              RegAccessDefs.Lookup[entry, readGroup];
  reader ← nameObj.reader;
  IF nameObj.type = group
  THEN rc ← PositionReader[@nameObj, members, stamp]
  ELSE IF nameObj.type # notFound OR nameObj.regState # yes
       THEN rc ← RegAccessDefs.Abandon[entry, @nameObj]
       ELSE BEGIN
            enumType: ProtocolDefs.RNameType = EnumeratedName[entry];
            IF enumType # notFound
            THEN BEGIN
                 reader ← EnumeratedMembers[entry, enumType];
                 stamp↑ ← RegistryDefs.MakeTimestamp[];
                 rc ← [done,group];
                 END
            ELSE [reader, rc] ← ImpliedOwners[entry, stamp, @nameObj];
            END;
  END;

ReadOwners: PUBLIC PROCEDURE [entry: BodyDefs.RName,
                              stamp: POINTER TO BodyDefs.Timestamp] 
                  RETURNS [reader: HeapDefs.ReaderHandle, rc: ReturnCode] =
  BEGIN
  nameObj: RegAccessDefs.NameState ←
              RegAccessDefs.Lookup[entry, readGroup];
  reader ← nameObj.reader;
  IF nameObj.type = group
  THEN rc ← PositionReader[@nameObj, owners, stamp]
  ELSE rc ← RegAccessDefs.Abandon[entry, @nameObj];
  END;

ReadFriends: PUBLIC PROCEDURE [entry: BodyDefs.RName,
                               stamp: POINTER TO BodyDefs.Timestamp] 
                  RETURNS [reader: HeapDefs.ReaderHandle, rc: ReturnCode] =
  BEGIN
  nameObj: RegAccessDefs.NameState ←
              RegAccessDefs.Lookup[entry, readGroup];
  reader ← nameObj.reader;
  IF nameObj.type = group
  THEN rc ← PositionReader[@nameObj, friends, stamp]
  ELSE rc ← RegAccessDefs.Abandon[entry, @nameObj];
  END;

ReadRegistry: PROC[entry: BodyDefs.RName,
                   stamp: POINTER TO BodyDefs.Timestamp,
                   work: PROC[BodyDefs.RName,POINTER TO BodyDefs.Timestamp] 
                         RETURNS[HeapDefs.ReaderHandle,ReturnCode] ] 
           RETURNS[reader: HeapDefs.ReaderHandle, rc: ReturnCode] =
   BEGIN
   gvReg: STRING = ".gv"L;
   regName: BodyDefs.RName = [BodyDefs.maxRNameLength];
   index: CARDINAL;
   sep: CARDINAL;
   FOR index DECREASING IN [0..entry.length)
   DO IF entry[index] = '. THEN BEGIN sep ← index+1; EXIT END; REPEAT
   FINISHED => sep ← 0
   ENDLOOP;
   FOR index IN [sep..entry.length)
   DO regName[index-sep] ← entry[index] ENDLOOP;
   regName.length ← entry.length-sep;
   IF regName.length + gvReg.length > regName.maxlength
   THEN { reader ← NIL; rc ← [BadRName, notFound] }
   ELSE BEGIN
        String.AppendString[regName, gvReg];
        [reader,rc] ← work[regName, stamp];
        END;
   END;

ReadRegistryMembers: PUBLIC PROC[entry: BodyDefs.RName,
                                 stamp: POINTER TO BodyDefs.Timestamp] 
                  RETURNS[reader: HeapDefs.ReaderHandle, rc: ReturnCode] =
   { [reader,rc] ← ReadRegistry[entry, stamp, ReadMembers] };

ReadRegistryOwners: PUBLIC PROC[entry: BodyDefs.RName,
                                stamp: POINTER TO BodyDefs.Timestamp] 
                  RETURNS[reader: HeapDefs.ReaderHandle, rc: ReturnCode] =
   { [reader,rc] ← ReadRegistry[entry, stamp, ReadOwners] };

ReadRegistryFriends: PUBLIC PROC[entry: BodyDefs.RName,
                                 stamp: POINTER TO BodyDefs.Timestamp] 
                  RETURNS[reader: HeapDefs.ReaderHandle, rc: ReturnCode] =
   { [reader,rc] ← ReadRegistry[entry, stamp, ReadFriends] };


PositionReader: PROCEDURE [nameObj: POINTER TO RegAccessDefs.NameState,
                           sublist: Position,
                           oldStamp: POINTER TO BodyDefs.Timestamp]
                  RETURNS [rc: ReturnCode] =
  BEGIN
  IF oldStamp↑ = nameObj.stamp
  THEN BEGIN
       HeapDefs.HeapEndRead[nameObj.reader];
       rc ← [noChange, nameObj.type];
       END
  ELSE BEGIN
       oldStamp↑ ← nameObj.stamp;
       Skip[nameObj.reader, Start[nameObj.type], sublist];
       rc ← [done, nameObj.type]
       END
  END;




-- ================ Access control and membership ================ --

LowerCase: PROC[c: CHARACTER] RETURNS[CHARACTER] = INLINE
   { RETURN[ IF c IN ['A..'Z] THEN 'a + (c-'A) ELSE c ] };

StarPattern: PROC[p: STRING] RETURNS[BOOLEAN] = INLINE
   { RETURN[ p.length > 0 AND p[0] = '* ] };

StarMatch: PROC[pattern, name: STRING] RETURNS[BOOLEAN] =
   BEGIN
   -- Assumes pattern.length > 0 and pattern[0] = '* --
   nPtr: CARDINAL ← name.length;
   IF name.length < pattern.length THEN RETURN[FALSE];
   FOR pPtr: CARDINAL DECREASING IN [1..pattern.length)
   DO nPtr ← nPtr - 1;
      IF LowerCase[name[nPtr]] # LowerCase[pattern[pPtr]]
      THEN RETURN[FALSE];
   ENDLOOP;
   RETURN[TRUE]
   END;

HaveIDoneThis: SIGNAL[new: BodyDefs.RName]RETURNS[BOOLEAN] = CODE;
-- used to prevent recursive loops --

IsInList: PUBLIC PROCEDURE[entry: BodyDefs.RName, member: BodyDefs.RName,
                           level: RegServerDefs.MembershipLevel,
                           grade: RegServerDefs.MembershipGrade,
                           acl:   RegServerDefs.MembershipAcl ]
            RETURNS[ membership: RegServerDefs.Membership,
                     rc: ProtocolDefs.ReturnCode ] =
   BEGIN
   getList: PROCEDURE[BodyDefs.RName, POINTER TO BodyDefs.Timestamp]
            RETURNS[HeapDefs.ReaderHandle,ReturnCode] =
     SELECT acl FROM
       members => IF grade = self THEN ReadMembers ELSE ReadRegistryMembers,
       owners =>  IF grade = self THEN ReadOwners  ELSE ReadRegistryOwners,
       friends => IF grade = self THEN ReadFriends ELSE ReadRegistryFriends,
     ENDCASE => ERROR;
   [membership,rc] ← IsInListSub[entry, member,
                                 IF level = closure THEN upArrow ELSE level,
                                 getList !
                                 HaveIDoneThis => RESUME[FALSE] ];
   IF level = closure AND membership = no
   THEN [membership,rc] ← IsInListSub[entry, member, level, getList !
                                   HaveIDoneThis => RESUME[FALSE] ];
   END;

IsInListSub: PROCEDURE[entry: BodyDefs.RName, member: BodyDefs.RName,
                      level: RegServerDefs.MembershipLevel,
                      getList: PROCEDURE[BodyDefs.RName,
                                         POINTER TO BodyDefs.Timestamp]
                             RETURNS[HeapDefs.ReaderHandle,ReturnCode] ]
              RETURNS[ membership: RegServerDefs.Membership,
                       rc: ProtocolDefs.ReturnCode ] =
   BEGIN
   reader:  HeapDefs.ReaderHandle;
   Work: PROC[name: BodyDefs.RName] RETURNS[done: BOOLEAN] =
      BEGIN
      Accept: PROC =
         { membership ← yes; done ← TRUE; rc ← [done,group] };
      ExpandThis: PROC RETURNS[ BOOLEAN ] = INLINE
         BEGIN
         SELECT level FROM
           direct => RETURN[FALSE];
           closure => RETURN[TRUE];
           upArrow =>
             BEGIN
             FOR i: CARDINAL DECREASING IN [0..name.length)
             DO IF name[i] = '. THEN RETURN[i > 0 AND name[i-1] = '↑];
             ENDLOOP;
             RETURN[FALSE]
             END;
         ENDCASE => ERROR;
         END;
      done ← FALSE;
      SELECT TRUE FROM
        (IF StarPattern[name]
         THEN StarMatch[name, member]
         ELSE String.EquivalentString[name, member]) => Accept[];
        ExpandThis[] =>
          IF SIGNAL HaveIDoneThis[name]
          THEN NULL -- recursive loop elimination --
          ELSE BEGIN
               ENABLE HaveIDoneThis =>
                 IF String.EquivalentString[new,name] THEN RESUME[TRUE];
               subMember: RegServerDefs.Membership;
               subRC: ProtocolDefs.ReturnCode;
               [subMember, subRC] ←
                      IsInListSub[entry:name, member:member,
                                  level:level,
                                  getList: ReadMembers];
               SELECT subRC.code FROM
                 done => { IF subMember = yes THEN Accept[] };
                 WrongServer =>
                    BEGIN -- ask remote R-Server -
                    subInfo: NameInfoDefs.MemberInfo =
                                          NameInfoDefs.GetMembers[name];
                    WITH s: subInfo SELECT FROM
                      group =>
                        BEGIN
                        NameInfoDefs.Enumerate[s.members, Work];
                        NameInfoDefs.Close[s.members];
                        IF membership = yes THEN done ← TRUE;
                        END;
                      allDown => rc.code ← AllDown;
                    ENDCASE => NULL;
                    END;
                 AllDown => rc.code ← AllDown; -- stronger default RC --
               ENDCASE => NULL;
               END;
      ENDCASE => NULL;
      END;

   SELECT TRUE FROM

     -- IsMember["*", foo] or IsMember["*.pa", foo] --
     getList = ReadMembers AND StarPattern[entry] =>
       BEGIN
       rc ← [done,group];
       membership ← IF StarMatch[entry, member] THEN yes ELSE no;
       END;

     -- IsMember["individuals.reg", foo] --
     getList = ReadMembers AND (EnumeratedName[entry] = individual) =>
       BEGIN
       silly: BodyDefs.Timestamp ← BodyDefs.oldestTime;
       namePtr: CARDINAL ← entry.length;
       membership ← no; rc ← [done,group];
       FOR memberPtr: CARDINAL DECREASING IN [0 .. member.length) 
       DO namePtr ← namePtr-1;
          IF namePtr = 0 THEN EXIT;
          IF String.LowerCase[entry[namePtr]] #
                String.LowerCase[member[memberPtr]] THEN EXIT;
          IF entry[namePtr] = '.
          THEN { membership ← IF CheckRName[member,@silly].type=individual
                              THEN yes ELSE no;
                 EXIT };
       ENDLOOP;
       END;

   ENDCASE =>
     BEGIN
     oldTimePtr: BodyDefs.Timestamp ← BodyDefs.oldestTime; --ugh!--
     [reader, rc] ← getList[entry, @oldTimePtr]; -- sets default RC --
     IF rc.code = done
     THEN BEGIN
          membership ← no;
          RegistryDefs.EnumerateRList[reader, Work];
          HeapDefs.HeapEndRead[reader];
          END
     ELSE membership ← badList;
     END;

   END;



-- ================ Changing entries ================ --

CheckCreation: PROC[name: BodyDefs.RName,
                    nameObj: POINTER TO RegAccessDefs.NameState]
            RETURNS[ ok: BOOLEAN ] =
   BEGIN
   IF EnumeratedName[name] # notFound
     OR CheckForOwners[name]
     OR StarPattern[name]
     THEN nameObj.type ← group;
   RETURN[ nameObj.type = notFound AND nameObj.regState = yes ]
   END;

CreateIndividual: PUBLIC PROC[name: BodyDefs.RName,
                              password: Password]
                     RETURNS [rc: ReturnCode] =
  BEGIN
  nameObj: RegAccessDefs.NameState ← RegAccessDefs.Lookup[name, readAny];
  -- must use "readAny", for update synchronization logic if entry was dead
  DO IF CheckCreation[name, @nameObj]
     THEN BEGIN
          stamp: BodyDefs.Timestamp ← RegistryDefs.MakeTimestamp[];
          IF RegistryDefs.CompareTimestamps[stamp, nameObj.stamp] # greater
          THEN BEGIN
               rc ← [outOfDate, nameObj.type];
               IF nameObj.reader # NIL
               THEN HeapDefs.HeapEndRead[nameObj.reader];
               END
          ELSE BEGIN
               writer: HeapDefs.WriterHandle =
                            HeapDefs.HeapStartWrite[RSobject];
               RegistryDefs.WritePrefix[writer, individual, @stamp, name];
               RegistryDefs.WritePassword [writer, password, stamp];
               RegistryDefs.WriteConnect[writer, ""L, stamp];
               RegistryDefs.WriteList[writer, NIL, stamp];
               RegistryDefs.WriteList[writer, NIL, stamp];
               IF NOT FinishUpdate[name, individual, @stamp, writer,
                                   @nameObj, NIL, CreateIndividual]
               THEN LOOP;
               rc ← [done, individual];
               END;
          END
     ELSE rc ← RegAccessDefs.Abandon[name, @nameObj];
     EXIT
  ENDLOOP
  END; 

DeleteIndividual: PUBLIC PROCEDURE [name: BodyDefs.RName]
                           RETURNS [rc: ReturnCode] =
  BEGIN
  rc ← DeleteRName[name, individual];
  END;

CreateGroup: PUBLIC PROCEDURE [name, caller: BodyDefs.RName]
                      RETURNS [rc: ReturnCode] =
  BEGIN
  nameObj: RegAccessDefs.NameState ← RegAccessDefs.Lookup[name, readAny];
  -- must use "readAny", for update synchronization logic if entry was dead
  DO IF CheckCreation[name, @nameObj]
     THEN BEGIN
          stamp: BodyDefs.Timestamp ← RegistryDefs.MakeTimestamp [];
          IF RegistryDefs.CompareTimestamps[stamp, nameObj.stamp] # greater
          THEN BEGIN
               rc ← [outOfDate, nameObj.type];
               IF nameObj.reader # NIL
               THEN HeapDefs.HeapEndRead[nameObj.reader];
               END
          ELSE BEGIN
               writer: HeapDefs.WriterHandle =
                            HeapDefs.HeapStartWrite [RSobject];
               RegistryDefs.WritePrefix[writer, group, @stamp, name];
 --TEMP--      RegistryDefs.WriteConnect[writer, ""L, stamp]; -- remark
               RegistryDefs.WriteList[writer, NIL, stamp];    -- members
               RegistryDefs.WriteList[writer, NIL, stamp];    -- owners
               RegistryDefs.WriteList[writer, NIL, stamp];    -- friends
               IF NOT FinishUpdate[name, group, @stamp, writer, @nameObj, NIL, CreateGroup]
               THEN LOOP;
               rc ← [done, group];
               END;
          END
     ELSE rc ← RegAccessDefs.Abandon[name, @nameObj];
     EXIT
  ENDLOOP;
  END;

DeleteGroup: PUBLIC PROCEDURE [name: BodyDefs.RName]
                      RETURNS [rc: ReturnCode] =
  BEGIN
  rc ← DeleteRName[name, group];
  END;

DeleteRName: PROCEDURE[name: BodyDefs.RName, type: ProtocolDefs.RNameType]
               RETURNS[rc: ReturnCode] =
  BEGIN
  nameObj: RegAccessDefs.NameState ← RegAccessDefs.Lookup[name, readAny];
  -- must acquire reader on old object if it's an individual, to allow
  -- internal mail to mail servers --
  DO IF nameObj.type = type
     THEN BEGIN
          stamp: BodyDefs.Timestamp ← RegistryDefs.MakeTimestamp[];
          IF RegistryDefs.CompareTimestamps[stamp, nameObj.stamp] # greater
          THEN BEGIN
               rc ← [outOfDate, nameObj.type];
               IF nameObj.reader # NIL
               THEN HeapDefs.HeapEndRead[nameObj.reader];
               END
          ELSE BEGIN
               writer: HeapDefs.WriterHandle =
                            HeapDefs.HeapStartWrite [RSobject];
               RegistryDefs.WritePrefix[writer, dead, @stamp, name];
               IF NOT FinishUpdate[name, dead, @stamp, writer, @nameObj,
                    NIL,
                    IF type = individual THEN DeleteIndividual ELSE DeleteGroup]
               THEN LOOP;
               rc ← [done, dead];
               END;
          END
     ELSE rc ← RegAccessDefs.Abandon[name, @nameObj];
     EXIT
  ENDLOOP;
  END;

NewName: PUBLIC PROCEDURE[new, old: BodyDefs.RName]
                  RETURNS[rc: ProtocolDefs.ReturnCode] =
  BEGIN
  reader: HeapDefs.ReaderHandle;
  components: CARDINAL;
  [reader, rc, components,] ← Read[old];
  IF rc.code = done
  THEN BEGIN 
       nameObj: RegAccessDefs.NameState ←
                   RegAccessDefs.Lookup[new, readAny];
       DO IF CheckCreation[new, @nameObj]
          THEN BEGIN
               stamp: BodyDefs.Timestamp ← RegistryDefs.MakeTimestamp [];
               IF RegistryDefs.CompareTimestamps[stamp, nameObj.stamp] # greater
               THEN BEGIN
                    rc ← [outOfDate, nameObj.type];
                    IF nameObj.reader # NIL
                    THEN HeapDefs.HeapEndRead[nameObj.reader];
                    END
               ELSE BEGIN
                    writer: HeapDefs.WriterHandle =
                                 HeapDefs.HeapStartWrite [RSobject];
                    RegistryDefs.WritePrefix[writer, rc.type, @stamp, new];
                    HeapDefs.SetReaderOffset[reader, HeapDefs.objectStart];
                    RegistryDefs.Skip[reader];
                    THROUGH (1..components]
                    DO RegistryDefs.Copy[reader, writer] ENDLOOP;
                    IF NOT FinishUpdate[new, rc.type, @stamp, writer,
                                        @nameObj, NIL, NewName]
                    THEN BEGIN
                         HeapDefs.SetReaderOffset[reader,
                                                  HeapDefs.objectStart];
                         LOOP
                         END;
                    END;
               END
          ELSE rc ← RegAccessDefs.Abandon[new, @nameObj];
          EXIT
       ENDLOOP;
       -- Only specify to support names both in same registry --
       IF rc.code = WrongServer THEN rc.code ← BadProtocol;
       END;
  IF reader # NIL THEN HeapDefs.HeapEndRead[reader];
  END;

ChangePassword: PUBLIC PROCEDURE [entry: BodyDefs.RName, pw: Password]
                         RETURNS [rc: ReturnCode] =
  BEGIN
  rc ← ChangeItem[entry, [password[pw]], ChangePassword ];
  END;

ChangeConnect: PUBLIC PROCEDURE [entry: BodyDefs.RName,
                                 connect: ProtocolDefs.Connect]
                        RETURNS [rc: ProtocolDefs.ReturnCode] =
  BEGIN
  rc ← ChangeItem[entry, [conn[connect]], ChangeConnect ];
  END;

ChangeRemark: PUBLIC PROCEDURE [entry: BodyDefs.RName,
                                remark: ProtocolDefs.Remark]
                       RETURNS [rc: ProtocolDefs.ReturnCode] =
   BEGIN
   rc ← ChangeItem[entry, [rem[remark]], ChangeRemark ];
   END;
 
ChangeItem: PROCEDURE[ entry: BodyDefs.RName,
                       item: RECORD[ SELECT t:* FROM
                                       password => [pw: Password],
                                       conn => [connect: Connect],
                                       rem => [remark: Remark],
                                     ENDCASE ],
                      op: ProtocolDefs.RSOperation]
              RETURNS[ rc: ReturnCode ] =
  BEGIN
  nameObj: RegAccessDefs.NameState ← RegAccessDefs.Lookup[entry, readAny];
  DO IF nameObj.type = (SELECT item.t FROM
           password, conn => individual, rem => group, ENDCASE => ERROR)
     THEN BEGIN
          stamp: BodyDefs.Timestamp ← RegistryDefs.MakeTimestamp[];
          oldpw: Password;
          oldConnect: Connect = [ProtocolDefs.maxConnectLength];
          oldRemark: Remark = [ProtocolDefs.maxRemarkLength];
          oldstamp: BodyDefs.Timestamp;
          writer : HeapDefs.WriterHandle =
                       HeapDefs.HeapStartWrite [RSobject];
          start: Position = Start[nameObj.type];
          [,] ← RegistryDefs.ReadPrefix[nameObj.reader, entry]--correct case--;
          RegistryDefs.WritePrefix[writer, nameObj.type, @stamp, entry];
          Copy[nameObj.reader, writer, start,
               SELECT item.t FROM
                  password => password, conn => connect,
                  rem => remark ENDCASE => ERROR];
          WITH item SELECT FROM
            password =>
              [oldpw, oldstamp] ← RegistryDefs.ReadPassword [nameObj.reader];
            conn =>
              oldstamp ← RegistryDefs.ReadConnect[nameObj.reader, oldConnect];
            rem =>
--TEMP--      oldstamp ← RegistryDefs.ReadConnect[nameObj.reader, oldRemark];
          ENDCASE => ERROR;
          SELECT RegistryDefs.CompareTimestamps [stamp, oldstamp] FROM
            less =>  BEGIN
                       HeapDefs.HeapAbandonWrite[writer];
                       HeapDefs.HeapEndRead[nameObj.reader];
                       rc ← [outOfDate, nameObj.type];
                     END;
            equal => BEGIN
                       HeapDefs.HeapAbandonWrite[writer];
                       HeapDefs.HeapEndRead[nameObj.reader];
                       rc ← [noChange, nameObj.type];
                     END;
            greater=>
              BEGIN
              WITH item SELECT FROM
                password =>
                  BEGIN
                  RegistryDefs.WritePassword [writer, pw, stamp];
                  Copy[nameObj.reader, writer, password, endI];
                  END;
                conn =>
                  BEGIN
                  RegistryDefs.WriteConnect[writer, connect, stamp];
                  Copy[nameObj.reader, writer, connect, endI];
                  END;
                rem =>
                  BEGIN
--TEMP--          RegistryDefs.WriteConnect[writer, remark, stamp];
                  Copy[nameObj.reader, writer, remark, endG];
                  END;
              ENDCASE => ERROR;
              IF NOT FinishUpdate[entry, nameObj.type, @stamp, writer,
                                  @nameObj,
                                  NIL,
                                  op ]
              THEN LOOP;
              rc ← [done, nameObj.type];
              END;
          ENDCASE => ERROR;
          END
     ELSE rc ← RegAccessDefs.Abandon[entry, @nameObj];
     EXIT
  ENDLOOP;
  END;

AddMailbox: PUBLIC PROCEDURE [name, entry: BodyDefs.RName]
                     RETURNS [rc: ReturnCode] =
  BEGIN
  rc ← UpdateSublist[name, entry, AddMailBox];
  END;

DeleteMailbox: PUBLIC PROCEDURE [name, entry: BodyDefs.RName]
                        RETURNS [rc: ReturnCode] =
  BEGIN
  rc ← UpdateSublist[name, entry, DeleteMailBox];
  END;

AddForward: PUBLIC PROCEDURE [name, entry: BodyDefs.RName]
                     RETURNS [rc: ReturnCode] =
  BEGIN
  rc ← UpdateSublist[name, entry, AddForward];
  END;

DeleteForward: PUBLIC PROCEDURE [name, entry: BodyDefs.RName]
                        RETURNS [rc: ReturnCode] =
  BEGIN
  rc ← UpdateSublist[name, entry, DeleteForward];
  END;

AddMember: PUBLIC PROCEDURE [name, entry: BodyDefs.RName]
                    RETURNS [rc: ReturnCode] =
  BEGIN
  [rc] ← UpdateSublist[name, entry, AddMember]
  END;

DeleteMember: PUBLIC PROCEDURE [name, entry: BodyDefs.RName]
                       RETURNS [rc: ReturnCode] =
  BEGIN
  [rc] ← UpdateSublist[name, entry, DeleteMember]
  END;

AddOwner: PUBLIC PROCEDURE [name, entry: BodyDefs.RName]
                        RETURNS [rc: ReturnCode] =
  BEGIN
  rc ← UpdateSublist[name, entry, AddOwner]
  END;

DeleteOwner: PUBLIC PROCEDURE [name, entry: BodyDefs.RName]
                           RETURNS [rc: ReturnCode] =
  BEGIN
  rc ← UpdateSublist[name, entry, DeleteOwner]
  END;

AddFriend: PUBLIC PROCEDURE [name, entry: BodyDefs.RName]
                     RETURNS [rc: ReturnCode] =
  BEGIN
  rc ← UpdateSublist[name, entry, AddFriend]
  END;

DeleteFriend: PUBLIC PROCEDURE [name, entry: BodyDefs.RName]
                        RETURNS [rc: ReturnCode] =
  BEGIN
  rc ← UpdateSublist[name, entry, DeleteFriend]
  END;

AddListOfMembers: PUBLIC PROCEDURE[name: BodyDefs.RName,
                                   reader: HeapDefs.ReaderHandle]
                  RETURNS[ rc: ReturnCode ] =
   BEGIN
   stamp: BodyDefs.Timestamp ← RegistryDefs.MakeTimestamp[];
   rc ← UpdateSingleSublist[name, reader, @stamp, group, members];
   END;


UpdateSublist: PUBLIC PROCEDURE [name, element: BodyDefs.RName,
                          op: ProtocolDefs.RSOperation,
                          prevStamp: BodyDefs.Timestamp ← BodyDefs.oldestTime]
                 RETURNS [rc: ReturnCode] =
   BEGIN
   propagate: BOOLEAN = prevStamp = BodyDefs.oldestTime;
   expectedType: ProtocolDefs.RNameType = SELECT op FROM
     AddMailBox, DeleteMailBox, AddForward, DeleteForward => individual,
     AddMember, DeleteMember, AddOwner, DeleteOwner, AddFriend, DeleteFriend => group,
     ENDCASE => ERROR;
   nameObj: RegAccessDefs.NameState ←
          RegAccessDefs.Lookup[name, SELECT expectedType FROM
                                       individual => readIndividual,
                                       group => readGroup,
                                     ENDCASE => ERROR];
   DO IF nameObj.type = expectedType
      THEN BEGIN
           writer: HeapDefs.WriterHandle = HeapDefs.HeapStartWrite[RSobject];
           elementStamp: BodyDefs.Timestamp ←
             IF propagate
             THEN RegistryDefs.MakeTimestamp[]
             ELSE prevStamp;
           globalStamp: BodyDefs.Timestamp ←
             IF RegistryDefs.CompareTimestamps[elementStamp, nameObj.stamp]
                                  = greater
             THEN elementStamp
             ELSE RegistryDefs.MakeTimestamp[];
           info: RegistryDefs.UpdateInfo;
           component: Position = SELECT op FROM
             AddMailBox, DeleteMailBox => sites,
             AddForward, DeleteForward => forward,
             AddMember, DeleteMember => members,
             AddOwner, DeleteOwner => owners,
             AddFriend, DeleteFriend => friends,
             ENDCASE => ERROR;
           add: BOOLEAN = SELECT op FROM
             AddMailBox, AddForward, AddMember, AddOwner, AddFriend => TRUE,
             ENDCASE => FALSE;
           [,] ← RegistryDefs.ReadPrefix[nameObj.reader, name]--correct case--;
           RegistryDefs.WritePrefix[writer, nameObj.type,
                                    @globalStamp, name];
           Copy[nameObj.reader, writer, Start[nameObj.type], component];
           info ← IF add
                  THEN RegistryDefs.AddName[nameObj.reader, element,
                                            @elementStamp, writer]
                  ELSE RegistryDefs.RemoveName[nameObj.reader, element,
                                               @elementStamp, writer];
          IF info = done OR (NOT propagate AND info = noChange)
          THEN BEGIN
               Copy[nameObj.reader, writer,
                    SUCC[SUCC[SUCC[component]]], End[nameObj.type] ];
               IF NOT (IF propagate
                   THEN FinishUpdate[name, expectedType,
                                   @globalStamp,
                                   writer,
                                   @nameObj,
                                   element, op]
                   ELSE RegAccessDefs.Insert[name, expectedType,
                                   @globalStamp,
                                   writer,
                                   @nameObj]
                  )
               THEN LOOP;
               rc ← [done, nameObj.type]
               END
          ELSE BEGIN
               HeapDefs.HeapEndRead [nameObj.reader];
               rc ← [SELECT info FROM
                       outOfDate => outOfDate,
                       noChange => noChange,
                     ENDCASE => ERROR, nameObj.type];
               HeapDefs.HeapAbandonWrite[writer];
               END;
          IF NOT propagate
          THEN LogMerge[name, prevStamp, info = outOfDate,
                        info # noChange, TRUE];
          END
     ELSE rc ← RegAccessDefs.Abandon[name, @nameObj];
     EXIT
   ENDLOOP;
   END;

UpdateSingleSublist: PROCEDURE[name: BodyDefs.RName,
                               update: HeapDefs.ReaderHandle,
                               stamp: POINTER TO BodyDefs.Timestamp,
                               expectedType: RNameType,
                               component: Position]
                 RETURNS [rc: ReturnCode] =
  BEGIN
  nameObj: RegAccessDefs.NameState ←
              RegAccessDefs.Lookup[name, SELECT expectedType FROM
                                      individual => readIndividual,
                                      group => readGroup,
                                    ENDCASE => ERROR];
  DO IF nameObj.type = expectedType
     THEN BEGIN
          writer: HeapDefs.WriterHandle = HeapDefs.HeapStartWrite[RSobject];
          updateNewer, readerNewer: BOOLEAN;
          [,] ← RegistryDefs.ReadPrefix[nameObj.reader, name]--correct case--;
          RegistryDefs.WritePrefix[writer, nameObj.type, stamp, name];
          Copy[nameObj.reader, writer, Start[nameObj.type], component];
          [updateNewer, readerNewer] ←
            RegistryDefs.MergeLists [update, nameObj.reader, writer];
          Copy[nameObj.reader, writer,
               SUCC[SUCC[SUCC[component]]], End[nameObj.type] ];
          IF updateNewer
          THEN BEGIN
               IF NOT FinishUpdate[name, expectedType, stamp, writer,
                                   @nameObj, NIL, AddListOfMembers]
               THEN LOOP;
               rc ← [done, nameObj.type]
               END
          ELSE BEGIN
               HeapDefs.HeapEndRead [nameObj.reader];
               IF readerNewer
               THEN rc ← [outOfDate, nameObj.type]
               ELSE rc ← [noChange, nameObj.type];
               HeapDefs.HeapAbandonWrite[writer];
               END;
          END
     ELSE rc ← RegAccessDefs.Abandon[name, @nameObj];
     EXIT
  ENDLOOP;
  HeapDefs.HeapEndRead[update];
  END;

dontOptimizeMembers: BOOLEAN ← TRUE;

FinishUpdate: PROCEDURE [name: BodyDefs.RName,
                         type: ProtocolDefs.RNameType,
                         stamp: POINTER TO BodyDefs.Timestamp,
                         writer: HeapDefs.WriterHandle,
                         info: POINTER TO RegAccessDefs.NameState,
                         element: BodyDefs.RName,
                         op: ProtocolDefs.RSOperation]
                 RETURNS[done: BOOLEAN] =
   BEGIN
   -- write an RSmail object to ensure others are told about the update --
   -- then end the writer, committing us to the update --
   -- then tell others about the update --
   -- then throw away the RSmail object --
   -- returns done=FALSE iff Insert does so --
   rsMailObject: HeapDefs.ObjectNumber = RecordDelivery[name];
   ResetForMailboxSites[info];
   IF (done ← RegAccessDefs.Insert[name, type, stamp, writer, info])
   THEN RegServerDefs.MailUpdate[name, stamp↑, element, op, rsMailObject]
   ELSE ObjectDirDefs.FreeObject[rsMailObject];
   END;

RecordDelivery: PROCEDURE [name: BodyDefs.RName]
                  RETURNS [object: HeapDefs.ObjectNumber] =
  BEGIN
  writer: HeapDefs.WriterHandle = HeapDefs.HeapStartWrite [RSmail];
  now: Time.Packed ← Time.Current[];
  GetObjectNumber: PROCEDURE [number: HeapDefs.ObjectNumber] =
     { object ← number; ObjectDirDefs.UseObject[number] };
  HeapDefs.HeapWriteRName[writer, name];
  HeapDefs.HeapWriteData[writer, [@now,SIZE[Time.Packed]] ];
  HeapDefs.HeapEndWrite [writer, GetObjectNumber];
  END;



-- ================ Merge update from other server ================ --

Update: PUBLIC PROCEDURE [object: HeapDefs.ObjectNumber] =
  BEGIN
  oldNameObj: RegAccessDefs.NameState;
  newReader: HeapDefs.ReaderHandle = HeapDefs.HeapStartRead[object];
  newRName: BodyDefs.RName = [BodyDefs.maxRNameLength];
  newType: ProtocolDefs.RNameType;
  newStamp: BodyDefs.Timestamp;
  mineIsNewer: BOOLEAN ← FALSE;
  hisIsNewer:  BOOLEAN ← FALSE;
  [newType, newStamp] ← RegistryDefs.ReadPrefix[newReader, newRName];
  oldNameObj ← RegAccessDefs.Lookup[newRName, readAny];
  -- state of newReader here must be same as after the "tryAgain" exit --
  DO BEGIN
     stampCompare: RegistryDefs.Comparison =
          RegistryDefs.CompareTimestamps[newStamp,oldNameObj.stamp];
     bestStamp: BodyDefs.Timestamp ←
       IF stampCompare = greater
       THEN newStamp
       ELSE -- Our stamp was newer (or equal?) --
            -- Stamps on our disk must be monotonic, for restart sequence --
            RegistryDefs.MakeTimestamp[];
     writer: HeapDefs.WriterHandle = HeapDefs.HeapStartWrite[RSobject];
     -- newReader has had the prefix read from it;
     -- oldRegObj.reader has had nothing read from it;
     -- writer has had nothing written to it;
     SELECT TRUE FROM -- the order of the arms of the SELECT matters! --
       ( oldNameObj.type = dead AND (newType # dead OR stampCompare = less) ),
-- and: ignore update for registry we don't know about.
-- NB: this is correct only in absence of addition of registries!
       ( oldNameObj.type = notFound AND oldNameObj.regState # yes ) =>
          BEGIN
          -- our object is better --
          IF oldNameObj.reader # NIL
          THEN HeapDefs.HeapEndRead[oldNameObj.reader];
          HeapDefs.HeapAbandonWrite[writer];
          mineIsNewer ← TRUE;
          EXIT --no update needed--
          END;
       (oldNameObj.type = notFound
        OR newType = dead--and newStamp is newer or old is not dead--) =>
          BEGIN
          -- new entry, or his object is "dead" --
          RegistryDefs.WritePrefix[writer, newType, @bestStamp, newRName];
          Copy[newReader, writer, Start[newType], End[newType]];
          hisIsNewer ← TRUE;
          END
     ENDCASE =>
       BEGIN
       n1, n2: BOOLEAN;
       RegistryDefs.WritePrefix[writer, newType, @bestStamp, newRName];
       SELECT newType FROM
         dead => ERROR;
         group =>
           [n1,n2] ← UpdateGroup[oldNameObj.reader, newReader, writer];
         individual =>
           [n1,n2] ← UpdateIndividual[oldNameObj.reader, newReader, writer];
       ENDCASE => ERROR;
       IF n1 THEN mineIsNewer ← TRUE;
       IF n2 THEN hisIsNewer ← TRUE;
       END;
     ResetForMailboxSites[@oldNameObj];
     IF RegAccessDefs.Insert[newRName, newType, @bestStamp, writer,
                             @oldNameObj]
     THEN EXIT
     ELSE BEGIN
        HeapDefs.SetReaderOffset[newReader, HeapDefs.objectStart];
        [newType, newStamp] ← RegistryDefs.ReadPrefix[newReader, newRName];
        END;
     END
  ENDLOOP;
  HeapDefs.HeapEndRead[newReader];
  LogMerge[newRName, newStamp, mineIsNewer, hisIsNewer, FALSE];
  END;

LogMerge: PROC[name: BodyDefs.RName, stamp: BodyDefs.Timestamp,
               mineIsNewer, hisIsNewer, brief: BOOLEAN] =
   BEGIN
   log: STRING = [140];
   String.AppendString[log, IF brief THEN "Brief merge "L ELSE "Merge "L];
   String.AppendString[log, name];
   String.AppendString[log, ", stamp="L];
   ProtocolDefs.AppendTimestamp[log, stamp];
   IF NOT hisIsNewer
   THEN String.AppendString[log, ", no change"L];
   IF mineIsNewer
   THEN String.AppendString[log, ", anti-entropy"L];
   LogDefs.WriteLogEntry[log];
   IF brief THEN LogDefs.WriteString["Brief "L];
   LogDefs.WriteString["Merge "L]; LogDefs.WriteString[name];
   END;

UpdateGroup: PROCEDURE [oldReader, newReader: HeapDefs.ReaderHandle,
                        writer: HeapDefs.WriterHandle]
               RETURNS[ n1, n2: BOOLEAN ] =
   BEGIN
   -- returns n1=TRUE iff oldReader has info newer than newReader;
   -- returns n2=TRUE iff newReader has info newer than oldReader;
   -- Note that both or neither may be true.
   ListsInGroup: CARDINAL = 3;
   n1 ← n2 ← FALSE;
   Skip[oldReader, prefixG, remark];
   BEGIN
     oldStamp, newStamp: BodyDefs.Timestamp;
     oldRem: Remark = [maxRemarkLength];
     newRem: Remark = [maxRemarkLength];
     oldStamp ← RegistryDefs.ReadConnect[oldReader, oldRem];
     newStamp ← RegistryDefs.ReadConnect[newReader, newRem];
     SELECT RegistryDefs.CompareTimestamps[oldStamp, newStamp] FROM
       greater => { n1 ← TRUE;
                    RegistryDefs.WriteConnect[writer, oldRem, oldStamp] };
       equal =>     RegistryDefs.WriteConnect[writer, newRem, newStamp];
       less =>    { n2 ← TRUE;
                    RegistryDefs.WriteConnect[writer, newRem, newStamp] };
     ENDCASE => ERROR;
   END;
   THROUGH [1 .. ListsInGroup]
   DO newer1, newer2: BOOLEAN;
      [newer1, newer2] ←
                   RegistryDefs.MergeLists[oldReader, newReader, writer];
      IF newer1 THEN n1 ← TRUE;  IF newer2 THEN n2 ← TRUE;
   ENDLOOP;
   END;

UpdateIndividual: PROCEDURE [oldReader, newReader: HeapDefs.ReaderHandle,
                             writer: HeapDefs.WriterHandle] 
                    RETURNS[ n1, n2: BOOLEAN ] =
   BEGIN
   -- returns n1=TRUE iff oldReader has info newer than newReader;
   -- returns n2=TRUE iff newReader has info newer than oldReader;
   -- Note that both or neither may be true.
   ListsInIndividual: CARDINAL = 2;
   n1 ← n2 ← FALSE;
   Skip[oldReader, prefixI, SUCC[prefixI]];
   BEGIN
     oldStamp, newStamp: BodyDefs.Timestamp;
     oldpw, newpw: Password;
     [oldpw, oldStamp] ← RegistryDefs.ReadPassword [oldReader];
     [newpw, newStamp] ← RegistryDefs.ReadPassword [newReader];
     SELECT RegistryDefs.CompareTimestamps[oldStamp, newStamp] FROM
       greater => { n1 ← TRUE;
                    RegistryDefs.WritePassword[writer, oldpw, oldStamp] };
       equal =>     RegistryDefs.WritePassword[writer, newpw, newStamp];
       less =>    { n2 ← TRUE;
                    RegistryDefs.WritePassword[writer, newpw, newStamp] };
     ENDCASE => ERROR;
   END;
   BEGIN
     oldStamp, newStamp: BodyDefs.Timestamp;
     oldConn: Connect = [maxConnectLength];
     newConn: Connect = [maxConnectLength];
     oldStamp ← RegistryDefs.ReadConnect[oldReader, oldConn];
     newStamp ← RegistryDefs.ReadConnect[newReader, newConn];
     SELECT RegistryDefs.CompareTimestamps[oldStamp, newStamp] FROM
       greater => { n1 ← TRUE;
                    RegistryDefs.WriteConnect[writer, oldConn, oldStamp] };
       equal =>     RegistryDefs.WriteConnect[writer, newConn, newStamp];
       less =>    { n2 ← TRUE;
                    RegistryDefs.WriteConnect[writer, newConn, newStamp] };
     ENDCASE => ERROR;
   END;
   THROUGH [1 .. ListsInIndividual]
   DO newer1, newer2: BOOLEAN;
      [newer1, newer2] ←
                   RegistryDefs.MergeLists [oldReader, newReader, writer];
      IF newer1 THEN n1 ← TRUE;  IF newer2 THEN n2 ← TRUE;
   ENDLOOP;
   END;

CantReadOldEntry: ERROR = CODE;

ResetForMailboxSites: PROC[info: POINTER TO RegAccessDefs.NameState] =
   BEGIN
   -- re-position reader to mailbox sites, for generating
   -- internal mail to appropriate MS's --
   IF info.type = individual -- old entry was individual --
   THEN BEGIN
        IF info.reader = NIL THEN ERROR CantReadOldEntry[];
        HeapDefs.SetReaderOffset[info.reader, HeapDefs.objectStart];
        Skip[info.reader, Start[individual], sites];
        END;
   END;  



-- ================ RegPurger utilities ================ --

ConsiderPurging: PUBLIC PROC[regObj: RegBTreeDefs.RegistryObject,
                             oldest: BodyDefs.Timestamp]
            RETURNS[ yes: BOOLEAN ] =
   BEGIN
   SELECT regObj.type FROM
     dead =>
       yes ← RegistryDefs.CompareTimestamps[regObj.stamp, oldest] = less;
     individual =>
       BEGIN
       Skip[regObj.reader, prefixI, SUCC[SUCC[SUCC[forward]]]];
       yes ← RegistryDefs.CheckStampList[regObj.reader, oldest];
       IF yes THEN RETURN;
       Skip[regObj.reader, SUCC[SUCC[SUCC[SUCC[forward]]]],
                                SUCC[SUCC[SUCC[sites]]]];
       yes ← RegistryDefs.CheckStampList[regObj.reader, oldest];
       END;
     group =>
       BEGIN
       Skip[regObj.reader, prefixG, SUCC[SUCC[SUCC[members]]]];
       yes ← RegistryDefs.CheckStampList[regObj.reader, oldest];
       IF yes THEN RETURN;
       Skip[regObj.reader, SUCC[SUCC[SUCC[SUCC[members]]]],
                                SUCC[SUCC[SUCC[owners]]]];
       yes ← RegistryDefs.CheckStampList[regObj.reader, oldest];
       IF yes THEN RETURN;
       Skip[regObj.reader, SUCC[SUCC[SUCC[SUCC[owners]]]],
                                SUCC[SUCC[SUCC[friends]]]];
       yes ← RegistryDefs.CheckStampList[regObj.reader, oldest];
       END;
   ENDCASE => ERROR;
   END;

ReallyPurge: PUBLIC PROC[name: BodyDefs.RName,
                         regObj: RegBTreeDefs.RegistryObject,
                         oldest: BodyDefs.Timestamp]
         RETURNS[yes: BOOLEAN, writer: HeapDefs.WriterHandle] =
   BEGIN
   SELECT regObj.type FROM
     dead =>
       BEGIN
       yes ← RegistryDefs.CompareTimestamps[regObj.stamp, oldest] = less;
       writer←NIL
       END;
     individual =>
       BEGIN
       yes ← TRUE; writer ← HeapDefs.HeapStartWrite[RSobject];
       RegistryDefs.Copy[regObj.reader, writer]; --prefix--
       Copy[regObj.reader, writer, prefixI, SUCC[SUCC[forward]]];
       RegistryDefs.FilterStampList[regObj.reader, oldest, writer];
       Copy[regObj.reader, writer, SUCC[SUCC[SUCC[forward]]],
                                SUCC[SUCC[sites]]];
       RegistryDefs.FilterStampList[regObj.reader, oldest, writer];
       END;
     group =>
       BEGIN
       yes ← TRUE; writer ← HeapDefs.HeapStartWrite[RSobject];
       RegistryDefs.Copy[regObj.reader, writer]; --prefix--
       Copy[regObj.reader, writer, prefixG, SUCC[SUCC[members]]];
       RegistryDefs.FilterStampList[regObj.reader, oldest, writer];
       Copy[regObj.reader, writer, SUCC[SUCC[SUCC[members]]],
                                SUCC[SUCC[owners]]];
       RegistryDefs.FilterStampList[regObj.reader, oldest, writer];
       Copy[regObj.reader, writer, SUCC[SUCC[SUCC[owners]]],
                                SUCC[SUCC[friends]]];
       RegistryDefs.FilterStampList[regObj.reader, oldest, writer];
       END;
   ENDCASE => ERROR;
   IF regObj.reader # NIL THEN HeapDefs.HeapEndRead[regObj.reader];
   END;



END.