-- Transport Mechanism Registration Server - restart sequence.

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

-- Randy Gobbel,	20-May-81 10:37:16 
-- Andrew Birrell,	29-Oct-82 10:16:19
-- Mike Schroeder	25-Jan-83 15:48:25 

DIRECTORY
BodyDefs	USING [maxRNameLength, oldestTime, Password, RName, 
		       Timestamp],
EnquiryDefs	USING [],
HeapDefs	USING [HeapAbandonWrite, HeapEndWrite, HeapEndRead,
		       HeapReadData, HeapReadRName, HeapStartRead,
		       HeapStartWrite, ObjectNumber,
		       ReceiveComponent, ReaderHandle, ReadRList,
		       WriterHandle],
LocalNameDefs	USING[ ReadRSName ],
LocateDefs	USING[ FindNearestServer, FindRegServer, FoundServerInfo ],
LogDefs		USING[ WriteChar, WriteLine, WriteLogEntry, WriteString ],
ObjectDirDefs	USING[ Enumerate, UseObject ],
PolicyDefs	USING[ EndOperation, WaitOperation ],
Process		USING[ Detach ],
ProtocolDefs,
PupDefs		USING[ PupAddress ],
RegAccessDefs	USING[ RegAccess ],
RegBTreeDefs	USING[ EnumerateTree, KeepObject, Lookup, LookupReason,
		       MarkKnown, RegBTree, RegistryObject, RegPurger,
		       TestKnownReg ],
RegServerDefs	USING[ AddMailbox, AddMember, AddOwner, ChangeConnect, CreateGroup,
		       CreateIndividual, IsMember, MailUpdate, ReadMail,
		       ReadMembers, Registration, RegMail, Update ],
RegistryDefs	USING[ CompareTimestamps, EnumerateRList, MakeTimestamp,
		       ReadPrefix],
RestartDefs	USING[ ],
Runtime		USING[ CallDebugger ],
String		USING[ AppendString, EquivalentString,
		       EquivalentSubStrings, SubStringDescriptor ],
Time		USING[ Append, Packed, Unpack ];

RegRestart: PROGRAM[ initHeap: BOOLEAN ]
   IMPORTS HeapDefs, LocalNameDefs, LocateDefs, LogDefs, ObjectDirDefs,
           PolicyDefs, Process, ProtocolDefs, RegAccessDefs, RegBTreeDefs,
           RegServerDefs, RegistryDefs, Runtime, String, Time
   EXPORTS EnquiryDefs--AddRegistry--, RestartDefs --PROGRAM-- =

BEGIN

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

maxDownTime: CARDINAL ← 4 -- days --;

WaitForTime: PROC[then: BodyDefs.Timestamp] =
   BEGIN
   log: STRING = [64];
   futureLimit: Time.Packed ← 
     LOOPHOLE[then.time + (LONG[maxDownTime] * 24) * 60 * 60];
   -- Note: "CompareTimestamps" treats very future times as zero --
   IF RegistryDefs.CompareTimestamps[then, RegistryDefs.MakeTimestamp[]]
         # less
   THEN BEGIN
        String.AppendString[log, "Current time is less than "L];
        Time.Append[log, Time.Unpack[then.time]];
        LogDefs.WriteLogEntry[log];
        Runtime.CallDebugger[log];
        END;
   IF RegistryDefs.MakeTimestamp[].time > futureLimit
   THEN BEGIN
        log.length ← 0;
        String.AppendString[log, "Current time is too long after "L];
        Time.Append[log, Time.Unpack[then.time]];
        LogDefs.WriteLogEntry[log];
        Runtime.CallDebugger[log];
        END;
   END;

FindKnownRegistries: PROC =
  BEGIN
  myName: BodyDefs.RName = LocalNameDefs.ReadRSName[].name;
  CheckLocalRegistry: PROC[name: BodyDefs.RName] =
    BEGIN
    IF MyRegistry[myName, name]
    THEN { LogDefs.WriteString["Known registry "L];
           LogDefs.WriteString[name];
           LogDefs.WriteString["; "L];
           SIGNAL RegBTreeDefs.MarkKnown[] };
    END;
  RegBTreeDefs.EnumerateTree[group, CheckLocalRegistry];
  END;

MyRegistry: PROC[myName, group: BodyDefs.RName] RETURNS[ BOOLEAN ] = INLINE
   { RETURN[ RegServerDefs.IsMember[group, myName, direct].membership =
                yes ] };

baseOfWorld: BodyDefs.RName = "GV.GV";

InitializeFromLocalHeap: PROCEDURE RETURNS[limit: BodyDefs.Timestamp] =
  BEGIN
  registries: BOOLEAN ← TRUE; -- registries on first pass, then others --
  RestartObject: PROCEDURE [object: HeapDefs.ObjectNumber] RETURNS [BOOLEAN] =
    BEGIN
    oldRegObj:   RegBTreeDefs.RegistryObject;
    newReader:   HeapDefs.ReaderHandle = HeapDefs.HeapStartRead[object];
    newName:     BodyDefs.RName = [BodyDefs.maxRNameLength];
    newType:     ProtocolDefs.RNameType;
    newStamp:    BodyDefs.Timestamp;
    [newType, newStamp] ← RegistryDefs.ReadPrefix[newReader, newName];
    IF EndsWith[newName,".GV"L] = registries
    THEN BEGIN
         -- first pass: only names ending with ".gv"
         -- second pass: only names not ending with ".gv"
         IF registries OR RegBTreeDefs.TestKnownReg[newName] = yes
         THEN BEGIN
              IF RegistryDefs.CompareTimestamps[newStamp, limit] = greater
              THEN limit ← newStamp;
              oldRegObj ← RegBTreeDefs.Lookup[newName, readNone];
              IF oldRegObj.type = notFound
              OR RegistryDefs.CompareTimestamps[oldRegObj.stamp,newStamp] = less
              THEN RegBTreeDefs.KeepObject[newName, newType, @newStamp, object];
              IF oldRegObj.reader # NIL
              THEN  HeapDefs.HeapEndRead[oldRegObj.reader];
              END
         ELSE LogDiscard[newName];
         END;
    HeapDefs.HeapEndRead[newReader];
    RETURN [FALSE]; -- ie keep enumerating
    END;
  limit ← BodyDefs.oldestTime;
  [] ← ObjectDirDefs.Enumerate[RSobject, RestartObject];
  FindKnownRegistries[];
  registries ← FALSE;
  [] ← ObjectDirDefs.Enumerate[RSobject, RestartObject];
  limit ← RecoverRSMailObjects[limit];
  WaitForTime[limit];
  END;

LogDiscard: PROC[name: BodyDefs.RName] =
   BEGIN
   log: STRING = [80];
   String.AppendString[log, "Unknown: "L];
   String.AppendString[log, name];
   LogDefs.WriteLogEntry[log];
   LogDefs.WriteChar[' ];
   LogDefs.WriteString[log];
   END;

initializeWorldCalled: BOOLEAN ← FALSE;

InitializeWorld: PROCEDURE =
   BEGIN
   OPEN RegServerDefs;
   -- Must be called only if this is the first R-Server in the world --
   wizard: BodyDefs.RName = "Wizard.gv"L;
   firstRS: BodyDefs.RName = "FirstRS.gv"L;
   msReg: BodyDefs.RName = "MS.gv"L;
   deadLetter: BodyDefs.RName = "DeadLetter.ms"L;
   firstMS: BodyDefs.RName = "FirstMS.ms"L;
   maildrop: BodyDefs.RName = "MailDrop.ms"L;
   foreignReg: BodyDefs.RName = "Foreign.gv"L;
   [] ← CreateGroup[baseOfWorld, wizard]; -- GV.GV
   [] ← AddMember[baseOfWorld, firstRS];
   [] ← CreateIndividual[wizard, ProtocolDefs.MakeKey["grapevine"L]];
   [] ← CreateIndividual[firstRS, ProtocolDefs.MakeKey["grapevine"L]];
   [] ← ChangeConnect[firstRS, "ME"L];
   RESTART RegServerDefs.Registration; -- in "local" mode --
   FindKnownRegistries[];
   [] ← CreateGroup[msReg, wizard]; -- create "MS" registry
   [] ← AddMember[msReg, firstRS]; -- known to this R-Server
   [] ← CreateGroup[deadLetter, wizard]; -- deadLetter.ms for "return-to" in update mail
   [] ← CreateIndividual[firstMS, ProtocolDefs.MakeKey["grapevine"L]]; -- MS-name
   [] ← ChangeConnect[firstMS, "ME"L];
   [] ← CreateGroup[maildrop, wizard];
   [] ← AddMailbox[firstRS, firstMS];
   [] ← AddMailbox[firstMS, firstMS];
   [] ← AddMailbox[wizard, firstMS];
   [] ← AddMember[deadLetter, wizard];
   [] ← AddMember[maildrop, firstMS];
   [] ← CreateGroup[foreignReg, wizard]; -- create "Foreign" registry
   [] ← AddMember[foreignReg, firstRS]; -- known to this R-Server
   [] ← AddOwner[baseOfWorld, wizard];
   [] ← AddOwner[msReg, wizard];
   [] ← AddOwner[foreignReg, wizard];
   initializeWorldCalled ← TRUE;
   END;


ThisServerIsntInGrapevine: ERROR = CODE;

InitializeServer: PROCEDURE[myName: BodyDefs.RName,
                            myKey: BodyDefs.Password] =
   BEGIN
   -- Must be called only if this server's database is empty --
   rc: ProtocolDefs.ReturnCode;
   reader: HeapDefs.ReaderHandle;
   oldTimePtr: BodyDefs.Timestamp ← BodyDefs.oldestTime; --ugh!--
   LogDefs.WriteLine["Initializing Registration Server"L];
   LogDefs.WriteLogEntry["Initializing RServer"L];
   FetchRegistry[baseOfWorld, myName, myKey];
   FindKnownRegistries[];
   IF NOT MyRegistry[myName, baseOfWorld]
   THEN ERROR ThisServerIsntInGrapevine[];
   [reader, rc] ← RegServerDefs.ReadMembers["Groups.GV"L, @oldTimePtr];
   IF rc # [code: done, type: group] THEN ERROR;
   BEGIN
      Work: PROC[name: BodyDefs.RName] RETURNS[done: BOOLEAN] =
         BEGIN
         done ← FALSE;
         IF NOT String.EquivalentString[name, baseOfWorld]
         AND MyRegistry[myName, name]
         THEN FetchRegistry[name, myName, myKey];
         END;
      RegistryDefs.EnumerateRList[reader, Work];
      HeapDefs.HeapEndRead[reader];
   END;
   END;

AddSelfToRegistry: PUBLIC PROC[name: BodyDefs.RName]
         RETURNS[done: BOOLEAN] =
   BEGIN
   IF RegServerDefs.IsMember["*.gv", name, direct].membership # yes
   THEN RETURN[FALSE];
   IF RegServerDefs.AddMember[name, LocalNameDefs.ReadRSName[].name]
         # [done, group]
   THEN RETURN[FALSE];
   RETURN[TRUE]
   END;

AddRegistry: PUBLIC PROC[name: BodyDefs.RName]
         RETURNS[done: BOOLEAN] =
   BEGIN
   myName: BodyDefs.RName;
   myKey: BodyDefs.Password;
   [myName,,myKey] ← LocalNameDefs.ReadRSName[];
   IF NOT MyRegistry[myName, name] THEN RETURN[FALSE];
   done ← TRUE;
   FetchRegistry[name, myName, myKey !
     CantFetchRegistry => {done ← FALSE; CONTINUE} ];
   END;

CantFetchRegistry: ERROR[name: BodyDefs.RName] = CODE;

FetchRegistry: PROCEDURE[name, myName: BodyDefs.RName,
                         myKey: BodyDefs.Password] =
   BEGIN
   str: ProtocolDefs.Handle ← NIL;
   AcceptNonLocal: PROCEDURE[addr: PupDefs.PupAddress]RETURNS[ BOOLEAN ] =
      BEGIN
      IF ProtocolDefs.IsLocal[addr]
      THEN RETURN[FALSE]
      ELSE BEGIN
           addr.socket ← ProtocolDefs.RegServerEnquirySocket;
           str ← ProtocolDefs.CreateStream[addr: addr, secs: 600 !
                    ProtocolDefs.Failed => GOTO no ];
           RETURN[TRUE];
           EXITS no => RETURN[FALSE]
           END;
      END;
   BEGIN
      ENABLE UNWIND => IF str # NIL THEN str.delete[str];
      info: LocateDefs.FoundServerInfo =
         LocateDefs.FindNearestServer[name, AcceptNonLocal];
      LogDefs.WriteString["FetchRegistry: "L];
      LogDefs.WriteLine[name];
      WITH info SELECT FROM
        notFound, allDown => ERROR CantFetchRegistry[name];
        found =>
          BEGIN
          ENABLE ProtocolDefs.Failed => ERROR CantFetchRegistry[name];
          BEGIN
            rc: ProtocolDefs.ReturnCode;
            ProtocolDefs.SendRSOperation[str, IdentifyCaller];
            ProtocolDefs.SendRName[str, myName];
            ProtocolDefs.SendPassword[str:str, pw:myKey, key:[0,0,0,0]];
            ProtocolDefs.SendNow[str];
            rc ← ProtocolDefs.ReceiveRC[str];
            IF rc.code # done THEN ERROR CantFetchRegistry[name];
          END;
          FetchSingleEntry[name, str]; -- particularly "GV.GV" very early!
          FetchType[group, name, str];
          FetchType[individual, name, str];
          FetchType[dead, name, str];
          END;
      ENDCASE => ERROR;
   END;
   IF str # NIL THEN str.delete[str];
   END;

MakeRNameInRegistry: PROCEDURE [sname, reg, destination: BodyDefs.RName] =
  BEGIN
  -- sname is of the form "SN.something" or just "SN"
  -- reg is of the form "NA.something" or just "NA"
  -- assumes that SN and NA do not contain '.
  -- constructs "SN.NA", truncating NA if needed
  sep: CHARACTER = '.;
  destination.length ← 0;
  FOR index: CARDINAL IN [0..sname.length) WHILE sname[index] # sep
  DO IF destination.length = destination.maxlength THEN ERROR;
     destination[destination.length] ← sname[index];
     destination.length ← destination.length + 1;
  ENDLOOP;
  IF destination.length = destination.maxlength THEN RETURN;
  destination[destination.length] ← sep;
  destination.length ← destination.length + 1;
  FOR index: CARDINAL IN [0..reg.length) WHILE reg[index] # sep
  DO IF destination.length = destination.maxlength THEN EXIT;
     destination[destination.length] ← reg[index];
     destination.length ← destination.length + 1;
  ENDLOOP;
  END;

FetchType: PROCEDURE[type: ProtocolDefs.RNameType, registry: BodyDefs.RName,
                     str: ProtocolDefs.Handle ] =
   BEGIN
   writer: HeapDefs.WriterHandle = HeapDefs.HeapStartWrite[temp];
   BEGIN
      ENABLE UNWIND => HeapDefs.HeapAbandonWrite[writer];
      typeName: BodyDefs.RName = [BodyDefs.maxRNameLength];
      MakeRNameInRegistry[SELECT type FROM
                            group => "Groups"L, individual => "Individuals"L,
                            dead => "Dead"L, ENDCASE => ERROR,
                          registry, typeName];
      IF ProtocolDefs.Enquire[str, ReadMembers, typeName].rc
         # [code:done, type:group] THEN ERROR;
      HeapDefs.ReceiveComponent[writer, str];
   END;
   BEGIN
      GetEntries: PROCEDURE[ obj: HeapDefs.ObjectNumber] =
         BEGIN
         reader: HeapDefs.ReaderHandle = HeapDefs.HeapStartRead[obj];
         Work: PROCEDURE[entry: BodyDefs.RName] RETURNS[done: BOOLEAN] =
            { FetchSingleEntry[entry, str]; done ← FALSE };
         HeapDefs.ReadRList[reader, Work !
                            UNWIND => HeapDefs.HeapEndRead[reader] ];
         HeapDefs.HeapEndRead[reader];
         END;
      HeapDefs.HeapEndWrite[writer, GetEntries ];
   END;
   END;

FetchSingleEntry: PROCEDURE[entry: BodyDefs.RName,
                            str: ProtocolDefs.Handle] =
   BEGIN
      writer: HeapDefs.WriterHandle = HeapDefs.HeapStartWrite[temp];
      BEGIN
         ENABLE UNWIND => HeapDefs.HeapAbandonWrite[writer];
         IF ProtocolDefs.Enquire[str, ReadEntry, entry].rc.code # done
         THEN ERROR;
         THROUGH [0.. ProtocolDefs.ReceiveCount[str] )
         DO HeapDefs.ReceiveComponent[writer, str]; ENDLOOP;
      END;
      HeapDefs.HeapEndWrite[writer, RegServerDefs.Update];
   END;

RecoverRSMailObjects: PROC[oldLimit: BodyDefs.Timestamp]
                   RETURNS[limit: BodyDefs.Timestamp] =
   BEGIN
   -- enumerate the heap looking for objects of the type created by
   -- RecordDelivery. These define updates which might not have been
   -- mailed before we crashed. These objects contain the time at which
   -- they were written, so that we can distinguish them later from
   -- objects written during this run.
   name: BodyDefs.RName = [BodyDefs.maxRNameLength];
   Look: PROC[object: HeapDefs.ObjectNumber] RETURNS[found: BOOLEAN] =
      BEGIN
      reader: HeapDefs.ReaderHandle = HeapDefs.HeapStartRead[object];
      then: Time.Packed;
      thenStamp: BodyDefs.Timestamp ← BodyDefs.oldestTime;
      [] ← HeapDefs.HeapReadRName[reader, name];
      [] ← HeapDefs.HeapReadData[reader,
               [@then, SIZE[Time.Packed]] ];
      ObjectDirDefs.UseObject[object];
      HeapDefs.HeapEndRead[reader];
      thenStamp.time ← then;
      -- CompareTimestamps includes a garbage check --
      IF RegistryDefs.CompareTimestamps[thenStamp, limit] = greater
      THEN limit ← thenStamp;
      RETURN[FALSE] --i.e. keep enumerating--
      END;
   limit ← oldLimit;
   [] ← ObjectDirDefs.Enumerate[RSmail, Look];
   END;

ActOnRSMailObjects: PROC[limit: Time.Packed] =
   BEGIN
   name: BodyDefs.RName = [BodyDefs.maxRNameLength];
   Look: PROC[object: HeapDefs.ObjectNumber] RETURNS[found: BOOLEAN] =
      BEGIN
      reader: HeapDefs.ReaderHandle = HeapDefs.HeapStartRead[object];
      then: Time.Packed;
      [] ← HeapDefs.HeapReadRName[reader, name];
      [] ← HeapDefs.HeapReadData[reader,
               [@then, SIZE[Time.Packed]] ];
      HeapDefs.HeapEndRead[reader];
      IF then <= limit
      THEN RegServerDefs.MailUpdate[entry: name, stamp:, element: NIL, op: ReadEntry,
                                             rsMailObj: object];
      RETURN[FALSE] --i.e. keep enumerating--
      END;
   PolicyDefs.WaitOperation[regExpand];
   [] ← ObjectDirDefs.Enumerate[RSmail, Look];
   PolicyDefs.EndOperation[regExpand];
   END;

IsThisTheFirstRServerInTheWorld: SIGNAL = CODE;

Restart: PROC =
  BEGIN
  -- We'd prefer to consult other reg servers, not ourselves.
  -- If no other is up, we're willing to use ourself.
  -- If our heap is empty and no-one else is up, we can't proceed
  -- unless we're the first in the world;  in that case the operator
  -- must use the debugger to call "InitializeWorld".

  AcceptNonLocal: PROCEDURE[addr: PupDefs.PupAddress] RETURNS[BOOLEAN] =
     BEGIN RETURN[ NOT ProtocolDefs.IsLocal[addr] ] END;
  info: LocateDefs.FoundServerInfo =
              LocateDefs.FindRegServer[baseOfWorld, AcceptNonLocal];
  myName: BodyDefs.RName;
  myPassword: STRING;
  myKey: BodyDefs.Password;
  START RegServerDefs.Registration; -- in "none" mode --
  START RegServerDefs.RegMail; -- with update propagation disbaled! --
  START RegBTreeDefs.RegBTree;
  START RegAccessDefs.RegAccess; -- with MS internal mail disabled! --
  IF initHeap
  THEN BEGIN
       IF info.t = allDown -- no other servers up --
       THEN BEGIN
            UNTIL initializeWorldCalled
	    DO SIGNAL IsThisTheFirstRServerInTheWorld[] ENDLOOP;
            -- operator should call "InitializeWorld" from the debugger --
	    RESTART RegServerDefs.RegMail; -- enable update propagation --
	    RESTART RegAccessDefs.RegAccess; -- enable MS internal mail --
            [myName,myPassword,myKey] ← LocalNameDefs.ReadRSName[];
            END
       ELSE BEGIN
	    RESTART RegServerDefs.RegMail; -- enable update propagation --
	    RESTART RegAccessDefs.RegAccess; -- enable MS internal mail --
            [myName,myPassword,myKey] ← LocalNameDefs.ReadRSName[];
            InitializeServer[myName, myKey];
            RESTART RegServerDefs.Registration; -- in "local" mode --
            END;
       END
  ELSE BEGIN
       RESTART RegServerDefs.RegMail; -- enable update propagation --
       RESTART RegAccessDefs.RegAccess; -- enable MS internal mail --
       IF info.t = allDown -- no other servers up --
       THEN RESTART RegServerDefs.Registration; -- in "local" mode --
       rsMailLimit ← InitializeFromLocalHeap[]; -- also gets local name --
       [myName,myPassword,myKey] ← LocalNameDefs.ReadRSName[];
       IF info.t # allDown
       THEN RESTART RegServerDefs.Registration; -- in "local" mode --
       END;
  END;


rsMailLimit: BodyDefs.Timestamp ← BodyDefs.oldestTime;

Restart[];

STOP;

-- now Compactor has started --

Process.Detach[
  FORK ActOnRSMailObjects[rsMailLimit.time]--may wait on PolicyDefs--];

RegServerDefs.ReadMail[];

RegBTreeDefs.RegPurger[];

RESTART RegServerDefs.Registration; -- in "all" mode --

END.