-- Copyright (C) 1983  by Xerox Corporation. All rights reserved. 
-- PupBootServerCold.mesa, HGM, 24-Sep-83 15:56:30

DIRECTORY
  Ascii USING [CR],
  Process USING [Detach, SetTimeout, MsecToTicks, Pause, Yield],
  Put USING [Text],
  Runtime USING [IsBound],
  Stream USING [Handle],
  String USING [AppendChar, AppendString, Equivalent],
  System USING [GreenwichMeanTime, GetGreenwichMeanTime],
  Time USING [AppendCurrent],
  Window USING [Handle],

  Buffer USING [AccessHandle, DestroyPool, GetBuffer, MakePool, ReturnBuffer],
  Driver USING [Network],
  BootServer USING [AppendBFN, CardinalToPupBFN, PupBFNToCardinal],
  BootServerDefs USING [
    SendBootDir, BreatherOn, BreatherOff, PupBootServer, UpdatePicture,
    BootStatsEntry, bootStatsReply, bootVersion],
  BootServerFriends USING [
    BootFile, BootFileNumber, CreateTempFile, DeleteTempFile, FileHandle,
    GetPointerToBootTable, MakeTempFileIntoBootFile, timeNotKnown],
  MiscServerDefs USING [
    PupMiscServerOn, PupMiscServerOff, IgnoreThisPacket, SetBootServer],
  PupDefs USING [
    AppendHostName, PupAddress, PupBuffer, SendPup, PupSocket,
    PupSocketDestroy, PupSocketID, PupSocketMake,
    SecondsToTocks, SetPupContentsWords, GetPupContentsBytes,
    UniqueLocalPupSocketID, UniqueLocalPupAddress, ReturnPup,
    defaultNumberOfNetworks, GetHopsToNetwork],
  PupTypes USING [miscSrvSoc, PupNetID, allNets, allHosts],
  PupWireFormat USING [
    BcplLongNumber, BcplToMesaLongNumber, BcplSTRING, MesaToBcplLongNumber],
  Slosh USING [RecvFile, RecvStatus, RetransmissionInterval],
  Stats USING [StatCounterIndex, StatGetCounter];

PupBootServerCold: MONITOR LOCKS lock
  IMPORTS
    Process, Put, Runtime, String, System, Time, BootServer, BootServerDefs,
    BootServerFriends, Buffer, MiscServerDefs, PupDefs, PupWireFormat, Slosh, Stats
  EXPORTS Buffer, BootServerDefs
  SHARES Buffer =
  BEGIN OPEN BootServerDefs;

  -- EXPORTed TYPEs
  Network: PUBLIC TYPE = Driver.Network;

  lock: PUBLIC MONITORLOCK;
  running, booting, slowBooting, pleaseStop, probing, sloshing: PUBLIC BOOLEAN ←
    FALSE;
  longRangeMode: BOOLEAN ← FALSE;
  msg: PUBLIC Window.Handle ← NIL;

  slosheeHost, slosheeFileName: PUBLIC LONG STRING ← NIL;

  first: LONG POINTER TO BootServerFriends.BootFile ←
    BootServerFriends.GetPointerToBootTable[];

  probeTicks: CARDINAL ← 1*3600/5;  -- 1 hour


  statLife: PUBLIC Stats.StatCounterIndex;
  statBootNew: PUBLIC Stats.StatCounterIndex;
  statBootDir: PUBLIC Stats.StatCounterIndex;
  statFile: PUBLIC Stats.StatCounterIndex;
  statSun: PUBLIC Stats.StatCounterIndex;
  statFileSent: PUBLIC Stats.StatCounterIndex;
  statFileSentSlow: PUBLIC Stats.StatCounterIndex;
  statFileTroubles: PUBLIC Stats.StatCounterIndex;
  statFileNeverStarted: PUBLIC Stats.StatCounterIndex;
  statBusyDisk: PUBLIC Stats.StatCounterIndex;
  statBusyBooting: PUBLIC Stats.StatCounterIndex;
  statMicrocodeBooted: PUBLIC Stats.StatCounterIndex;
  statUnknown: PUBLIC Stats.StatCounterIndex;
  verbose: BOOLEAN = TRUE;

  PupBootServerOn: PUBLIC PROCEDURE =
    BEGIN
    running ← TRUE;
    pleaseStop ← FALSE;
    MiscServerDefs.PupMiscServerOn[];
    MiscServerDefs.SetBootServer[BootServerDefs.PupBootServer];
    Process.Detach[FORK BootServerDefs.BreatherOn[]];
    Process.Detach[FORK Prober[]];
    FixupPicture[];
    END;

  PupBootServerOff: PUBLIC PROCEDURE =
    BEGIN
    running ← FALSE;
    pleaseStop ← TRUE;
    MiscServerDefs.SetBootServer[MiscServerDefs.IgnoreThisPacket];
    BreatherOff[];
    WHILE booting OR slowBooting OR probing OR sloshing DO
      Process.Pause[Process.MsecToTicks[100]]; ENDLOOP;
    MiscServerDefs.PupMiscServerOff[];
    FixupPicture[];
    END;

  FixupPicture: PROCEDURE =
    BEGIN
    IF msg # NIL AND Runtime.IsBound[LOOPHOLE[UpdatePicture]] THEN UpdatePicture[];
    END;

  Newer: PROCEDURE [him, me: System.GreenwichMeanTime] RETURNS [BOOLEAN] =
    BEGIN
    IF him = BootServerFriends.timeNotKnown THEN RETURN[FALSE];
    RETURN[him > me];  -- FIX THIS FOR EPOC STUFF
    END;


  -- This stuff should probably migrate to another file

  StartProbingForBootFiles: PUBLIC ENTRY PROCEDURE =
    BEGIN longRangeMode ← FALSE; ProbeForBootFiles[]; END;

  StartLongRangeProbingForBootFiles: PUBLIC ENTRY PROCEDURE =
    BEGIN longRangeMode ← TRUE; ProbeForBootFiles[]; END;

  Prober: ENTRY PROCEDURE =
    BEGIN
    counter: CARDINAL ← 120/5;  -- initial probe 2 min after startup
    delay: CONDITION;
    tryHarder: CARDINAL ← 0;
    Process.SetTimeout[@delay, Process.MsecToTicks[5000]];
    UNTIL pleaseStop DO
      WAIT delay;
      IF (counter ← counter - 1) = 0 THEN
        BEGIN
        tryHarder ← tryHarder + 1;
        longRangeMode ← (tryHarder MOD 24) = 0;
        ProbeForBootFiles[];
        counter ← probeTicks;
        END;
      ENDLOOP;
    END;

  ProbeForBootFiles: INTERNAL PROCEDURE =
    BEGIN
    IF probing OR sloshing THEN RETURN;
    probing ← TRUE;
    FOR bf: BootServerFriends.BootFile ← first↑, bf.next UNTIL bf = NIL DO
      bf.tries ← 0; ENDLOOP;
    IF longRangeMode THEN Process.Detach[FORK LongRangeProbe[]]
    ELSE Process.Detach[FORK ShortRangeProbe[]];
    END;

  ProbeSomeMore: ENTRY PROCEDURE =
    BEGIN
    IF probing OR sloshing THEN RETURN;
    probing ← TRUE;
    IF longRangeMode THEN Process.Detach[FORK LongRangeProbe[]]
    ELSE Process.Detach[FORK ShortRangeProbe[]];
    END;

  ShortRangeProbe: PROCEDURE =
    BEGIN OPEN PupDefs;
    FixupPicture[];
    ProbeOne[PupTypes.allNets];
    probing ← FALSE;
    FixupPicture[];
    IF ~sloshing THEN BootServerDefs.SendBootDir[NIL];
    END;

  LongRangeProbe: PROCEDURE =
    BEGIN
    FixupPicture[];
    FOR net: CARDINAL IN [1..PupDefs.defaultNumberOfNetworks) DO
      IF PupDefs.GetHopsToNetwork[[net]] > 3 THEN LOOP;
      ProbeOne[[net]];
      IF sloshing THEN EXIT;
      ENDLOOP;
    probing ← FALSE;
    FixupPicture[];
    IF ~sloshing THEN BootServerDefs.SendBootDir[NIL];
    END;

  ProbeOne: PROCEDURE [net: PupTypes.PupNetID] =
    BEGIN OPEN PupDefs;
    pool: Buffer.AccessHandle ← Buffer.MakePool[send: 1, receive: 10];
    soc: PupSocket ← PupSocketMake[
      UniqueLocalPupSocketID[],
      [net, PupTypes.allHosts, PupTypes.miscSrvSoc],
      SecondsToTocks[2]];
    b: PupBuffer;
    THROUGH [0..5) UNTIL pleaseStop OR sloshing DO
      b ← Buffer.GetBuffer[pup, pool, send];
      b.pup.pupType ← bootDirReq;
      SetPupContentsWords[b, 0];
      soc.put[b];
      UNTIL pleaseStop OR sloshing DO
        b ← soc.get[];
        IF b = NIL THEN EXIT;
        IF b.pup.pupType # bootDirReply THEN BEGIN Buffer.ReturnBuffer[b]; LOOP; END;
        LookAtBootDir[b];
        ENDLOOP;
      ENDLOOP;
    PupSocketDestroy[soc];
    Buffer.DestroyPool[pool];
    END;

  LookAtBootDir: PUBLIC ENTRY PROCEDURE [b: PupDefs.PupBuffer] =
    BEGIN OPEN PupDefs;
    bf: BootServerFriends.BootFile ← NIL;
    where: PupAddress ← b.pup.source;
    name: STRING = [256];
    word, size, end: CARDINAL;
    timeStamp: System.GreenwichMeanTime;
    timeStampLocation: LONG POINTER TO PupWireFormat.BcplLongNumber;
    now: System.GreenwichMeanTime ← System.GetGreenwichMeanTime[];
    heNeedsOne: BOOLEAN ← FALSE;
    IF pleaseStop THEN GOTO Stopping;
    IF sloshing THEN GOTO AlreadySloshing;
    BEGIN
    network: Network ← b.network;
    IF network.pupNetNumber = b.pup.source.net
      AND network.pupHostNumber = b.pup.source.host THEN GOTO FromMe;
    END;
    end ← GetPupContentsBytes[b]/2;
    word ← 0;
    UNTIL word >= end OR bf # NIL DO
      timeStampLocation ← LOOPHOLE[@b.pup.pupWords[word + 1]];
      timeStamp ← LOOPHOLE[PupWireFormat.BcplToMesaLongNumber[
        timeStampLocation↑]];
      StringCopy[LOOPHOLE[@b.pup.pupWords[word + 1 + 2]], name];
      size ← ((1 + name.length) + 1)/2;
      FOR bf ← first↑, bf.next UNTIL bf = NIL DO
        IF ~bf.pup THEN LOOP;
        IF b.pup.pupWords[word] >= 100000B THEN LOOP;
        IF bf.inTransit THEN LOOP;
        IF bf.code # BootServer.CardinalToPupBFN[b.pup.pupWords[word]] THEN
          LOOP;
        IF ~String.Equivalent[name, bf.fileName] THEN LOOP;
        IF Newer[timeStamp, now] THEN LOOP;  -- Don't propagate garbage
        IF ~bf.unknown AND bf.create # BootServerFriends.timeNotKnown
          AND Newer[bf.create, timeStamp] THEN heNeedsOne ← TRUE;
        IF ~bf.unknown AND ~Newer[timeStamp, bf.create] THEN LOOP;
        IF bf.tries > 1 THEN LOOP;
        EXIT;
        ENDLOOP;
      word ← word + size + 1 + 2;
      ENDLOOP;
    IF sloshing THEN GOTO Sloshing;
    -- This is just a hack to let him get the new version sooner.  It should all work ok without this code.
    IF heNeedsOne THEN
      BEGIN
      b.pup.dest.socket ← PupTypes.miscSrvSoc;
      BootServerDefs.SendBootDir[b];
      END
    ELSE Buffer.ReturnBuffer[b];
    -- Don't call AppendHostName (or friends) from here.  The lock is still locked.
    IF bf # NIL THEN
      BEGIN
      sloshing ← TRUE;
      bf.tries ← bf.tries + 1;
      Process.Detach[FORK GetNewBootFile[bf, where]];
      RETURN;
      END;
    EXITS AlreadySloshing, FromMe, Stopping, Sloshing => Buffer.ReturnBuffer[b];
    END;

  StringCopy: PROCEDURE [
    s: LONG POINTER TO PupWireFormat.BcplSTRING, d: LONG STRING] =
    BEGIN
    d.length ← s.length;
    FOR i: CARDINAL IN [0..s.length) DO d[i] ← s.char[i]; ENDLOOP;
    END;

  GetNewBootFile: PROCEDURE [
    bf: BootServerFriends.BootFile, where: PupDefs.PupAddress] =
    BEGIN OPEN PupDefs;
    pool: Buffer.AccessHandle;
    hisName: STRING = [30];
    from: PupAddress ← UniqueLocalPupAddress[@where];
    fh: BootServerFriends.FileHandle;
    sh: Stream.Handle;
    status: Slosh.RecvStatus;
    Ask: PROCEDURE =
      BEGIN
      b: PupBuffer ← Buffer.GetBuffer[pup, pool, send];
      b.pup.source ← from;
      b.pup.dest ← where;
      b.pup.pupID ← [0, BootServer.PupBFNToCardinal[bf.code]];
      SendPup[b, bootFileSend, 0];
      END;
    AppendHostName[hisName, where];
    slosheeHost ← hisName;
    slosheeFileName ← bf.fileName;
    FixupPicture[];
    IF verbose THEN StartupMessage[bf, where];
    DoSomeYields[];
    [fh, sh] ← BootServerFriends.CreateTempFile[];
    IF fh = NIL THEN
      BEGIN
      Message["Rats, disk full or something (can't get temp file)."L];
      sloshing ← FALSE;
      slosheeHost ← slosheeFileName ← NIL;
      FixupPicture[];
      RETURN;
      END;
    pool ← Buffer.MakePool[send: 1, receive: 0];
    status ← Slosh.RecvFile[msg, bf.fileName, sh, from, Ask];
    Buffer.DestroyPool[pool];
    IF status = statusStoreOk THEN
      BEGIN
      IF bf.unknown THEN bf.inTransit ← TRUE;
      IF ~BootServerFriends.MakeTempFileIntoBootFile[bf, fh] THEN
        Message["Oops, after all that, it didn't quite work."L];
      END
    ELSE
      BEGIN
      n: CARDINAL ← Slosh.RetransmissionInterval[];
      BootServerFriends.DeleteTempFile[fh];
      IF status = statusDiskFull THEN bf.tries ← bf.tries + 1;
      THROUGH [0..n) UNTIL pleaseStop DO
        Process.Pause[Process.MsecToTicks[1000]]; ENDLOOP;
      END;
    sloshing ← FALSE;
    slosheeHost ← slosheeFileName ← NIL;
    FixupPicture[];
    IF pleaseStop OR probing THEN RETURN;
    DoSomeYields[];
    ProbeSomeMore[];
    END;

  StartupMessage: PROCEDURE [
    bf: BootServerFriends.BootFile, where: PupDefs.PupAddress] =
    BEGIN
    text: STRING = [100];
    Time.AppendCurrent[text];
    String.AppendString[text, "  Found "L];
    IF ~bf.unknown THEN String.AppendString[text, "newer version of "L];
    String.AppendString[text, bf.fileName];
    String.AppendString[text, " (#"L];
    BootServer.AppendBFN[text, bf.code];
    String.AppendString[text, ") on "L];
    PupDefs.AppendHostName[text, where];
    LogString[text];
    END;

  BootServerStats: PUBLIC PROCEDURE [b: PupDefs.PupBuffer] =
    BEGIN OPEN Stats;
    WireCounter: PROCEDURE [s: StatCounterIndex]
      RETURNS [PupWireFormat.BcplLongNumber] =
      BEGIN RETURN[PupWireFormat.MesaToBcplLongNumber[StatGetCounter[s]]]; END;
    bse: LONG POINTER TO BootStatsEntry ← LOOPHOLE[@b.pup.pupWords];
    bse↑ ← [
      version: bootVersion, directories: WireCounter[statBootDir],
      fastSends: WireCounter[statFileSent],
      slowSends: WireCounter[statFileSentSlow],
      filesRecv: WireCounter[statBootNew]];
    PupDefs.ReturnPup[b, bootStatsReply, 2*SIZE[BootStatsEntry]];
    END;

  Message: PROCEDURE [s1, s2, s3, s4: LONG STRING ← NIL] =
    BEGIN
    text: STRING = [200];
    String.AppendString[text, "BootServer: "L];
    String.AppendString[text, s1];
    IF s2 # NIL THEN String.AppendString[text, s2];
    IF s3 # NIL THEN String.AppendString[text, s3];
    IF s4 # NIL THEN String.AppendString[text, s4];
    LogString[text];
    END;

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

  DoSomeYields: PROCEDURE =
    BEGIN THROUGH [0..100) DO Process.Yield[]; ENDLOOP; END;

  -- initialization
  END.