-- File: PupDirServerNoDisk.mesa - last edit:
-- WIrish               7-Feb-88 19:29:02
-- AOF                  3-Feb-88 14:32:58
-- HGM                  9-May-86 16:14:21
-- Copyright (C) 1984, 1988 by Xerox Corporation. All rights reserved. 

-- We don't have a copy of the file, but we keep a BIG cache of things we find
-- on other servers so we can pass the service on to clients on adjacent nets.
-- This module watches the directory version on the net (and on the NameServer
--  Helpers), and flushes the cache so it will get reloaded with current information.
-- It also "forwards" netDirVersion requests aka the Pup boot servers to keep IFSs happy.

DIRECTORY
  Ascii USING [CR],
  CmFile USING [Handle, TableError],
  Heap USING [systemZone],
  Process USING [Detach, SetTimeout, MsecToTicks],
  Put USING [Text],
  String USING [AppendChar, AppendNumber, AppendString, AppendDecimal],
  StringLookUp USING [noMatch],
  Time USING [AppendCurrent],
  Token USING [FreeTokenString, Item, Octal],
  Indirect USING [Close, NextValue, OpenSection],
  MiscServerDefs USING [PupMiscServerOn, SetDirectoryServer],
  NameServerDefs USING [
    lockDirRequest, lockDirReply, unlockDirRequest, unlockDirReply,
    FlushWholeCache, PupNameServerOn, BumpCacheSize],
  PupDefs USING [
    AppendHostName, Body, GetPupAddress, GetPupContentsBytes, PupBuffer, PupNameTrouble,
    PupRouterBroadcastThis, PupRouterSendThis, PupSocket, PupSocketDestroy, PupSocketID,
    PupSocketMake, SecondsToTocks, SetPupContentsWords,
    ReturnPup, UniqueLocalPupSocketID,
    AccessHandle, DestroyPool, GetBuffer, MakePool, ReturnBuffer],
  PupRouterDefs USING [NetworkContext],
  PupTypes USING [fillInPupAddress, miscSrvSoc, PupAddress, PupNetID],
  Stats USING [StatCounterIndex, StatIncr];

PupDirServerNoDisk: MONITOR
  IMPORTS
    CmFile, Heap, Process, Put, String, Time, Token,
    Indirect, MiscServerDefs, NameServerDefs, PupDefs, Stats
  EXPORTS NameServerDefs =
  BEGIN OPEN Stats, NameServerDefs, PupDefs;


  z: UNCOUNTED ZONE = Heap.systemZone;
  remote: Chain ← NIL;
  helpers: HelperChain ← NIL;
  
  Chain: TYPE = LONG POINTER TO ChainSlot;
  ChainSlot: TYPE = RECORD [
    next: Chain,
    source: PupTypes.PupNetID,
    dest: PupTypes.PupAddress,
    target: LONG STRING];

  HelperChain: TYPE = LONG POINTER TO HelperChainSlot;
  HelperChainSlot: TYPE = RECORD [
    next: HelperChain,
    dest: PupTypes.PupAddress,
    target: LONG STRING];
  
  dirRunning, probing, sending: PUBLIC BOOLEAN ← FALSE;
  useCount: CARDINAL ← 0;
  pleaseStop: BOOLEAN ← FALSE;
  probePeriod: CARDINAL ← 30;  -- in minutes
  tries: CARDINAL ← 0;
  delay: CONDITION;
  lock: BOOLEAN ← FALSE;
  verbose: BOOLEAN = TRUE;
  currentVersion: CARDINAL ← 0;
  oldVersionAround: BOOLEAN ← TRUE;

  fileName: STRING = "Pup-network.directory";

  statVers, statSend: PUBLIC StatCounterIndex;

  GetNewDirectoryVersion, GetOldDirectoryVersion: PUBLIC PROCEDURE RETURNS [CARDINAL, BOOLEAN] =
    BEGIN RETURN[currentVersion, FALSE]; END;

  PupDirServerOn: PUBLIC PROCEDURE =
    BEGIN
    IF (useCount ← useCount + 1) = 1 THEN
      BEGIN
      dirRunning ← TRUE;
      Starter[];
      END;
    END;

  Starter: PROCEDURE =
    BEGIN
    pleaseStop ← FALSE;
    MiscServerDefs.PupMiscServerOn[];
    ScanParameterFile[];
    MiscServerDefs.SetDirectoryServer[PupDirServer];
    Process.Detach[FORK ProbeGovenor[]];
    END;


  CountTries: ENTRY PROCEDURE = BEGIN tries ← tries + 1; END;

  KickProber: PUBLIC ENTRY PROCEDURE =
    BEGIN IF tries > 3 THEN RETURN; StartProbing[]; END;

  StartProbingForDirectory: PUBLIC ENTRY PROCEDURE = BEGIN StartProbing[]; END;

  StartProbing: INTERNAL PROCEDURE =
    BEGIN
    IF pleaseStop OR probing THEN RETURN;
    probing ← TRUE;
    Process.Detach[FORK Probe[]];
    END;

  ProbeGovenor: ENTRY PROCEDURE =
    BEGIN
    n: CARDINAL ← 1;  -- probe 1 min after startup
    Process.SetTimeout[@delay, Process.MsecToTicks[60000]];
    UNTIL pleaseStop DO
      WAIT delay;  -- one minute
      IF probing THEN BEGIN n ← probePeriod; LOOP; END;
      IF (n ← n - 1) = 0 THEN
        BEGIN
        tries ← 0;
        StartProbing[];
        WHILE probing DO WAIT delay; ENDLOOP;
        n ← probePeriod;
        END;
      ENDLOOP;
    END;

  Probe: PROCEDURE =
    BEGIN
    b: PupBuffer;
    from: PupSocketID ← UniqueLocalPupSocketID[];
    pool: PupDefs.AccessHandle ← PupDefs.MakePool[send: 1, receive: 10];
    soc: PupSocket;
    bestSoFar: CARDINAL ← currentVersion;
    where: PupTypes.PupAddress;
    who: PupTypes.PupAddress ← PupTypes.fillInPupAddress;
    sawOldVersion: BOOLEAN ← FALSE;
    body: PupDefs.Body;
    helper: HelperChain ← helpers;
    tryBroadcast: BOOLEAN ← FALSE;
    try: CARDINAL ← 2;
    -- Check the NameServer Helpers first, then try local broadcasts...
    soc ← PupSocketMake[from, who, SecondsToTocks[5]];
    IF helper # NIL
      THEN who ← helper.dest
      ELSE tryBroadcast ← TRUE;
    UNTIL (bestSoFar > currentVersion) OR (tryBroadcast AND try = 0) DO
      IF try = 0 THEN
        BEGIN
        IF helper # NIL THEN helper ← helper.next;
	IF helper # NIL
	  THEN who ← helper.dest
	  ELSE tryBroadcast ← TRUE;
        try ← 2;
        END;
      try ← try - 1;
      b ← PupDefs.GetBuffer[pool, send];
      body ← b.pup;
      body.dest ← who;
      body.source ← soc.getLocalAddress[];
      body.source.socket ← from;
      body.dest.socket ← PupTypes.miscSrvSoc;
      body.pupType ← netDirVersion;
      body.pupWords[0] ← 0;
      body.pupWords[1] ← 0;
      SetPupContentsWords[b, 2];
      IF tryBroadcast
        THEN PupRouterBroadcastThis[b]
        ELSE PupRouterSendThis[b];
      DO
	b ← soc.get[];
	IF b = NIL THEN EXIT;
	IF b.pup.pupType = netDirVersion THEN
	  BEGIN
	  SELECT b.pup.pupWords[0] FROM
	    > bestSoFar =>
	      BEGIN bestSoFar ← b.pup.pupWords[0]; where ← b.pup.source; END;
	    < currentVersion => sawOldVersion ← TRUE;
	    ENDCASE => NULL;
	  IF GetPupContentsBytes[b] > 2 THEN
	    BEGIN
	    SELECT b.pup.pupWords[1] FROM
	      > bestSoFar =>
		BEGIN bestSoFar ← b.pup.pupWords[0]; where ← b.pup.source; END;
	      < currentVersion => sawOldVersion ← TRUE;
	      ENDCASE => NULL;
	    END;
	  END;
	PupDefs.ReturnBuffer[b];
	ENDLOOP;
      ENDLOOP;
    PupSocketDestroy[soc];
    PupDefs.DestroyPool[pool];
    IF bestSoFar > currentVersion THEN
      BEGIN
      IF verbose THEN
        BEGIN OPEN String;
        text: STRING = [100];
        Time.AppendCurrent[text];
        AppendString[text, "  Saw "L];
        AppendString[text, fileName];
        AppendString[text, "!"L];
        AppendDecimal[text, bestSoFar];
        AppendString[text, " on "L];
        AppendHostName[text, where];
        LogString[text];
        END;
      FlushWholeCache[];
      currentVersion ← bestSoFar;
      probing ← FALSE;
      RETURN;
      END;
    IF sawOldVersion OR oldVersionAround THEN
      BEGIN
      oldVersionAround ← sawOldVersion;
      FlushWholeCache[];
      END;
    probing ← FALSE;
    END;

  PupDirServer: PUBLIC PROCEDURE [b: PupBuffer] =
    BEGIN
    IF ~(lock OR pleaseStop) OR b.pup.pupType = unlockDirRequest THEN
      SELECT b.pup.pupType FROM
        netDirVersion =>
          BEGIN
          StatIncr[statVers];
          SELECT b.pup.pupWords[0] FROM
            = currentVersion => NULL;  -- we have the same ones
            > currentVersion => KickProber[];  -- he has a newer one
            < currentVersion => NULL; -- Don't respond since we can't send it
            ENDCASE => ERROR;
	  MaybeForwardThisOne[b];
	  RETURN;
          END;
        sendNetDir => NULL;
        lockDirRequest =>
          BEGIN
          lock ← TRUE;
          ReturnPup[b, lockDirReply, 0];
          FlushWholeCache[];
          RETURN;
          END;
        unlockDirRequest =>
          BEGIN
          wasLocked: BOOLEAN ← lock;
          lock ← FALSE;
          ReturnPup[b, unlockDirReply, 0];
          tries ← 0;
          KickProber[];
          RETURN;
          END;
        ENDCASE;
    PupDefs.ReturnBuffer[b];
    END;

  MaybeForwardThisOne: PROCEDURE [b: PupDefs.PupBuffer] =
    BEGIN
    context: PupRouterDefs.NetworkContext = b.fo.context;
    IF b.pup.source.net = 0 THEN b.pup.source.net ← [context.pupNetNumber];
    FOR finger: Chain ← remote, finger.next UNTIL finger = NIL DO
      IF context.pupNetNumber = finger.source THEN
        BEGIN
	IF finger.dest = PupTypes.fillInPupAddress THEN EXIT;
	b.pup.dest.net ← finger.dest.net;
	b.pup.dest.host ← finger.dest.host;
	PupDefs.PupRouterSendThis[b];
	RETURN;
	END;
      ENDLOOP;
    PupDefs.ReturnBuffer[b];
    END;

 ScanParameterFile: PROCEDURE =
    BEGIN
    cmFile: CmFile.Handle;
    Option: TYPE = {remote};
    NextValue: PROCEDURE [
      h: CmFile.Handle, table: LONG DESCRIPTOR FOR ARRAY Option OF LONG STRING]
      RETURNS [Option] = LOOPHOLE[Indirect.NextValue];
    optionTable: ARRAY Option OF LONG STRING ← [remote: "Remote"L];
    cmFile ← Indirect.OpenSection["PupDirServer"L];
    IF cmFile = NIL THEN RETURN;
    DO
      option: Option;
      option ← NextValue[
        cmFile, DESCRIPTOR[optionTable] !
        CmFile.TableError =>
          BEGIN
	  IF name[0] # '; THEN Message["Unrecognized parameter: ", name];
	  RETRY;
	  END];
      SELECT option FROM
        LOOPHOLE[StringLookUp.noMatch] => EXIT;
        remote =>
	  BEGIN
	  source: PupTypes.PupNetID = [Token.Octal[cmFile]];
	  temp: LONG STRING ← Token.Item[cmFile, FALSE];
	  new: Chain ← z.NEW[ChainSlot];
	  new↑ ← [NIL, source, PupTypes.fillInPupAddress, z.NEW[StringBody[temp.length]]];
	  String.AppendString[new.target, temp];
	  [] ← Token.FreeTokenString[temp];
	  PupDefs.GetPupAddress[@new.dest, new.target ! PupDefs.PupNameTrouble => CONTINUE];
	  IF remote = NIL THEN remote ← new
	  ELSE
	    BEGIN
	    FOR finger: Chain ← remote, finger.next DO
	      IF finger.next = NIL THEN
	        BEGIN
		finger.next ← new;
		EXIT;
		END;
	      ENDLOOP;
	    END;
          MessageNet["Forwarding Pup NetDirVersion requests from net "L, new.source, " to "L, new.target];
	  END;
        ENDCASE => ERROR;
      ENDLOOP;
    Indirect.Close[cmFile];
    ScanParameterFileForNameServerHelpers[];
    END;

  -- We also have to check the NameServer Helpers...
  ScanParameterFileForNameServerHelpers: PROCEDURE =
    BEGIN
    cmFile: CmFile.Handle;
    Option: TYPE = {helper};
    NextValue: PROCEDURE [
      h: CmFile.Handle, table: LONG DESCRIPTOR FOR ARRAY Option OF LONG STRING]
      RETURNS [Option] = LOOPHOLE[Indirect.NextValue];
    optionTable: ARRAY Option OF LONG STRING ← [helper: "Helper"L];
    cmFile ← Indirect.OpenSection["NameServer"L];
    IF cmFile = NIL THEN RETURN;
    DO
      option: Option;
      option ← NextValue[
        cmFile, DESCRIPTOR[optionTable] !
        CmFile.TableError =>
          BEGIN
	  IF name[0] # '; THEN Message["Unrecognized parameter: ", name];
	  RETRY;
	  END];
      SELECT option FROM
        LOOPHOLE[StringLookUp.noMatch] => EXIT;
        helper =>
	  BEGIN
	  text: STRING = [20];
	  temp: LONG STRING ← Token.Item[cmFile, FALSE];
	  new: HelperChain ← z.NEW[HelperChainSlot];
	  new↑ ← [NIL, PupTypes.fillInPupAddress, z.NEW[StringBody[temp.length]]];
	  String.AppendString[new.target, temp];
	  [] ← Token.FreeTokenString[temp];
	  PupDefs.GetPupAddress[@new.dest, new.target ! PupDefs.PupNameTrouble => CONTINUE];
	  IF helpers = NIL THEN helpers ← new
	  ELSE
	    BEGIN
	    FOR finger: HelperChain ← helpers, finger.next DO
	      IF finger.next = NIL THEN
	        BEGIN
		finger.next ← new;
		EXIT;
		END;
	      ENDLOOP;
	    END;
	  END;
        ENDCASE => ERROR;
      ENDLOOP;
    Indirect.Close[cmFile];
    END;

  MessageNet: PROCEDURE [one: LONG STRING, net: CARDINAL, three, four: LONG STRING] =
    BEGIN
    two: STRING = [20];
    String.AppendNumber[two, net, 8];
    Message[one, two, three, four];
    END;

  Message: PROCEDURE [one, two, three, four: LONG STRING ← NIL] =
    BEGIN
    text: STRING = [200];
    String.AppendString[text, one];
    IF two # NIL THEN String.AppendString[text, two];
    IF three # NIL THEN String.AppendString[text, three];
    IF four # NIL THEN String.AppendString[text, four];
    LogString[text];
    END;

  LogString: PROCEDURE [text: LONG STRING] =
    BEGIN
    String.AppendChar[text, '.];
    String.AppendChar[text, Ascii.CR];
    Put.Text[NIL, text];
    END;

  -- initialization
  PupDirServerOn[];
  NameServerDefs.PupNameServerOn[];
  NameServerDefs.BumpCacheSize[2000];
  END.