-- Copyright (C) 1981, 1982, 1984, 1985  by Xerox Corporation. All rights reserved. 
-- RegServerImpl.mesa, Registration Server - Operations on entries in a registry.

-- HGM, 15-Sep-85  4:04:32 
-- Randy Gobbel,	19-May-81 18:51:53 
-- J. Dion,		September 8, 1979
-- Andrew Birrell,	27-Oct-82 15:46:02 
-- Brenda Hankins	14-Aug-84 16:25:47	alter UpdateSublist return vals.

DIRECTORY
  BodyDefs USING [maxRNameLength, oldestTime, RName, RNameSize, Timestamp],
  Heap USING [systemZone],
  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, Heap, 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: LONG STRING, b: LONG 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: LONG STRING, b: LONG 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: LONG STRING] RETURNS [BOOLEAN] = INLINE {
    RETURN[p.length > 0 AND p[0] = '*]};

  StarMatch: PROC [pattern, name: LONG 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] = {rc ← UpdateSublist[name, entry, AddMailBox].rc};

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

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

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

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

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

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

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

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

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

  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, regLocal: BOOLEAN ← TRUE] =
    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];
        regLocal ← nameObj.regState = yes};
      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: LONG STRING ← Heap.systemZone.NEW[StringBody[200]];
    IF brief THEN LogDefs.WriteString["Brief "L];
    LogDefs.WriteString["Merge "L];
    LogDefs.WriteString[name];
    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];
    Heap.systemZone.FREE[@log];
    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.