-- Grapevine: database comparison tool

-- DBCompare.mesa

-- Andrew Birrell  17-Dec-81 14:09:34

DIRECTORY
BodyDefs,
IODefs,
LocateDefs	USING[ FindRegServer, FoundServerInfo ],
NameInfoDefs,
ProtocolDefs,
PupDefs,
StringDefs,
TimeDefs;

DBCompare: PROGRAM
IMPORTS IODefs, LocateDefs, NameInfoDefs, ProtocolDefs, PupDefs,
        StringDefs, TimeDefs =

BEGIN

OPEN BodyDefs, IODefs, NameInfoDefs, ProtocolDefs, PupDefs;

target: STRING = [64];          -- server under investigation --
targetAddr: PupDefs.PupAddress; -- target's address --
targetStr:  Handle ← NIL;       -- stream to target server --
serverAddr: PupDefs.PupAddress; -- other server's address --
addrKnown: BOOLEAN ← FALSE;     -- validity of serverAddr --
str: Handle ← NIL;              -- stream to other server --

-- "Operate" procedure stolen from Maintain and modified to reject
-- the target server --

Operate: PUBLIC PROC[op: ProtocolDefs.RSOperation, name: BodyDefs.RName,
                    value: BodyDefs.RName ← NIL,
                    connect: BodyDefs.Connect ← NIL,
                    remark: BodyDefs.Remark ← NIL,
                    key: BodyDefs.Password ← [0,0,0,0],
                    sendRList: PROC[ProtocolDefs.Handle] ← NIL ]
        RETURNS[ rc: ProtocolDefs.ReturnCode ] =
   BEGIN
   OPEN ProtocolDefs;
   TryUpdate: PROC[str: ProtocolDefs.Handle] =
      BEGIN
      SendRSOperation[str, op];
      IF op # NoOp THEN SendRName[str, name];
      SELECT op FROM
        IN [Expand..ReadEntry] =>
          SendTimestamp[str, BodyDefs.oldestTime];
        IdentifyCaller =>
          SendPassword[str:str, pw: key, key: [0,0,0,0]];
        IN [AddMember..DeleteFriend], NewName,
        IN [IsMemberDirect..IsFriendClosure] =>
          { IF value = NIL THEN ERROR; SendRName[str, value] };
        Authenticate, CreateIndividual, ChangePassword =>
          SendPassword[str:str, pw: key, key: [0,0,0,0]];
        ChangeConnect =>
          { IF connect = NIL THEN ERROR; SendConnect[str, connect] };
        ChangeRemark =>
          { IF remark = NIL THEN ERROR; SendRemark[str, remark] };
        AddListOfMembers =>
          { IF sendRList = NIL THEN ERROR; sendRList[str] };
      ENDCASE => NULL;
      SendNow[str];
      IF op # NoOp THEN rc ← ReceiveRC[str];
      END;
   oldBad: BOOLEAN ← FALSE;
   Create: PROC =
      BEGIN
      serverSite: STRING = [21] --377#377#177777|177777--;
      PupDefs.AppendPupAddress[serverSite, serverAddr];
      WriteString[serverSite];
      WriteString[" ... "L];
      str ← ProtocolDefs.CreateStream[serverAddr];
      END;
   Destroy: PROC =
      BEGIN
      IF str # NIL THEN DestroyStream[str];
      str ← NIL;
      END;
   Accept: PROC[addr: PupDefs.PupAddress]RETURNS[BOOLEAN] =
      BEGIN
      addr.socket ← RegServerEnquirySocket;
      IF addr = targetAddr THEN RETURN[FALSE];
      IF str # NIL AND serverAddr # addr THEN Destroy[];
      IF str = NIL
      THEN BEGIN
           serverAddr ← addr;
           Create[ ! Failed => GOTO failed];
           addrKnown ← TRUE;
           END;
      RETURN[TRUE];
      EXITS failed => RETURN[FALSE]
      END;
   BEGIN
      IF str # NIL
      THEN BEGIN
           TryUpdate[ str ! Failed => GOTO streamGone ];
           EXITS streamGone => Destroy[];
           END;
      IF str = NIL
      THEN BEGIN
           IF addrKnown
           THEN Create[ ! Failed => GOTO notThere]
           ELSE BEGIN
                [] ← LocateDefs.FindRegServer["x.GV"L, Accept];
                IF str = NIL THEN GOTO notThere;
                END;
           TryUpdate[str ! Failed => GOTO notThere];
           END;
      IF rc.code = WrongServer
      THEN oldBad ← TRUE
      ELSE oldBad ← FALSE;
   EXITS notThere => { Destroy[]; oldBad ← TRUE };
   END;
   IF oldBad
   THEN BEGIN -- need to find the correct R-Server --
        foundInfo: LocateDefs.FoundServerInfo;
        WriteString["Locating registration server ... "L];
        foundInfo ← LocateDefs.FindRegServer[name, Accept];
        WITH foundInfo SELECT FROM
          notFound => rc ← [BadRName, notFound];
          allDown => rc ← [AllDown,notFound];
          found =>
            BEGIN
            TryUpdate[ str ! Failed => GOTO down ];
            EXITS down =>
              { Destroy[]; rc ← [AllDown,notFound] };
            END;
        ENDCASE => ERROR;
        END;
   END;



WriteStamp: PROC[stamp: Timestamp] =
   BEGIN
   text: STRING = [30];
   WriteOctal: PROC[n: CARDINAL] =
      BEGIN
      buffer: STRING = [6] --177777--;
      StringDefs.AppendNumber[buffer, n, 8];
      WriteString[buffer];
      END;
   WriteChar['[];
   WriteOctal[stamp.net];
   WriteChar['#];
   WriteOctal[stamp.host];
   WriteChar[',];
   TimeDefs.AppendDayTime [text, TimeDefs.UnpackDT [stamp.time]];
   WriteString[text];
   WriteChar[']];
   END;

WriteType: PROC[type: RNameType] =
   BEGIN
   WriteString[SELECT type FROM
       group => "group"L,
       individual => "individual"L,
       notFound => "not found"L,
       dead => "dead"L,
     ENDCASE => ERROR];
   END;

WriteRC: PROC[rc: ReturnCode] =
   BEGIN
   WriteString[SELECT rc.code FROM
       done => "ok"L,
       noChange => "no change"L,
       outOfDate => "out of date"L,
       NotAllowed => "not allowed"L,
       BadOperation => "bad operation"L,
       BadProtocol => "bad protocol"L,
       BadRName => "bad R-Name: "L,
       BadPassword => "bad password"L,
       WrongServer => "wrong server"L,
       AllDown => "all suitable R-Servers down"L,
     ENDCASE => ERROR];
   IF rc.code = BadRName THEN WriteType[rc.type];
   END;

WriteComponent: PROC[type: RNameType, i: CARDINAL] =
   BEGIN
   WriteString["component="L];
   WriteString[SELECT type FROM
       group => SELECT i FROM
         0 => "prefix"L,
         1 => "remark"L,
         2 => "members"L,
         3 => "memberStamps"L,
         4 => "delMembers"L,
         5 => "delMemberStamps",
         6 => "owners"L,
         7 => "ownerStamps"L,
         8 => "delOwners"L,
         9 => "delOwnerStamps"L,
         10 => "friends"L,
         11 => "friendStamps"L,
         12 => "delFriends"L,
         13 => "delFriendStamps"L,
         ENDCASE => ERROR,
       individual => SELECT i FROM
         0 => "prefix"L,
         1 => "password"L,
         2 => "connect"L,
         3 => "forward"L,
         4 => "forwardStamps"L,
         5 => "delForward"L,
         6 => "delForwardStamps",
         7 => "inboxes"L,
         8 => "inboxStamps"L,
         9 => "delInboxes"L,
         10 => "delInboxStamps"L,
         ENDCASE => ERROR,
       dead => SELECT i FROM
         0 => "prefix",
         ENDCASE => ERROR,
       ENDCASE => ERROR];
   WriteString[".  "L];
   END;

Complaint: PROC[server: STRING, name: RName, rc: ReturnCode] =
   BEGIN
   WriteChar[CR]; WriteString[name];
   WriteString[" ("L]; WriteString[server]; WriteString[") "L];
   WriteRC[rc];
   END;

Differ: PROC[name: RName, difference: STRING] =
   BEGIN
   WriteChar[CR];
   WriteString[name]; WriteString[": "L];
   WriteString[difference]; WriteString[" different.  "L];
   END;

CompareComponent: PROC[type: RNameType, count: CARDINAL, name: RName] =
   BEGIN
   differ: BOOLEAN ← FALSE;
   bLength: CARDINAL = 64;
   buffer1: PACKED ARRAY [0..bLength) OF CHARACTER;
   buffer2: PACKED ARRAY [0..bLength) OF CHARACTER;
   length1: CARDINAL ← ReceiveCount[targetStr];
   length2: CARDINAL ← ReceiveCount[str];
   IF length1 # length2
   THEN {Differ[name, "component length"];
         WriteComponent[type, count]};
   WHILE length1 > 0 OR length2 > 0
   DO wanted1: CARDINAL = 2*MIN[bLength/2, length1] --bytes--;
      wanted2: CARDINAL = 2*MIN[bLength/2, length2];
      IF wanted1 > 0 THEN ReceiveBytes[targetStr, @buffer1, wanted1];
      IF wanted2 > 0 THEN ReceiveBytes[str, @buffer2, wanted2];
      length1 ← length1 - wanted1/2; length2 ← length2 - wanted2/2;
      FOR i: CARDINAL IN [0..MIN[wanted1,wanted2]/2)
      DO IF buffer1[i] # buffer2[i] THEN differ ← TRUE ENDLOOP;
   ENDLOOP;
   IF differ AND count # 0
   THEN {Differ[name, "component contents"L];
         WriteComponent[type, count]};
   END;

SkipComponent: PROC[thisStr: Handle] =
   BEGIN
   bLength: CARDINAL = 64;
   buffer1: PACKED ARRAY [0..bLength) OF CHARACTER;
   length1: CARDINAL ← ReceiveCount[targetStr];
   WHILE length1 > 0
   DO wanted1: CARDINAL = 2*MIN[bLength/2, length1] --bytes--;
      ReceiveBytes[thisStr, @buffer1, wanted1];
      length1 ← length1 - wanted1/2;
   ENDLOOP;
   END;

Look: PROC[name: RName]RETURNS[done: BOOLEAN] =
   BEGIN
   ok: BOOLEAN ← TRUE;
   stamp1, stamp2: Timestamp;
   rc1, rc2: ReturnCode;
   [rc1,stamp1] ← Enquire[targetStr, ReadEntry, name, oldestTime];
   rc2 ← Operate[op: ReadEntry, name: name];
   IF rc2.code = done THEN stamp2 ← ReceiveTimestamp[str];
   WriteChar['!];
   IF rc1.code # done
   THEN { Complaint[target, name, rc1]; ok←FALSE };
   IF rc2.code # done
   THEN { Complaint["other server"L, name, rc2]; ok←FALSE };
   IF rc1.type # rc2.type
   THEN { Differ[name, "types"L]; ok←FALSE };
   BEGIN
     count1: CARDINAL ← IF rc1.code # done THEN 0
                        ELSE ReceiveCount[targetStr];
     count2: CARDINAL ← IF rc2.code # done THEN 0
                        ELSE ReceiveCount[str];
     IF ok AND count1 # count2 THEN ERROR;
     IF ok AND stamp1 # stamp2 
     THEN { Differ[name, "global stamps"L]; ok ← FALSE };
     IF ok
     THEN FOR c: CARDINAL IN [0..MIN[count1,count2])
          DO count1 ← count1-1;
             count2 ← count2-1;
             CompareComponent[rc1.type, c, name];
          ENDLOOP;
     THROUGH [1..count1] DO SkipComponent[targetStr] ENDLOOP;
     THROUGH [1..count2] DO SkipComponent[str] ENDLOOP;
   END;
   done ← (rc1.code=WrongServer OR rc2.code=WrongServer)
       OR (rc1.code=AllDown OR rc2.code=AllDown);
   END;

LookEnum: PROC[enumName: RName,
               work: PROC[RName]RETURNS[done: BOOLEAN] ] =
   -- argument is "groups.reg", "individuals.reg", or "dead.reg" --
   BEGIN
   memberInfo: MemberInfo = GetMembers[enumName];
   WriteChar[CR];
   WriteString[enumName];
   IF targetStr = NIL THEN targetStr ← CreateStream[targetAddr];
   WITH memberInfo SELECT FROM
     group => BEGIN ENABLE UNWIND => Close[members];
       WriteChar[CR];
       Enumerate[members, work];
       Close[members];
       END;
     allDown => WriteString[": all R-Servers down!"L];
     notFound => WriteString[": not found!"L];
   ENDCASE => ERROR;
   IF targetStr # NIL THEN { DestroyStream[targetStr]; targetStr←NIL };
   END;

LookAtRegistry: PROC[regGroup: RName] RETURNS[done: BOOLEAN] =
   BEGIN
   wanted: RName = [maxRNameLength];
   IF targetStr # NIL THEN { DestroyStream[targetStr]; targetStr←NIL };
   SELECT NameInfoDefs.IsMemberDirect[regGroup, target] FROM
     yes => NULL;
     no => RETURN[FALSE]; -- target isn't in this registry --
     notGroup => ERROR;
     allDown => NULL;
   ENDCASE => ERROR;
   FOR i: CARDINAL DECREASING IN [0..regGroup.length)
   DO regGroup.length ← regGroup.length-1;
      IF regGroup[i] = '. THEN EXIT;
   ENDLOOP;
   WriteString["Registry "L]; WriteLine[regGroup];
   StringDefs.AppendString[wanted, "Groups."L];
   StringDefs.AppendString[wanted, regGroup];
   LookEnum[wanted, Look];
   wanted.length ← 0;
   StringDefs.AppendString[wanted, "Individuals."L];
   StringDefs.AppendString[wanted, regGroup];
   LookEnum[wanted, Look];
   wanted.length ← 0;
   StringDefs.AppendString[wanted, "Dead."L];
   StringDefs.AppendString[wanted, regGroup];
   LookEnum[wanted, Look];
   done ← FALSE;
   END;

LookAtAll: PROC =
   { LookEnum["Groups.gv"L, LookAtRegistry ! ProtocolDefs.Failed =>
        { WriteString["ProtocolDefs.Failed"L]; CONTINUE }] };

GetStream: PROC RETURNS[ok: BOOLEAN] =
   BEGIN
   targetConnect: STRING = [64];
   WriteString["Server R-Name: "L];
   DO c: CHARACTER = ReadChar[];
      SELECT c FROM
        SP, CR, ESC => EXIT;
        BS => IF target.length > 0
              THEN { WriteChar['\]; target.length←target.length-1;
                     WriteChar[target[target.length]] };
      ENDCASE => { WriteChar[c]; StringDefs.AppendChar[target,c] };
   ENDLOOP;
   SELECT NameInfoDefs.GetConnect[target, targetConnect] FROM
      individual => NULL;
   ENDCASE => { WriteString[" bad name"L]; RETURN[FALSE] };
   GetPupAddress[@targetAddr, targetConnect];
   targetAddr.socket ← RegServerEnquirySocket;
   WriteChar[CR];
   RETURN[TRUE];
   END;


ProtocolDefs.Init[];

UNTIL GetStream[] DO target.length ← 0 ENDLOOP;

LookAtAll[];

IF targetStr # NIL THEN DestroyStream[targetStr];
IF str # NIL THEN DestroyStream[str];


END.