-- Copyright (C) 1983  by Xerox Corporation. All rights reserved. 
-- FindBootFiles.mesa, HGM, 24-Sep-83 13:37:27

DIRECTORY
  Display USING [Bitmap, Invert, replaceFlags, White],
  Format USING [],  -- Needed by Put.Number and Put.Date
  FormSW USING [
    ClientItemsProcType, ProcType, AllocateItemDescriptor, newLine, CommandItem,
    StringItem, NumberItem],
  Heap USING [Create, Delete, MakeNode],
  MsgSW USING [Post],
  Process USING [Detach, Yield],
  Put USING [Char, CR, Date, Decimal, Text, Line, Number, LongDecimal],
  Runtime USING [GetBcdTime],
  String USING [AppendString, AppendNumber, CopyToNewString, Equivalent],
  System USING [GreenwichMeanTime],
  Time USING [Append, AppendCurrent, Unpack],
  Tool USING [
    Create, MakeSWsProc, UnusedLogName, MakeMsgSW, MakeFormSW, MakeFileSW,
    AddThisSW],
  ToolWindow USING [CreateSubwindow, DisplayProcType, nullBox, TransitionProcType],
  Window USING [Handle, Box],

  Buffer USING [AccessHandle, DestroyPool, GetBuffer, MakePool, ReturnBuffer],
  PupWireFormat USING [BcplLongNumber, BcplToMesaLongNumber, BcplSTRING],
  PupDefs USING [
    PupPackageMake, PupPackageDestroy,
    PupBuffer, PupSocket, PupSocketDestroy, PupSocketMake,
    defaultNumberOfNetworks, GetHopsToNetwork, SecondsToTocks,
    SetPupContentsWords, GetPupContentsBytes, AppendPupAddress, AppendHostName,
    AppendErrorPup, GetPupAddress, PupNameTrouble],
  PupTypes USING [PupAddress, fillInSocketID, miscSrvSoc];

FindBootFiles: PROGRAM
  IMPORTS
    Display, FormSW, Heap, MsgSW, Process, Put, Runtime, String,
    Time, Tool, ToolWindow,
    Buffer, PupDefs, PupWireFormat =
  BEGIN OPEN PupDefs, PupTypes;

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

  defaultMaxHops: CARDINAL = 3;
  pleaseStop: BOOLEAN ← FALSE;
  running: BOOLEAN ← FALSE;
  indicator: {left, right, off} ← off;
  first: BootFile ← NIL;
  maxHops: CARDINAL ← defaultMaxHops;
  where: PupAddress ← [[0], [0], PupTypes.miscSrvSoc];
  target: LONG STRING ← NIL;

  BootFile: TYPE = LONG POINTER TO BootFileInfo;
  BootFileInfo: TYPE = RECORD [
    next: BootFile,
    number: WORD,
    where: PupTypes.PupAddress,
    date: System.GreenwichMeanTime,
    name: LONG STRING];

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

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

  Stop: FormSW.ProcType = BEGIN Off[]; END;

  Off: PROCEDURE =
    BEGIN
    IF ~running THEN RETURN;
    pleaseStop ← TRUE;
    WHILE running DO Process.Yield[]; ENDLOOP;
    pleaseStop ← FALSE;
    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;

  DoCircle: PROCEDURE =
    BEGIN
    first: BOOLEAN ← TRUE;
    SetupBoxes[];
    FOR net: CARDINAL IN [1..PupDefs.defaultNumberOfNetworks) 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, ')];
      SearchOne[];
      first ← FALSE;
      ENDLOOP;
    Put.CR[log];
    SetDownBoxes[];
    PrintBootFileList[];
    ForgetBootFileList[];
    running ← FALSE;
    END;

  DoOne: PROCEDURE =
    BEGIN
    SetupBoxes[];
    SearchOne[];
    SetDownBoxes[];
    PrintBootFileList[];
    ForgetBootFileList[];
    running ← FALSE;
    END;
  
  ForgetBootFileList: PROCEDURE =
    BEGIN
    ZapHeap[];
    first ← NIL;
    END;

  SearchOne: PROCEDURE =
    BEGIN
    pool: Buffer.AccessHandle ← Buffer.MakePool[send: 1, receive: 10];
    soc: PupSocket ← PupSocketMake[fillInSocketID, where, SecondsToTocks[2]];
    packetNumber: CARDINAL ← GetNextSequenceNumber[];
    FOR i: CARDINAL IN [0..10) UNTIL pleaseStop DO
      b: PupBuffer ← Buffer.GetBuffer[pup, pool, send];
      b.pup.pupID.a ← b.pup.pupID.b ← packetNumber;
      b.pup.pupType ← bootDirReq;
      SetPupContentsWords[b, 0];
      soc.put[b];
      UNTIL (b ← soc.get[]) = NIL DO
        SELECT TRUE FROM
          ((b.pup.pupType # bootDirReply) OR (b.pup.pupID.a # packetNumber)
            OR (b.pup.pupID.b # packetNumber)) =>
            BEGIN
            temp: STRING = [100];
            PupDefs.AppendErrorPup[temp, b];
            MsgSW.Post[msg, temp];
            END;
          ENDCASE => BEGIN FlipBoxes[]; LookAtBootDir[b]; END;
        Buffer.ReturnBuffer[b];
        ENDLOOP;
      IF b # NIL THEN Buffer.ReturnBuffer[b];
      ENDLOOP;
    PupSocketDestroy[soc];
    Buffer.DestroyPool[pool];
    END;

  LookAtBootDir: PUBLIC PROCEDURE [b: PupDefs.PupBuffer] =
    BEGIN OPEN PupDefs;
    word, size, end: CARDINAL;
    end ← GetPupContentsBytes[b]/2;
    word ← 0;
    UNTIL word >= end DO
      name: STRING = [256];
      timeStamp: System.GreenwichMeanTime;
      timeStampLocation: LONG POINTER TO PupWireFormat.BcplLongNumber;
      timeStampLocation ← LOOPHOLE[@b.pup.pupWords[word + 1]];
      timeStamp ← LOOPHOLE[PupWireFormat.BcplToMesaLongNumber[
        timeStampLocation↑]];
      CopyBcplString[name, LOOPHOLE[@b.pup.pupWords[word + 1 + 2]]];
      AddToList[b.pup.pupWords[word], b.pup.source, timeStamp, name];
      size ← ((1 + name.length) + 1)/2;
      word ← word + size + 1 + 2;
      ENDLOOP;
    END;

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

  AddToList: PROCEDURE [
    number: WORD, where: PupTypes.PupAddress, date: System.GreenwichMeanTime,
    name: LONG STRING] =
    BEGIN
    finger: BootFile ← NIL;
    new: BootFile;
    FOR bf: BootFile ← first, bf.next UNTIL bf = NIL DO
      IF number = bf.number AND where = bf.where AND date = bf.date
        AND String.Equivalent[name, bf.name] THEN RETURN;
      IF bf.number < number OR (bf.number = number AND bf.date < date)
        OR
          (bf.number = number AND bf.date = date
            AND LessPupAddress[bf.where, where]) THEN finger ← bf;
      ENDLOOP;
    new ← Node[SIZE[BootFileInfo]];
    new↑ ← [
      next: NIL, number: number, where: where, date: date, name: ];
    IF finger # NIL AND String.Equivalent[name, finger.name] THEN
      new.name ← finger.name
    ELSE new.name ← CopyString[name];
    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;

  PrintBootFileList: PROCEDURE =
    BEGIN
    bf: BootFile ← first;
    UNTIL bf = NIL DO
      temp: STRING = [40];
      PupDefs.AppendHostName[temp, bf.where];
      Put.Number[log, bf.number, [8, FALSE, TRUE, 6]];
      Put.Text[log, "  "L];
      Put.Date[log, bf.date, dateTime];
      Put.Text[log, "  "L];
      Put.Text[log, bf.name];
      THROUGH [bf.name.length..25) DO Put.Char[log, ' ]; ENDLOOP;
      Put.Text[log, temp];
      FOR bf2: BootFile ← bf.next, bf2.next UNTIL bf2 = NIL DO
        temp: STRING = [40];
        IF bf.number # bf2.number OR bf.date # bf2.date
          OR ~String.Equivalent[bf2.name, bf.name] THEN EXIT;
        PupDefs.AppendHostName[temp, bf2.where];
        Put.Text[log, ", "L];
        Put.Text[log, temp];
        bf ← bf2;
        ENDLOOP;
      Put.CR[log];
      bf ← bf.next;
      ENDLOOP;
    END;

  nextSequenceNumber: CARDINAL ← 123;
  GetNextSequenceNumber: PROCEDURE RETURNS [CARDINAL] =
    BEGIN RETURN[nextSequenceNumber ← nextSequenceNumber + 1]; 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, "FindBootFiles.log$"L];
    log ← Tool.MakeFileSW[window: window, name: logFileName];
    END;

  MakeForm: FormSW.ClientItemsProcType =
    BEGIN
    nParams: CARDINAL = 5;
    items ← FormSW.AllocateItemDescriptor[nParams];
    items[0] ← FormSW.CommandItem[
      tag: "Stop"L, proc: Stop, place: FormSW.newLine];
    items[1] ← FormSW.CommandItem[
      tag: "ScanCircle"L, proc: ScanCircle, place: FormSW.newLine];
    items[2] ← FormSW.NumberItem[
      tag: "MaxHops"L, value: @maxHops, default: defaultMaxHops];
    items[3] ← FormSW.CommandItem[
      tag: "ScanTarget"L, proc: ScanTarget, place: FormSW.newLine];
    items[4] ← FormSW.StringItem[tag: "Target"L, string: @target, inHeap: TRUE];
    RETURN[items, TRUE];
    END;

  ClientTransition: ToolWindow.TransitionProcType =
    BEGIN
    SELECT TRUE FROM
      old = inactive =>
        BEGIN
        PupDefs.PupPackageMake[];
        END;
      new = inactive =>
        BEGIN
        IF running THEN Off[];
        PupDefs.PupPackageDestroy[];
        END;
      ENDCASE;
    END;

  Init: PROCEDURE =
    BEGIN
    herald: STRING = [100];
    String.AppendString[herald, "FindBootFiles of  "L];
    Time.Append[herald, Time.Unpack[Runtime.GetBcdTime[]]];
    [] ← Tool.Create[
      name: herald, makeSWsProc: MakeSWs, clientTransition: ClientTransition];
    END;
  
  -- Our own allocation krockery since things fill up if we use Storage OR Heap

  pagesForHeap: CARDINAL = 400;
  z: UNCOUNTED ZONE ← NIL;
  
  InitializeHeap: PROCEDURE =
    BEGIN
    z ← Heap.Create[initial: 100, increment: 100];
    END;

  ZapHeap: PROCEDURE =
    BEGIN
    Heap.Delete[z];
    InitializeHeap[];
    END;

  Node: PROCEDURE [n: CARDINAL] RETURNS [p: LONG POINTER] =
    BEGIN
    RETURN[Heap.MakeNode[z, n]];
    END;

  CopyString: PROCEDURE [s: LONG STRING] RETURNS [LONG STRING] =
    BEGIN
    RETURN[String.CopyToNewString[s, z]];
    END;

  
  InitializeHeap[];
  Init[];
  END.