-- Copyright (C) 1983  by Xerox Corporation. All rights reserved. 
-- FixTimeServers.mesa, HGM, 24-Sep-83 14:58:55

DIRECTORY
  Display USING [Bitmap, Invert, replaceFlags, White],
  Format USING [],  -- Needed by Put.Number and Put.Date
  FormSW USING [
    AllocateItemDescriptor, ClientItemsProcType, CommandItem, Display, FindItem,
    ItemHandle, LongNumberItem, ModifyEditable, newLine, NumberItem, ProcType, StringItem],
  Heap USING [systemZone],
  MsgSW USING [Post],
  Profile USING [GetUser],
  Process USING [Detach, MsecToTicks, Pause],
  Put USING [Char, CR, Decimal, Text, Line, Number, LongDecimal, LongNumber],
  Runtime USING [GetBcdTime, IsBound],
  SpecialSystem USING [AdjustClock],
  String USING [AppendString, AppendNumber],
  System USING [
    GreenwichMeanTime, GetClockPulses,
    GetGreenwichMeanTime, Pulses, PulsesToMicroseconds],
  Time USING [Append, AppendCurrent, Unpack],
  Tool USING [
    Create, MakeSWsProc, UnusedLogName, MakeMsgSW, MakeFormSW, MakeFileSW,
    AddThisSW],
  ToolWindow USING [CreateSubwindow, DisplayProcType, nullBox, TransitionProcType],
  UserInput USING [
    CreateIndirectStringOut, DestroyIndirectStringOut, GetDefaultWindow, SetUserAbort, UserAbort],
  Window USING [Handle, Box],

  Buffer USING [AccessHandle, DestroyPool, GetBuffer, MakePool, ReturnBuffer],
  GateDefs USING [typescript],
  Password USING [Status, ValidMemberOfGroup],
  PupDefs USING [
    PupPackageMake, PupPackageDestroy, GetLocalPupAddress,
    PupBuffer, PupSocket, PupSocketDestroy, PupSocketMake,
    defaultNumberOfNetworks, GetHopsToNetwork, SecondsToTocks,
    SetPupContentsWords, AppendPupAddress, AppendHostName, AppendErrorPup,
    GetPupAddress, PupNameTrouble],
  PupTimeServerFormat USING [
    PupTimeFormat, resetTimeReply, resetTimeRequest, timeStatsRequest,
    timeStatsReply],
  PupTypes USING [PupAddress, fillInSocketID, miscSrvSoc],
  PupWireFormat USING [BcplLongNumber, BcplToMesaLongNumber],
  TimeServerOps USING [InsertTime, SetClockError];

FixTimeServers: PROGRAM
  IMPORTS
    Display, FormSW, Heap, MsgSW, Process, Profile, Put, Runtime, SpecialSystem, String,
    System, Time, Tool, ToolWindow, UserInput,
    Buffer, GateDefs, PupWireFormat, Password, PupDefs, TimeServerOps =
  BEGIN OPEN PupDefs, PupTypes;

  z: UNCOUNTED ZONE = Heap.systemZone;

  msg, form, boxes, log: Window.Handle ← NIL;

  defaultMaxHops: CARDINAL = 3;
  defaultStartingError: LONG CARDINAL = 30000;
  stopUpdating: BOOLEAN ← FALSE;
  running: BOOLEAN ← FALSE;
  indicator: {left, right, off} ← off;
  first: Handle ← NIL;
  maxHops: CARDINAL ← defaultMaxHops;
  startingError: LONG CARDINAL ← defaultStartingError;
  where: PupAddress ← [[0], [0], PupTypes.miscSrvSoc];
  now, target: LONG STRING ← NIL;

  Handle: TYPE = LONG POINTER TO Object;
  Object: TYPE = RECORD [
    next: Handle,
    where: PupTypes.PupAddress,
    on: BOOLEAN,
    known: BOOLEAN,
    offset: LONG INTEGER,
    delay: System.Pulses];

  ScanCircle: FormSW.ProcType =
    BEGIN
    IF running THEN
      BEGIN MsgSW.Post[msg, "Somebody is already running..."L]; RETURN; END;
    WriteCR[];
    WriteCurrentDateAndTime[];
    running ← TRUE;
    Process.Detach[FORK ScanSeveral[]];
    END;

  ScanTarget: FormSW.ProcType =
    BEGIN
    IF running THEN
      BEGIN MsgSW.Post[msg, "Somebody is already running..."L]; RETURN; END;
    WriteCR[];
    WriteCurrentDateAndTime[];
    WriteString["  Finding time on "L];
    IF ~FindPath[] THEN RETURN;
    running ← TRUE;
    Process.Detach[FORK ScanOne[]];
    END;

  ResetLocalTimeFromTarget: FormSW.ProcType =
    BEGIN
    IF running THEN
      BEGIN MsgSW.Post[msg, "Somebody is already running..."L]; RETURN; END;
    WriteCR[];
    WriteCurrentDateAndTime[];
    WriteString["  Resetting time from "L];
    IF ~FindPath[] THEN RETURN;
    running ← TRUE;
    Process.Detach[FORK ResetLocalFromTarget[]];
    END;

  FixupCircle: FormSW.ProcType =
    BEGIN
    IF running THEN
      BEGIN MsgSW.Post[msg, "Somebody is already running..."L]; RETURN; END;
    WriteCR[];
    WriteCurrentDateAndTime[];
    running ← TRUE;
    Process.Detach[FORK FixupSeveral[]];
    END;

  FixupTarget: FormSW.ProcType =
    BEGIN
    IF running THEN
      BEGIN MsgSW.Post[msg, "Somebody is already running..."L]; RETURN; END;
    WriteCR[];
    WriteCurrentDateAndTime[];
    WriteString["  Finding time servers on "L];
    IF ~FindPath[] THEN RETURN;
    running ← TRUE;
    Process.Detach[FORK FixupOne[]];
    END;

  Off: PROCEDURE =
    BEGIN
    IF ~running THEN RETURN;
    UserInput.SetUserAbort[log];
    WHILE running DO Process.Pause[1]; ENDLOOP;
    END;

  FindPath: PROCEDURE RETURNS [BOOLEAN] =
    BEGIN
    WriteString[target];
    WriteChar['=];
    GetPupAddress[
      @where, target !
      PupNameTrouble =>
        BEGIN MsgSW.Post[msg, e]; WriteLine[e]; GOTO Trouble; END];
    PrintPupAddress[where];
    WriteLine["."L];
    RETURN[TRUE];
    EXITS Trouble => RETURN[FALSE];
    END;

  ResetLocalFromTarget: PROCEDURE =
    BEGIN
    pool: Buffer.AccessHandle ← Buffer.MakePool[send: 1, receive: 10];
    soc: PupSocket ← PupSocketMake[fillInSocketID, where, SecondsToTocks[5]];
    sequenceNumber: CARDINAL ← GetNextSequenceNumber[];
    hit: BOOLEAN ← FALSE;
    FOR i: CARDINAL IN [0..5) UNTIL hit OR UserInput.UserAbort[log] DO
      b: PupBuffer ← Buffer.GetBuffer[pup, pool, send];
      b.pup.pupID.a ← b.pup.pupID.b ← sequenceNumber;
      b.pup.pupType ← dateAltoRequest;
      SetPupContentsWords[b, 0];
      soc.put[b];
      UNTIL hit OR (b ← soc.get[]) = NIL DO
        SELECT TRUE FROM
          ((b.pup.pupType # dateAltoIs) OR (b.pup.pupID.a # sequenceNumber)
            OR (b.pup.pupID.b # sequenceNumber)) =>
            BEGIN
            temp: STRING = [100];
            PupDefs.AppendErrorPup[temp, b];
            MsgSW.Post[msg, temp];
            END;
          ENDCASE =>
            BEGIN
            old: System.GreenwichMeanTime = System.GetGreenwichMeanTime[];
            new: System.GreenwichMeanTime;
            timeInfo: LONG POINTER TO PupTimeServerFormat.PupTimeFormat;
            timeInfo ← LOOPHOLE[@b.pup.pupWords[0]];
            new ← LOOPHOLE[PupWireFormat.BcplToMesaLongNumber[timeInfo.time]];
	    TimeServerOps.InsertTime[new, TRUE, startingError];
            WriteCurrentDateAndTime[];
            Put.Text[log, "  Time reset from "L];
            PrintPupAddress[where];
            Put.Text[log, ", correction was "L];
            Put.LongDecimal[log, new - old];
            Put.Line[log, "."L];
            hit ← TRUE;
            UpdateNow[];
            EXIT;
            END;
        Buffer.ReturnBuffer[b];
        b ← NIL;
        ENDLOOP;
      IF b # NIL THEN Buffer.ReturnBuffer[b];
      ENDLOOP;
    PupSocketDestroy[soc];
    Buffer.DestroyPool[pool];
    running ← FALSE;
    END;

  ScanSeveral: PROCEDURE =
    BEGIN
    first: BOOLEAN ← TRUE;
    SetupBoxes[];
    FOR net: CARDINAL IN [1..PupDefs.defaultNumberOfNetworks) UNTIL UserInput.UserAbort[log] DO
      IF GetHopsToNetwork[[net]] > maxHops THEN LOOP;
      where ← [[net], [0], miscSrvSoc];
      IF first THEN Put.Text[log, "  Searching network "]
      ELSE Put.Text[log, ", "];
      Put.Number[log, net, [8, FALSE, TRUE, 0]];
      Put.Char[log, '(];
      Put.Decimal[log, GetHopsToNetwork[[net]]];
      Put.Char[log, ')];
      ScanSingle[];
      first ← FALSE;
      ENDLOOP;
    Put.CR[log];
    FindHiddenServers[];
    SetDownBoxes[];
    PrintTimeServerList[];
    DeleteList[];
    running ← FALSE;
    END;

  ScanOne: PROCEDURE =
    BEGIN
    SetupBoxes[];
    ScanSingle[];
    FindHiddenServers[];
    SetDownBoxes[];
    PrintTimeServerList[];
    DeleteList[];
    running ← FALSE;
    END;

  ScanSingle: PROCEDURE =
    BEGIN
    pool: Buffer.AccessHandle ← Buffer.MakePool[send: 1, receive: 10];
    soc: PupSocket ← PupSocketMake[fillInSocketID, where, SecondsToTocks[5]];
    sequenceNumber: CARDINAL ← GetNextSequenceNumber[];
    launch: System.GreenwichMeanTime;
    pulses: System.Pulses;
    FOR i: CARDINAL IN [0..5) DO
      b: PupBuffer ← Buffer.GetBuffer[pup, pool, send];
      b.pup.pupID.a ← b.pup.pupID.b ← sequenceNumber;
      b.pup.pupType ← PupTimeServerFormat.timeStatsRequest;
      SetPupContentsWords[b, 0];
      soc.put[b];
      b ← Buffer.GetBuffer[pup, pool, send];
      b.pup.pupID.a ← b.pup.pupID.b ← sequenceNumber;
      b.pup.pupType ← dateAltoRequest;
      SetPupContentsWords[b, 0];
      launch ← System.GetGreenwichMeanTime[];
      pulses ← System.GetClockPulses[];
      soc.put[b];
      UNTIL (b ← soc.get[]) = NIL DO
        delay: System.Pulses = System.Pulses[System.GetClockPulses[] - pulses];
        SELECT TRUE FROM
          (b.pup.pupType = PupTimeServerFormat.timeStatsReply
            AND (b.pup.pupID.a = sequenceNumber) AND (b.pup.pupID.b = sequenceNumber)) =>
            BEGIN
            FlipBoxes[];
            AddToList[b.pup.source, FALSE, LAST[LONG INTEGER], delay];
            END;
          ((b.pup.pupType = dateAltoIs) AND (b.pup.pupID.a = sequenceNumber)
            AND (b.pup.pupID.b = sequenceNumber)) =>
            BEGIN FlipBoxes[]; LookAtTimeResponse[b, launch, delay]; END;
          ENDCASE =>
            BEGIN
            temp: STRING = [100];
            PupDefs.AppendErrorPup[temp, b];
            MsgSW.Post[msg, temp];
            END;
        Buffer.ReturnBuffer[b];
        ENDLOOP;
      IF b # NIL THEN Buffer.ReturnBuffer[b];
      ENDLOOP;
    PupSocketDestroy[soc];
    Buffer.DestroyPool[pool];
    END;

  FindHiddenServers: PROCEDURE =
    BEGIN
    once: BOOLEAN ← FALSE;
    FOR ts: Handle ← first, ts.next UNTIL ts = NIL DO
      IF ts.on THEN LOOP;
      IF ~once THEN
        BEGIN
        WriteCurrentDateAndTime[];
        WriteString["  Checking on Servers that appear to be off: "L];
        once ← TRUE;
        END
      ELSE Put.Text[log, ", "L];
      PrintPupAddress[ts.where];
      FindHiddenServer[ts];
      ENDLOOP;
    IF once THEN Put.Line[log, "."L];
    END;

  FindHiddenServer: PROCEDURE [ts: Handle] =
    BEGIN
    pool: Buffer.AccessHandle ← Buffer.MakePool[send: 1, receive: 10];
    soc: PupSocket ← PupSocketMake[fillInSocketID, ts.where, SecondsToTocks[2]];
    sequenceNumber: CARDINAL ← GetNextSequenceNumber[];
    launch: System.GreenwichMeanTime;
    pulses: System.Pulses;
    FOR i: CARDINAL IN [0..5) DO
      b: PupBuffer ← Buffer.GetBuffer[pup, pool, send];
      b.pup.pupID.a ← b.pup.pupID.b ← sequenceNumber;
      b.pup.pupType ← dateAltoRequest;
      SetPupContentsWords[b, 0];
      launch ← System.GetGreenwichMeanTime[];
      pulses ← System.GetClockPulses[];
      soc.put[b];
      UNTIL (b ← soc.get[]) = NIL DO
        delay: System.Pulses = System.Pulses[System.GetClockPulses[] - pulses];
        SELECT TRUE FROM
          ((b.pup.pupType = dateAltoIs) AND (b.pup.pupID.a = sequenceNumber)
            AND (b.pup.pupID.b = sequenceNumber)) =>
            BEGIN
            FlipBoxes[];
            LookAtSpecificTimeResponse[ts, b, launch, delay];
            END;
          ENDCASE =>
            BEGIN
            temp: STRING = [100];
            PupDefs.AppendErrorPup[temp, b];
            MsgSW.Post[msg, temp];
            END;
        Buffer.ReturnBuffer[b];
        ENDLOOP;
      IF b # NIL THEN Buffer.ReturnBuffer[b];
      ENDLOOP;
    PupSocketDestroy[soc];
    Buffer.DestroyPool[pool];
    END;

  FixupSeveral: PROCEDURE =
    BEGIN
    first: BOOLEAN ← TRUE;
    SetupBoxes[];
    FOR net: CARDINAL IN [1..PupDefs.defaultNumberOfNetworks)
    UNTIL UserInput.UserAbort[log] DO
      IF GetHopsToNetwork[[net]] > maxHops THEN LOOP;
      where ← [[net], [0], miscSrvSoc];
      IF first THEN Put.Text[log, "  Finding time servers on network "]
      ELSE Put.Text[log, ", "];
      Put.Number[log, net, [8, FALSE, TRUE, 0]];
      Put.Char[log, '(];
      Put.Decimal[log, GetHopsToNetwork[[net]]];
      Put.Char[log, ')];
      ScanSingle[];
      first ← FALSE;
      ENDLOOP;
    Put.CR[log];
    FixupList[];
    DeleteList[];
    SetDownBoxes[];
    running ← FALSE;
    END;

  FixupOne: PROCEDURE =
    BEGIN
    SetupBoxes[];
    ScanSingle[];
    FixupList[];
    DeleteList[];
    SetDownBoxes[];
    running ← FALSE;
    END;

  FixupList: PROCEDURE =
    BEGIN
    FOR ts: Handle ← first, ts.next UNTIL ts = NIL DO
      IF ~ts.on THEN LOOP;  -- don't muck with IFSs
      WriteCurrentDateAndTime[];
      Put.Text[log, "  Resetting time on "L];
      PrintPupAddress[ts.where];
      Put.Text[log, "..."L];
      IF ResetOne[ts.where] THEN Put.Line[log, "ok."L]
      ELSE Put.Line[log, "no response."L];
      ENDLOOP;
    END;

  ResetOne: PROCEDURE [where: PupAddress] RETURNS [worked: BOOLEAN] =
    BEGIN
    pool: Buffer.AccessHandle ← Buffer.MakePool[send: 1, receive: 10];
    soc: PupSocket ← PupSocketMake[fillInSocketID, where, SecondsToTocks[5]];
    sequenceNumber: CARDINAL ← GetNextSequenceNumber[];
    magic: WORD = 27182;
    worked ← FALSE;
    FOR i: CARDINAL IN [0..5) UNTIL worked OR UserInput.UserAbort[log] DO
      b: PupBuffer ← Buffer.GetBuffer[pup, pool, send];
      b.pup.pupID.a ← magic;
      b.pup.pupID.b ← sequenceNumber;
      b.pup.pupType ← PupTimeServerFormat.resetTimeRequest;
      b.pup.address ← GetLocalPupAddress[miscSrvSoc, @where];
      SetPupContentsWords[b, SIZE[PupAddress]];
      soc.put[b];
      UNTIL worked OR (b ← soc.get[]) = NIL DO
        SELECT TRUE FROM
          ((b.pup.pupType # PupTimeServerFormat.resetTimeReply) OR (b.pup.pupID.a # magic)
            OR (b.pup.pupID.b # sequenceNumber)) =>
            BEGIN
            temp: STRING = [100];
            PupDefs.AppendErrorPup[temp, b];
            MsgSW.Post[msg, temp];
            END;
          ENDCASE => BEGIN FlipBoxes[]; worked ← TRUE; END;
        Buffer.ReturnBuffer[b];
        b ← NIL;
        ENDLOOP;
      IF b # NIL THEN Buffer.ReturnBuffer[b];
      ENDLOOP;
    PupSocketDestroy[soc];
    Buffer.DestroyPool[pool];
    END;

  LookAtTimeResponse: PUBLIC PROCEDURE [
    b: PupDefs.PupBuffer, t: System.GreenwichMeanTime, delay: System.Pulses] =
    BEGIN
    timeStamp: System.GreenwichMeanTime;
    timeInfo: LONG POINTER TO PupTimeServerFormat.PupTimeFormat;
    offset: LONG INTEGER;
    timeInfo ← LOOPHOLE[@b.pup.pupWords[0]];
    timeStamp ← LOOPHOLE[PupWireFormat.BcplToMesaLongNumber[timeInfo.time]];
    offset ← timeStamp - t;
    AddToList[b.pup.source, TRUE, offset, delay];
    END;

  LookAtSpecificTimeResponse: PUBLIC PROCEDURE [
    ts: Handle, b: PupDefs.PupBuffer, t: System.GreenwichMeanTime,
    delay: System.Pulses] =
    BEGIN
    timeStamp: System.GreenwichMeanTime;
    timeInfo: LONG POINTER TO PupTimeServerFormat.PupTimeFormat;
    offset: LONG INTEGER;
    timeInfo ← LOOPHOLE[@b.pup.pupWords[0]];
    timeStamp ← LOOPHOLE[PupWireFormat.BcplToMesaLongNumber[timeInfo.time]];
    offset ← timeStamp - t;
    ts.offset ← MIN[ts.offset, offset];
    IF delay < ts.delay THEN ts.delay ← delay;
    ts.known ← TRUE;
    END;

  AddToList: PROCEDURE [
    where: PupTypes.PupAddress, on: BOOLEAN, offset: LONG INTEGER,
    delay: System.Pulses] =
    BEGIN
    finger: Handle ← NIL;
    new: Handle;
    FOR ts: Handle ← first, ts.next UNTIL ts = NIL DO
      IF where = ts.where THEN
        BEGIN
        IF on THEN ts.offset ← MIN[ts.offset, offset];
        IF delay < ts.delay THEN ts.delay ← delay;
        ts.known ← ts.on ← ts.on OR on;
        RETURN;
        END;
      IF LessPupAddress[ts.where, where] THEN finger ← ts;
      ENDLOOP;
    new ← z.NEW[Object];
    new↑ ← [
      next: NIL, where: where, on: on, known: on, offset: offset, delay: delay];
    SELECT TRUE FROM
      first = NIL => first ← new;  -- first
      finger = NIL => BEGIN new.next ← first; first ← new; END;  -- insert at front of list
      ENDCASE => BEGIN new.next ← finger.next; finger.next ← new; END;  -- middle or end
    END;

  LessPupAddress: PROCEDURE [a, b: PupAddress] RETURNS [BOOLEAN] =
    BEGIN
    IF a.net < b.net THEN RETURN[TRUE];
    IF a.net > b.net THEN RETURN[FALSE];
    IF a.host < b.host THEN RETURN[TRUE];
    IF a.host > b.host THEN RETURN[FALSE];
    IF a.socket.a < b.socket.a THEN RETURN[TRUE];
    IF a.socket.a > b.socket.a THEN RETURN[FALSE];
    IF a.socket.b < b.socket.b THEN RETURN[TRUE];
    IF a.socket.b > b.socket.b THEN RETURN[FALSE];
    RETURN[FALSE];
    END;

  DeleteList: PROCEDURE =
    BEGIN
    ts: Handle ← first;
    UNTIL ts = NIL DO
      next: Handle ← ts.next; z.FREE[@ts]; ts ← next; ENDLOOP;
    first ← NIL;
    END;

  PrintTimeServerList: PROCEDURE =
    BEGIN
    Put.Line[log, "Offset      Delay    Name+Address"L];
    FOR ts: Handle ← first, ts.next UNTIL ts = NIL DO
      temp: STRING = [40];
      ms: LONG CARDINAL = System.PulsesToMicroseconds[ts.delay]/1000;
      PupDefs.AppendHostName[temp, ts.where];
      IF ts.known THEN Put.LongNumber[log, ts.offset, [10, FALSE, FALSE, 6]]
      ELSE Put.Text[log, "      "L];
      IF ts.on THEN Put.Text[log, "     "L] ELSE Put.Text[log, " Off "L];
      Put.LongNumber[log, ms, [10, FALSE, FALSE, 6]];
      Put.Text[log, "  "L];
      Put.Text[log, temp];
      Put.Text[log, " = "L];
      PrintPupAddress[ts.where];
      Put.CR[log];
      ENDLOOP;
    END;

  nextSequenceNumber: CARDINAL ← 123;
  GetNextSequenceNumber: PROCEDURE RETURNS [CARDINAL] =
    BEGIN RETURN[nextSequenceNumber ← nextSequenceNumber + 1]; END;

  Enable: FormSW.ProcType =
    BEGIN
    person: STRING = [100];
    pwd: STRING = [100];
    status: Password.Status;
    SaveUserInfo: PROCEDURE [name, password: LONG STRING] =
      BEGIN
      String.AppendString[person, name];
      String.AppendString[pwd, password];
      END;
    Profile.GetUser[SaveUserInfo, registry];
    status ← Password.ValidMemberOfGroup[person, pwd, "TimeFixers↑.internet"L];
    SELECT status FROM
      yes =>
        BEGIN
        FormSW.FindItem[form, circleIX].flags.invisible ← FALSE;
        FormSW.FindItem[form, targetIX].flags.invisible ← FALSE;
        FormSW.Display[form];
        END;
      nil => MsgSW.Post[msg, "Name or Password is NIL."L];
      allDown => MsgSW.Post[msg, "All Grapevine servers appear to be down."L];
      notFound => MsgSW.Post[msg, "Grapevine doesn't like your name."L];
      badPwd => MsgSW.Post[msg, "Grapevine doesn't like your password."L];
      group => MsgSW.Post[msg, "Grapevine thinks you are a group."L];
      no => MsgSW.Post[msg, "You are not in TimeFixers↑.internet."L];
      notGroup =>
        MsgSW.Post[msg, "Grapevine doesn't recognize TimeFixers↑.internet."L];
      error => MsgSW.Post[msg, "Error from GrapevineUser package."L];
      ENDCASE => ERROR;
    END;

  SetStartingError: FormSW.ProcType =
    BEGIN
    TimeServerOps.SetClockError[startingError];
    END;

  ForwardOneMin: FormSW.ProcType =
    BEGIN
    AdjustClock[60];
    END;

  ForwardTenSec: FormSW.ProcType =
    BEGIN
    AdjustClock[10];
    END;

  ForwardOneSec: FormSW.ProcType =
    BEGIN
    AdjustClock[1];
    END;

  BackOneMin: FormSW.ProcType =
    BEGIN
    AdjustClock[-60];
    END;

  BackTenSec: FormSW.ProcType =
    BEGIN
    AdjustClock[-10];
    END;

  BackOneSec: FormSW.ProcType =
    BEGIN
    AdjustClock[-1];
    END;
    
  AdjustClock: PROCEDURE [seconds: INTEGER] =
    BEGIN
    SpecialSystem.AdjustClock[seconds];
    UpdateNow[];
    END;

  -- IO things

  WriteChar: PROCEDURE [c: CHARACTER] = BEGIN Put.Char[log, c]; END;

  WriteCR: PROCEDURE = BEGIN Put.CR[log]; END;

  WriteString: PROCEDURE [s: LONG STRING] = BEGIN Put.Text[log, s]; END;

  WriteLine: PROCEDURE [s: LONG STRING] = BEGIN Put.Line[log, s]; END;

  WriteLongDecimal: PROCEDURE [n: LONG CARDINAL] =
    BEGIN Put.LongDecimal[log, n]; END;

  WriteDecimal: PROCEDURE [n: CARDINAL] = INLINE BEGIN WriteNumber[n, 10, 0]; END;

  WriteOctal: PROCEDURE [n: CARDINAL] = INLINE BEGIN WriteNumber[n, 8, 0]; END;

  WriteNumber: PROCEDURE [n, radix, width: CARDINAL] = INLINE
    BEGIN
    temp: STRING = [25];
    String.AppendNumber[temp, n, radix];
    THROUGH [temp.length..width) DO WriteChar[' ]; ENDLOOP;
    WriteString[temp];
    END;

  D8: PROCEDURE [n: CARDINAL] = BEGIN WriteNumber[n, 10, 8]; END;

  O3: PROCEDURE [n: CARDINAL] = BEGIN WriteNumber[n, 8, 3]; END;

  O6: PROCEDURE [n: CARDINAL] = BEGIN WriteNumber[n, 8, 3]; END;

  O9: PROCEDURE [n: CARDINAL] = BEGIN WriteNumber[n, 8, 9]; END;

  WriteCurrentDateAndTime: PROCEDURE =
    BEGIN time: STRING = [20]; Time.AppendCurrent[time]; WriteString[time]; END;

  PrintPupAddress: PROCEDURE [a: PupAddress] =
    BEGIN temp: STRING = [40]; AppendPupAddress[temp, a]; WriteString[temp]; END;

  indicatorBox: Window.Box = [[25, 10], [16, 16]];
  DisplayBoxes: ToolWindow.DisplayProcType =
    BEGIN
    pattern: ARRAY [0..1] OF ARRAY [0..8) OF WORD;
    left: WORD = 177400B;
    right: WORD = 000377B;
    SELECT indicator FROM
      left => pattern ← [ALL[left], ALL[right]];
      right => pattern ← [ALL[right], ALL[left]];
      off => pattern ← [ALL[0], ALL[0]];
      ENDCASE;
    Display.Bitmap[window, indicatorBox, [@pattern, 0, 0], 16, Display.replaceFlags]
    END;

  SetupBoxes: PROCEDURE = BEGIN indicator ← left; DisplayBoxes[boxes]; END;

  FlipBoxes: PROCEDURE =
    BEGIN
    SELECT indicator FROM
      left => indicator ← right;
      off, right => indicator ← left;
      ENDCASE;
    Display.Invert[boxes, indicatorBox];
    END;

  SetDownBoxes: PROCEDURE =
    BEGIN indicator ← off; Display.White[boxes, indicatorBox]; END;

  MakeBoxesSW: PROCEDURE [window: Window.Handle] =
    BEGIN
    box: Window.Box ← ToolWindow.nullBox;
    box.dims.h ← 36;
    boxes ← ToolWindow.CreateSubwindow[parent: window, display: DisplayBoxes, box: box];
    Tool.AddThisSW[window: window, sw: boxes, swType: vanilla];
    END;

  MakeSWs: Tool.MakeSWsProc =
    BEGIN
    logFileName: STRING = [40];
    msg ← Tool.MakeMsgSW[window: window, lines: 5];
    form ← Tool.MakeFormSW[window: window, formProc: MakeForm];
    MakeBoxesSW[window];
    Tool.UnusedLogName[logFileName, "FixTimeServers.log$"L];
    log ← Tool.MakeFileSW[window: window, name: logFileName];
    Put.Line[
      log,
      "
Offset is in seconds.
  It is positive if the remote clock is faster than ours.
The phone company time server in Palo Alto is 767-8900.
Target can be a net as well as a specific machine.
  That's why it takes longer than it should to do simple things.
"L];
    END;

  UpdateIt: PROCEDURE =
    BEGIN
    then: System.GreenwichMeanTime ← System.GetGreenwichMeanTime[];
    UNTIL stopUpdating DO
      IF then # System.GetGreenwichMeanTime[] THEN
        BEGIN then ← System.GetGreenwichMeanTime[]; UpdateNow[]; END;
      Process.Pause[Process.MsecToTicks[250]];
      ENDLOOP;
    stopUpdating ← FALSE;
    END;

  UpdateNow: PROCEDURE =
    BEGIN
    left, right: CARDINAL;
    new: STRING = [30];
    item: FormSW.ItemHandle;
    IF form = NIL THEN RETURN;
    item ← FormSW.FindItem[form, nowIX];
    Time.Append[new, Time.Unpack[System.GetGreenwichMeanTime[]]];
    right ← MIN[new.length, now.length];
    FOR left ← 0, left + 1 UNTIL left = right DO
      IF new[left] # now[left] THEN EXIT; ENDLOOP;
    FOR i: CARDINAL IN [0..new.length - left) DO new[i] ← new[i + left]; ENDLOOP;
    new.length ← new.length - left;
    item.flags.readOnly ← FALSE;
    FormSW.ModifyEditable[form, nowIX, left, now.length - left, new];
    item.flags.readOnly ← TRUE;
    END;

  nowIX: CARDINAL = 2;
  circleIX: CARDINAL = 4;
  targetIX: CARDINAL = 7;
  MakeForm: FormSW.ClientItemsProcType =
    BEGIN
    nParams: CARDINAL = 17;
    items ← FormSW.AllocateItemDescriptor[nParams];

    items[0] ← FormSW.CommandItem[
      tag: "ResetLocalTimeFromTarget"L, proc: ResetLocalTimeFromTarget,
      place: FormSW.newLine];
    items[1] ← FormSW.CommandItem[tag: "Enable"L, proc: Enable];
    items[nowIX] ← FormSW.StringItem[tag: "Now"L, string: @now, readOnly: TRUE];

    items[3] ← FormSW.CommandItem[
      tag: "ScanCircle"L, proc: ScanCircle, place: FormSW.newLine];
    items[circleIX] ← FormSW.CommandItem[
      tag: "FixupCircle"L, proc: FixupCircle, invisible: TRUE];
    items[5] ← FormSW.NumberItem[
      tag: "MaxHops"L, value: @maxHops, default: defaultMaxHops];

    items[6] ← FormSW.CommandItem[
      tag: "ScanTarget"L, proc: ScanTarget, place: FormSW.newLine];
    items[targetIX] ← FormSW.CommandItem[
      tag: "FixupTarget"L, proc: FixupTarget, invisible: TRUE];
    items[8] ← FormSW.StringItem[tag: "Target"L, string: @target, inHeap: TRUE];

    items[9] ← FormSW.CommandItem[
      tag: "AheadOneMin"L, proc: ForwardOneMin, place: FormSW.newLine];
    items[10] ← FormSW.CommandItem[tag: "AheadTenSec"L, proc: ForwardTenSec];
    items[11] ← FormSW.CommandItem[tag: "AheadOneSec"L, proc: ForwardOneSec];

    items[12] ← FormSW.CommandItem[
      tag: "BackOneMin"L, proc: BackOneMin, place: FormSW.newLine];
    items[13] ← FormSW.CommandItem[tag: "BackTenSec"L, proc: BackTenSec];
    items[14] ← FormSW.CommandItem[tag: "BackOneSec"L, proc: BackOneSec];

    items[15] ← FormSW.CommandItem[
      tag: "SetStartingError"L, proc: SetStartingError, place: FormSW.newLine];
    items[16] ← FormSW.LongNumberItem[
      tag: "StartingError"L, value: @startingError, default: defaultStartingError];

    RETURN[items, TRUE];
    END;

  ClientTransition: ToolWindow.TransitionProcType =
    BEGIN
    SELECT TRUE FROM
      old = inactive =>
        BEGIN
        now ← z.NEW[StringBody[20]];
        target ← z.NEW[StringBody[20]];
        String.AppendString[target, "ME"L];
        PupDefs.PupPackageMake[];
        Process.Detach[FORK UpdateIt[]];
        END;
      new = inactive =>
        BEGIN
        IF running THEN Off[];
        stopUpdating ← TRUE;
        WHILE stopUpdating DO Process.Pause[1]; ENDLOOP;
	PupDefs.PupPackageDestroy[];
        z.FREE[@target];
        z.FREE[@now];
        msg ← form ← log ← NIL;
        END;
      ENDCASE;
    END;

  Init: PROCEDURE =
    BEGIN
    herald: STRING = [100];
    String.AppendString[herald, "FixTimeServers of  "L];
    Time.Append[herald, Time.Unpack[Runtime.GetBcdTime[]]];
    [] ← Tool.Create[
      name: herald, makeSWsProc: MakeSWs, clientTransition: ClientTransition];
    IF ~Runtime.IsBound[@GateDefs.typescript] THEN CaptureTypeOut;
    END;

  CaptureTypeOut: PROCEDURE =
    BEGIN
    defaultWindow: Window.Handle = UserInput.GetDefaultWindow[];
    UserInput.DestroyIndirectStringOut[defaultWindow];
    UserInput.CreateIndirectStringOut[from: defaultWindow, to: log];
    END;
    
  Init[];
  END.