{$Nostandard  [A+,B-,L-,R+,T=2]  }

PROGRAM BacBru;

{[F-]

BACBRU -- Run BRU from a Virtual Terminal

Written by:  Gary L. Maxwell
             National Strong Motion Data Center
             U. S. Geological Survey
             345 Middlefield Road, Mailstop 77
             Menlo Park, California  94025

Date:  04-Feb-83

Modifications:

	17-May-83	Disable BRU timeout for DECUS release.

This program runs the RSX Backup and Restore Utility (BRU)
on behalf of the issuing terminal.

Operating system: RSX-11M-PLUS Version 2.0 or later.
Compiler version: Oregon Software Pascal V2.0K

Functional Description
----------------------

Bacbru creates a virtual terminal and spawns BRU with a user-specified
set of command lines. Bacbru monitors the output from BRU and outputs
the collective set of BRU messages to a log file.

If the /DISPLAY option of BRU is used, then Bacbru collects the UIC
and file specification for each file BRU selects and formats this
output into the log file in a very readable format. The BRU diagnostic
messages are deferred from being printed in the log file until the
BRU session is completed.

Note that if a tape drive is to be used in the BRU session, it should
be mounted /FOREIGN/PUBLIC.

The intent of this program is to facilitate incremental backup operations
with a log of files that are transferred for any session.

Usage Notes
-----------

Command line:

        >BBR <command-file>

Where <command-file> is a file containing the information needed by
Bacbru. The format of a command file is as follows:

        <logfile-specification>
        <Bru-command-line-1>
        <Bru-command-line-2>
        ...

Where <logfile-specification> is the name of the log file where Bacbru
output will be directed. If the file already exists, then the existing
file is appended with the output from the current session.

<Bru-command-lines> are the one or more BRU commands to be given to BRU.
Note that these commands must appear as though they were input to BRU
directly. For example,

        /rewind/append/mount/back:08jul82/display-
        /verify/revised:after:(07-jul-82)
        Dr2:
        Mm1:

Note the prescence of the hyphen at the end of the first line. This is
required since the BRU qualifiers continue on the second line. The input
and output device follow the qualifiers.

If no command line is entered to BACBRU, the user's terminal is prompted
for all required information.

}

{[F+] Re-enable formatting }

  CONST

    { Program version number and date of most recent update }

    Version = 'V1.01'; { Version of this program }
    Version_Date = '17-May-83'; { Date of version }

    { RSX dependent constants: I/O function and return values,  }
    {                          exit status constants.           }

    IE_EOF = 366B; { End of file }
    IS_CR = 6401B; { Successful read with <CR> terminator }
    IS_SUC = 1; { Successful I/O completion }

    IO_RLB = 1000B; { Read logical block }
    IO_WLB = 400B; { Write logical block }
    IO_STC = 2500B; { Virtual terminal set characteristics }

    Error_Status = 2; { Exit status for error return }

    { Event constants, types, and structured constants }

    EF_Requested_Exit = 10; { Requested exit event flag }
    EF_BRU_Timeout = 11; { BRU Timeout event flag }
    EF_Input_Ast = 12; { VT: input AST event flag }
    EF_Output_Ast = 13; { VT: output AST event flag }
    EF_Attach_Ast = 14; { VT: attach/detach AST event flag }
    EF_BRU_Completed = 15; { Spawn BRU event flag (set by AST) }

    Mask_Requested_Exit = 1000B; { Bit mask for EF_Requested_Exit }
    Mask_BRU_Timeout = 2000B; { Bit mask for EF_BRU_Timeout }
    Mask_Input_Ast = 4000B; { Bit mask for EF_Input_Ast }
    Mask_Output_Ast = 10000B; { Bit mask for EF_Output_Ast }
    Mask_Attach_Ast = 20000B; { Bit mask for EF_Attach_Ast }
    Mask_BRU_Completed = 40000B; { Bit mask for EF_BRU_Completed }

    EF_Group = 0; { Group number of these event flags (1 through 16) }
    EF_Mask = 077000B; { Bit mask corresponding to above event flags }

    { NOTE WELL!                                                    }
    {                                                               }
    { The following enumerated type definition for Event_Types      }
    { must be ordered properly. Since the various events are tested }
    { for in order of the enumerated type, critical event types     }
    { must precede less important events. Therefore, Requested_exit }
    { events precede BRU_completion events, and so on.              }

  TYPE
    Event_Types = (Ev_Abort, Ev_Timeout, Ev_Attach, Ev_Output, Ev_Input,
                   Ev_Done); { Main loop event types }
    Event_Types_Range = Ev_Abort..Ev_Done;

    { The following type and structured constant definitions bind   }
    { the event flags used by the ast routines and other events     }
    { with the enumerated set of event types known by the main      }
    { program.                                                      }
    { Note that the ordering scheme of the structured constant      }
    { components must match the ordering of the enumerated type.    }

    Bind_Event_Type = ARRAY [Event_Types_Range] OF integer;

  CONST
    Mask_Binding = Bind_Event_Type(Mask_Requested_Exit, Mask_BRU_Timeout,
                                   Mask_Attach_Ast, Mask_Output_Ast,
                                   Mask_Input_Ast, Mask_BRU_Completed);

    EF_Binding = Bind_Event_Type(EF_Requested_Exit, EF_BRU_Timeout,
                                 EF_Attach_Ast, EF_Output_Ast, EF_Input_Ast,
                                 EF_BRU_Completed);

  CONST

    { Virtual terminal constants }

    VT_Buffer_Length = 132; { VT: buffer length }
    VT_Logical_Unit_No = 10; { VT: logical unit number for I/O }
    VT_Event_Flag = 17; { Event flag for VT: I/O }

    { Various other program constants }

    Max_BRU_Errors = 100; { Maximum BRU errors before abort }
    Max_Output_Column = 80; { Width of output for log file }
    Max_Command_Name = 3; { Our command name is 3 chars or less }
    Filespecs_per_line = 4; { No. BRU filespecs on line of output }
    Filespec_Length = 20; { Character width of BRU filespec output }

    { String constants and types }

    SPACE = ' '; { Space char }
    CR = 15B; { Carriage return }
    LF = 12B; { Line feed }
    Max_String_Length = 132; { Length of standard string }

  TYPE
    Character_Set = SET OF char;
    String_Index_Range = 1..Max_String_Length;
    String_Length_Range = 0..Max_String_Length;
    String_Type = PACKED ARRAY [String_Index_Range] OF char;
    String_Record =
      RECORD
        length: String_Length_Range;
        string: String_Type
      END;

    Date_Time_String = PACKED ARRAY [1..19] OF char;

    Error_Record_Ptr = ^Error_Record; { Pointer to an error record }
    Error_Record =
      RECORD
        next_error: Error_Record_Ptr;
        error_string: String_Record
      END;

    Command_Name_Type = PACKED ARRAY [1..Max_Command_Name] OF char;

    { The following declarations are for various BRU messages which }
    { are "fatal" in that there is no way this program can recover  }
    { from the situation. Currently, this is limited to BRU mount   }
    { requests. Since we can't mount a new tape ourselves, we have  }
    { to abort the run.                                             }

  CONST
    Mount_index = 7; { Character position of BRU mount request }

  TYPE
    Mount_type = PACKED ARRAY [1..5] OF char;

  CONST
    Mount_message = Mount_type('M', 'O', 'U', 'N', 'T');



   { The following type definitions are used by the PASCAL-2 RSX-11M
     Executive directive procedure definitions.                     }

  TYPE

    { Define array types                                          }

    rsx$devname = PACKED ARRAY [1..2] OF char;
    rsx$iosb = ARRAY [1..2] OF integer;
    rsx$iosbptr = ^rsx$iosb;
    rsx$esb = ARRAY [1..8] OF integer;
    rsx$esbptr = ^rsx$esb;
    rsx$rdafbuf = ARRAY [1..4] OF integer;
    rsx$rdxfbuf = ARRAY [1..6] OF integer;
    radix50 = ARRAY [1..2] OF integer;
    ascstr = PACKED ARRAY [1..3] OF char;
    charptr = ^char;

    { Define structured types                                     }

    rsx$rdb =
      RECORD
        gid: integer;
        gsiz: integer;
        gnam: radix50;
        gpar: radix50;
        gsts: integer;
        gpro: integer
      END;

    rsx$wdb =
      RECORD
        nid: char;
        napr: char;
        nbas: integer;
        nsiz: integer;
        nrid: integer;
        noff: integer;
        nlen: integer;
        nsts: integer;
        nsrb: ^char
      END;

    rsx$wvector =
      RECORD
        wve: ARRAY [1..8] OF rsx$wdb;
        terminator: integer
      END;

    rsx$glunbuf =
      RECORD
        luna: rsx$devname;
        lunu: char;
        lufb: char;
        lucw: ARRAY [1..4] OF integer
      END;

    rsx$gtskbuf =
      RECORD
        tstn: radix50;
        tspn: radix50;
        reserved1: integer;
        reserved2: integer;
        tspr: integer;
        tspc: char;
        tsgc: char;
        tsnl: integer;
        reserved3: integer;
        reserved4: integer;
        tsva: ^integer;
        tsvl: integer;
        tsts: integer;
        tssy: integer;
        tsdu: PACKED ARRAY [1..2] OF char
      END;

    rsx$gprtbuf =
      RECORD
        prpb: integer;
        prps: integer;
        prfw: integer;
        extra: integer
      END;

    rsx$gregbuf =
      RECORD
        rgrb: integer;
        rgrs: integer;
        rgfw: integer;
        extra: integer
      END;

    rsx$gtimbuf =
      RECORD
        tiyr: integer;
        timo: integer;
        tida: integer;
        tihr: integer;
        timi: integer;
        tisc: integer;
        tict: integer;
        ticp: integer
      END;

    rsx$ocb =
      RECORD
        olnk: integer;
        omcrl: integer;
        optcb: integer;
        oast: integer;
        oefn: integer;
        oesb: integer;
        ostat: ARRAY [1..8] OF integer
      END;
    rsx$ocbptr = ^rsx$ocb;


  FUNCTION Rad50(asciistr: ascstr): integer;
    EXTERNAL;


  PROCEDURE Ascii(r50str: integer;
                  VAR asciistr: ascstr);
    EXTERNAL;


  PROCEDURE Abrt$(tsk: radix50;
                  VAR dsw: integer);
    EXTERNAL;


  PROCEDURE Alun$(lun: integer;
                  dev: rsx$devname;
                  unt: integer;
                  VAR dsw: integer);
    EXTERNAL;


  PROCEDURE Clef$(efn: integer;
                  VAR dsw: integer);
    EXTERNAL;


  PROCEDURE Cmkt$(efn: integer;
                  VAR dsw: integer);
    EXTERNAL;


  PROCEDURE Crea$(VAR dsw: integer);
    EXTERNAL;


  PROCEDURE Crvt$(PROCEDURE Iast(VFC, count, flags: integer);
                  PROCEDURE oast(VFC, count, flags: integer);
                  PROCEDURE Aast(tname: radix50;
                                 flags: integer);
                  Mlen: integer;
                  VAR dsw: integer);
    EXTERNAL;


  PROCEDURE Elvt$(Unit: integer;
                  VAR dsw: integer);
    EXTERNAL;


  PROCEDURE Gtim$(VAR buf: rsx$gtimbuf;
                  VAR dsw: integer);
    EXTERNAL;


  PROCEDURE Mrkt$(efn: integer;
                  tmg: integer;
                  tnt: integer;
                  VAR dsw: integer);
    EXTERNAL;


  PROCEDURE Qiow$(fnc, lun, efn: integer;
                  isb: rsx$iosbptr;
                  pr1, pr2, pr3, pr4, pr5, pr6: integer;
                  VAR dsw: integer);
    EXTERNAL;


  PROCEDURE Rdaf$(VAR buf: rsx$rdafbuf;
                  VAR dsw: integer);
    EXTERNAL;


  PROCEDURE Setf$(efn: integer;
                  VAR dsw: integer);
    EXTERNAL;


  PROCEDURE Spwn$(tname: radix50;
                  ugc, umc, efn: integer;
                  esb: rsx$esbptr;
                  cmdlin: charptr;
                  cmdlen: integer;
                  unum: integer;
                  dnam: rsx$devname;
                  VAR dsw: integer);
    EXTERNAL;


  PROCEDURE Srea$(PROCEDURE Ast;
                  VAR dsw: integer);
    EXTERNAL;


  PROCEDURE Stlo$(grp: integer;
                  msk: integer;
                  VAR dsw: integer);
    EXTERNAL;


  PROCEDURE Stse$(efn: integer;
                  VAR dsw: integer);
    EXTERNAL;


  PROCEDURE GetMCR(VAR Line: String_Record);
    EXTERNAL;


  PROCEDURE Detach;
    EXTERNAL;


  PROCEDURE Exitst(Status: integer);
    EXTERNAL;


  { Global variables }

  VAR
    Vtunit: integer; { Unit number for virtual terminal }

    VT_Output_Size: integer; { Size of latest output transfer from VT: }

    VT_Attached_Task: radix50; { RAD50 name of task attached to VT: }

    BRU_Exact_Name: radix50; { Name of spawned BRU task name }
    BRU_esb: rsx$esbptr; { Emit status block for BRU spawn }

    Event: Event_Types; { Event type of latest AST event }

    Logfile: Text; { Log file variable }

    Command_Name: Command_Name_Type; { First three chars of our command }

    Current_Output: Error_Record_Ptr; { Current BRU output record }
    Error_List_Head: Error_Record_Ptr; { Pointer to list of error records }
    Error_List_Tail: Error_Record_Ptr; { Pointer to last error record }
    Error_Count: integer; { Count of error records }
    BRU_Input_List: Error_Record_Ptr; { List of input lines to BRU }

    Filespec_Buffer: String_Record; { BRU filespecs are buffered here }
    Filespec_Count: integer; { Number filespecs currently in buffer }
    Excluded_chars: Character_Set; { Characters to strip from BRU output }

    { The following boolean variables determine the active  }
    { status of the program.                                }

    Logfile_Open: boolean; { True if log file open }
    VT_Created: boolean; { True if VT: still created }
    BRU_Active: boolean; { True if BRU is still running }




  PROCEDURE Get_Time(VAR string: Date_Time_String);

   { Retrieve the current date and time and format into    }
   { the result string in the format: "dd-mmm-yy  hh:mm:ss }

    TYPE
      Month_Type = PACKED ARRAY [1..3] OF char;
      All_Months = ARRAY [1..12] OF Month_Type;

    CONST
      Month_Binding = All_Months('JAN', 'FEB', 'MAR', 'APR', 'MAY', 'JUN',
                                 'JUL', 'AUG', 'SEP', 'OCT', 'NOV', 'DEC');

    VAR
      Time_Buffer: rsx$gtimbuf;
      dsw: integer;


    PROCEDURE Stuff_Digits(position, value: integer);

     { Store a two digit decimal value in Time_Buffer        }
     { starting at position "position" with "value."         }


      BEGIN { Stuff_Digits }
        IF value < 10 THEN
          BEGIN
          string[position] := '0';
          string[position + 1] := chr(value + 60B)
          END
        ELSE
          BEGIN
          string[position] := chr((value DIV 10) + 60B);
          string[position + 1] := chr((value MOD 10) + 60B)
          END
      END; { Stuff_Digits }


    BEGIN { Get_Time }

      Gtim$(Time_Buffer, dsw); { Get system time }
      WITH Time_Buffer DO
        BEGIN { With }
        Stuff_Digits(1, tida);
        string[3] := '-';
        FOR dsw := 1 TO 3 DO
          string[dsw + 3] := Month_Binding[timo][dsw];
        string[7] := '-';
        Stuff_Digits(8, tiyr);
        string[10] := SPACE;
        string[11] := SPACE;
        Stuff_Digits(12, tihr);
        string[14] := ':';
        Stuff_Digits(15, timi);
        string[17] := ':';
        Stuff_Digits(18, tisc)
        END { With }

    END; { Get_Time }


  PROCEDURE Eliminate_VT;

   { This procedure eliminates the virtual terminal unit   }

    VAR
      dsw: integer;


    BEGIN { Eliminate_VT }

      Elvt$(Vtunit, dsw); { Call system directive }
      IF dsw < 0 THEN
        writeln('Bacbru - *WARNING* ELVT$ failure; DSW = ', dsw)
      ELSE
        VT_Created := false

    END; { Eliminate_VT }


  PROCEDURE Abort_BRU;

   { This procedure does just that...it aborts BRU, either as a    }
   { result of a timeout or a requested exit of this program.      }
   { Note that BRU is merely marked for abort; the program cannot  }
   { assume that BRU has actually aborted until the event flag     }
   { used to spawn BRU has been raised.                            }

    VAR
      dsw: integer; { Directive status }


    BEGIN { Abort_BRU }

      Abrt$(BRU_Exact_Name, dsw); { Call system directive }
      IF dsw < 0 THEN
        writeln('Bacbru - *WARNING* ABRT$ failure; DSW = ', dsw)

    END; { Abort_BRU }


  PROCEDURE Return_Status(Status: integer);

   { This procedure is the method by which the program is  }
   { terminated. The following checks are performed:       }
   {       - If BRU is active it is aborted                }
   {       - The virtual terminal is eliminated            }
   {       - The log file is closed                        }
   { The procedure then calls EXITST with the status       }
   { argument to terminate the program.                    }

    VAR
      dsw: integer;


    BEGIN { Return_Status }
      IF BRU_Active THEN
        BEGIN { Abort BRU }
        Abort_BRU; { Mark for abort }
        Stse$(EF_BRU_Completed, dsw); { Wait for it }
        BRU_Active := false
        END; { Abort BRU }
      IF VT_Created THEN
        Eliminate_VT;
      IF Logfile_Open THEN
        BEGIN
        close(Logfile);
        Logfile_Open := false
        END;
      Exitst(Status)
    END { Return_Status } ;


  PROCEDURE QIO_Error(dsw: integer;
                      iosb: rsx$iosbptr);

   { This routine prints the status of the fatal I/O error }
   { and calls Return_Status to kill the program           }


    BEGIN
      writeln('Bacbru - *FATAL* QIO error, DSW = ', dsw, '; I/O status = ',
              iosb^[1]);
      Return_Status(Error_Status)
    END;


  FUNCTION Upper(character: char): char;

   { Returns the upper case equivalent of the input argument }


    BEGIN
      IF (character >= 'a') AND (character <= 'z') THEN
        Upper := chr(ord(character) AND 337B)
      ELSE
        Upper := character
    END;

  {$Nodebug }


  FUNCTION len(VAR str: String_Type): String_Length_Range;

   { Returns the length of the string (counts back from the end) }

    VAR
      done: boolean;
      i: String_Length_Range;


    BEGIN { len }
      i := Max_String_Length;
      done := false;
      WHILE (i > 1) AND (NOT done) DO
        BEGIN
        i := i - 1;
        done := str[i] <> SPACE
        END;
      len := i;
      IF NOT done THEN
        len := 0
    END { len } ;

  { The following procedures are AST routines which are executed when     }
  { the appropriate AST is effected by an asynchronous event.             }


  PROCEDURE VT_Input_Request(VFC, count, flags: integer);

   { This AST routine is invoked if the offspring task requests    }
   { input from the virtual terminal. We set the appropriate event }
   { flag and let the main loop catch it.                          }

    VAR
      dsw: integer;


    BEGIN { VT_Input_Request }

      Setf$(EF_Input_Ast, dsw)

    END {VT_Input_Request } ;


  PROCEDURE VT_Output_Request(VFC, count, flags: integer);

   { This AST routine is invoked if the offspring task performs    }
   { an output operation to the virtual terminal. This routine     }
   { saves the size of the output request for the main routine     }
   { to actually perform the read of the offspring's output        }

    VAR
      dsw: integer;


    BEGIN { VT_Output_Request }

      VT_Output_Size := count;
      Setf$(EF_Output_Ast, dsw)

    END { VT_Output_Request } ;


  PROCEDURE VT_Attach_Detach(task: radix50;
                             flags: integer);

   { This AST routine is entered whenever an attach or detach      }
   { request is issued to a virtual terminal from an offspring     }
   { task. The task name is saved so that the type of request      }
   { is preserved (detach has a null task name).                   }

    VAR
      dsw: integer;


    BEGIN { VT_Attach_Detach }

      VT_Attached_Task[1] := task[1];
      VT_Attached_Task[2] := task[2];
      Setf$(EF_Attach_Ast, dsw)

    END { VT_Attach_Detach } ;


  PROCEDURE Abort_AST;

   { The following AST routine is entered if someone has tried     }
   { to abort this program. We set the appropriate event flag      }
   { so that the program can clean up and get out.                 }

    VAR
      dsw: integer;


    BEGIN
      Setf$(EF_Requested_Exit, dsw)
    END;

  {$Debug }

  { The following procedures are miscellaneous routines used by   }
  { various other components of this program.                     }

  { The following procedures handle the various events detected   }
  { by the main loop.                                             }


  PROCEDURE Process_Input;

   { This routine handles an offspring's input request to the      }
   { virtual terminal. This should only happen when BRU is prompt- }
   { ing for additional input. We extract the next BRU command     }
   { line from the linked list and pass it to BRU. We also copy    }
   { this command to the last output string (the prompt string)    }
   { we received. This makes the full BRU dialog complete.         }
   {                                                               }
   { If there are no commands left to give to BRU, we return an    }
   { EOF to BRU which will probably shut it down.                  }

    VAR
      string_pointer: ^String_Type;
      address: integer;
      dsw: integer; { Directive status }
      iosb: rsx$iosbptr;
      index1: String_Index_Range;
      temp_pointer: Error_Record_Ptr;


    BEGIN { Process_Input }

      new(iosb); { Allocate an I/O status block }

      IF BRU_Input_List <> NIL THEN
        BEGIN { Pass command }
        WITH BRU_Input_List^.error_string DO
          BEGIN { With }
          string_pointer := ref(string); { Pointer to string buffer }
          address := loophole(integer, string_pointer);
          Qiow$(IO_WLB, VT_Logical_Unit_No, VT_Event_Flag, iosb, address,
                length, IS_CR, 0, 0, 0, dsw);
          IF dsw < 0 THEN
            QIO_Error(dsw, iosb)
          END; { With }
        IF Error_List_Tail <> NIL THEN
          BEGIN { Copy to output buffer }
          WITH Error_List_Tail^.error_string DO
            BEGIN { With }
            FOR index1 := 1 TO BRU_Input_List^.error_string.length DO
              string[length + index1] := BRU_Input_List^.error_string.string[
                                         index1]; { Copy input }
            length := length + BRU_Input_List^.error_string.length
            END; { With }
          END; { Copy to output buffer }

        temp_pointer := BRU_Input_List;
        BRU_Input_List := temp_pointer^.next_error; { Point to next input }
        dispose(temp_pointer) { Discard used buffer }
        END { Pass command }

      ELSE
        BEGIN { Pass EOF }
        Qiow$(IO_STC, VT_Logical_Unit_No, VT_Event_Flag, iosb, 1, 0, IE_EOF,
              0, 0, 0, dsw); { Return EOF }
        IF dsw < 0 THEN
          QIO_Error(dsw, iosb)
        END; { Pass EOF }

      dispose(iosb); { Get rid of I/O status }

    END; { Process_Input }


  PROCEDURE Write_Errors;

   { This routine dumps the linked list of BRU and other error     }
   { messages to the log file. The error list is freed and all     }
   { error packets are deallocated.                                }

    VAR
      temp_pointer: Error_Record_Ptr;


    BEGIN { Write_Errors }

      IF Error_List_Head <> NIL THEN
        BEGIN { Write errors }
        writeln(Logfile);

        WHILE Error_List_Head <> NIL DO
          BEGIN { Loop }
          WITH Error_List_Head^.error_string DO
            writeln(Logfile, string: length);
          temp_pointer := Error_List_Head;
          Error_List_Head := temp_pointer^.next_error;
          dispose(temp_pointer)
          END { Loop }

        END; { Write errors }
      Error_List_Tail := NIL;
      Error_Count := 0
    END { Write_Errors } ;


  PROCEDURE Link_Error;

   { This procedure links the current buffer containing offspring  }
   { output to the end of the list of string records. The pointer  }
   { "Current_Output" is used, but not altered or NIL'ed by this   }
   { procedure.                                                    }


    BEGIN { Link_Error }

      IF Error_List_Head = NIL THEN
        Error_List_Head := Current_Output { First error }
      ELSE
        Error_List_Tail^.next_error := Current_Output; { Link to end }

      Error_List_Tail := Current_Output; { Make current the last }
      Error_List_Tail^.next_error := NIL; { Make sure list is terminated }
      Error_Count := Error_Count + 1

    END { Link_Error } ;


  PROCEDURE Write_Filespecs;

   { This routine dumps the file specification buffer to the log   }
   { file and resets the buffer for insertion of new filenames.    }


    BEGIN { Write_Filespecs }

      WITH Filespec_Buffer DO
        BEGIN
        IF length > 0 THEN
          writeln(Logfile, string: length);
        length := 0;
        Filespec_Count := 0
        END

    END; { Write_Filespecs }


  PROCEDURE Put_Filespec(VAR filespec: String_Record);

   { This procedure copies the file specification in the input     }
   { argument to the current file specification output buffer.     }

    VAR
      index, newindex: String_Index_Range;
      oldlength, newlength: String_Length_Range;


    BEGIN { Put_Filespec }

      WITH Filespec_Buffer DO { Find spot in buffer }

        IF Filespec_Count > 0 THEN
          BEGIN { Existing buffer }
          newindex := Filespec_Length * Filespec_Count; { Place for new
                                                         filespec }
          FOR index := (length + 1) TO newindex DO
            string[index] := SPACE; { Pad to next filespec position }
          length := newindex
          END; { Existing buffer }

      oldlength := Filespec_Buffer.length; { Existing length }
      newlength := filespec.length; { Length of new string }
      FOR index := 1 TO newlength DO
        Filespec_Buffer.string[index + oldlength] := filespec.string[index];{
        Copy filespec }
      Filespec_Buffer.length := oldlength + newlength; { New length }
      Filespec_Count := Filespec_Count + 1;
      IF Filespec_Count >= Filespecs_per_line THEN
        Write_Filespecs { Flush buffer }

    END { Put_Filespec } ;


  PROCEDURE Cleanup;

   { Procedure to dump buffers that may be hanging around when     }
   { BRU terminates.                                               }

    VAR
      Time: Date_Time_String;


    BEGIN { Cleanup }

      Write_Filespecs; { Write any file specifications }
      Write_Errors; { Output any BRU errors }
      Get_Time(Time);
      writeln(Logfile, '---------  Bacbru session ended at ', Time,
              '  ---------');
      writeln(Logfile);
      writeln('Bacbru - Completed.');

    END; { Cleanup }


  PROCEDURE Process_Output;

   { This procedure processes any output requests to the virtual   }
   { terminal from the offspring task. With BRU, we expect to get  }
   { several types of terminal output:                             }
   {                                                               }
   {       - Error, warning, and informational messages            }
   {       - A UIC specification for a directory being backed up   }
   {       - A file specification for a file being backed up       }
   {                                                               }
   { There is really nothing complicated about the latter two;     }
   { when we get a UIC, we flush the output buffers for the last   }
   { directory, output the new directory, and set up for any files }
   { that will be copied. We collect the file names as they arrive,}
   { and we output them, four to a line, on the log file.          }
   {                                                               }
   { The BRU error messages are a little tougher. We want to flag  }
   { at least two messages: the "MOUNT TAPE" messages which mean   }
   { that the tape drive is screwed up. In that case we have to    }
   { kill everything and exit with an error. All other messages are}
   { collected and output at the very end of the BRU run.          }

    VAR
      string_pointer: ^String_Type;
      address: integer;
      iosb: rsx$iosbptr;
      dsw: integer;
      index, index1, dot_index, semi_index: String_Length_Range;
      found: boolean;
      error_queued: boolean;


    BEGIN { Process_Output }

      error_queued := false; { True if an error is linked into list }
      new(iosb); { Allocate RSX I/O status block }

      IF Current_Output = NIL THEN
        new(Current_Output); { Get new string variable }
      WITH Current_Output^.error_string DO
        BEGIN { With string }
        string_pointer := ref(string); { Pointer to string buffer }
        address := loophole(integer, string_pointer);

        { Issue RSX QIO directive to read BRU output    }

        Qiow$(IO_RLB, VT_Logical_Unit_No, VT_Event_Flag, iosb, address,
              VT_Output_Size, IS_SUC, 0, 0, 0, dsw);

        FOR address := iosb^[2] + 1 TO Max_String_Length DO
          string[address] := SPACE; { Clear out rest of string }
        length := len(string); { Current length of string }

        { First, remove all excluded characters from string     }

        IF length > 0 THEN
          BEGIN { Pre-parse }
          index1 := 1;
          FOR index := 1 TO length DO
            IF NOT (string[index] IN Excluded_chars) THEN
              BEGIN { Not excluded }
              string[index1] := string[index];
              index1 := index1 + 1
              END; { Not excluded }
          length := index1 - 1 { New string length }
          END; { Pre-parse }

        IF length > 0 THEN
          BEGIN { Process string }
          found := false;

          { First, look for a UIC specification }

          IF string[1] = '[' THEN
            BEGIN { UIC }
            Write_Filespecs; { Output current filespec buffer }
            writeln(Logfile); { Blank line }
            writeln(Logfile, string: length); { Write directory specification
                                               }
            writeln(Logfile);
            found := true
            END { UIC } ;

          { Next, look for a file specification...we key on the   }
          { fact that BRU outputs a full filespec, including '.'  }
          { and ';' characters. Since the maximum length of a     }
          { file spec is 20 characters (given a 6 character       }
          { version number), make this a restrictive search.      }

          IF (NOT found) AND (length <= 20) THEN
            BEGIN { Try filespec }
            dot_index := 0;
            semi_index := 0;
            FOR index := 1 TO length DO
              IF string[index] = '.' THEN
                dot_index := index
              ELSE IF string[index] = ';' THEN
                semi_index := index;
            IF (dot_index <> 0) AND (semi_index <> 0) THEN
              BEGIN { Found file }
              found := true;
              Put_Filespec(Current_Output^.error_string) { Put in buffer }
              END { Found file }
            END { Try filespec } ;

          { Now we're in the last category...error messages and   }
          { other goodies. For now, we simply want to catch the   }
          { "mount tape" messages to terminate the program. For   }
          { all others, link the message at the end of the message}
          { list.                                                 }

          IF NOT found THEN
            BEGIN { Other messages }
            found := true; { Assume a mount command }
            FOR index := 1 TO 5 DO
              IF Upper(string[index + Mount_index - 1]) <>
                 Mount_message[index] THEN
                found := false;

            IF found THEN
              BEGIN { BRU is hung-up }
              Link_Error; { Link mount message into list }
              writeln('Bacbru - *FATAL*',
                      ' BRU hung waiting for device mount');
              Cleanup; { Flush the output }
              Return_Status(Error_Status)
              END { BRU is hung-up }

            ELSE
              BEGIN { Save error }
              Link_Error;
              error_queued := true
              END { Save error }

            END { Other messages }
          END { Process string }
        END; { with string }

      IF error_queued THEN
        Current_Output := NIL;
      dispose(iosb)

    END { Process_Output } ;


  PROCEDURE Process_Attach;

   { Process a attach or detach request from an offspring  }
   { task through the virtual terminal. Currently, this    }
   { is treated as a no-op.                                }


    BEGIN
    END;


  PROCEDURE Process_Timeout;

   { This routine is called when BRU has timed-out. The current    }
   { action is inelegant, but what can one do? When BRU has taken  }
   { two hours to do a daily incremental backup, then something is }
   { very wrong (perhaps some other sanity checks in this program  }
   { have failed.) So we abort BRU, which will cause everything    }
   { else to fall through to completion.                           }

   { NOTE: This has been disabled for the DECUS release, since     }
   {       some folks may have slow tape drives!                   }

    BEGIN
   {
      writeln(Logfile, 'Bacbru -- *Fatal* ',
              'Timeout expired on BRU - aborting run');
      Abort_BRU
    }
    END;



  { The following procedure initializes the program. Really, most }
  { of the guts of the program are right here, with most of the   }
  { work involved in setting up all the AST's and offsprings.     }


  PROCEDURE Initialize;


    PROCEDURE Get_Parameters;

     { This routine parses the command line for the command filename }
     { and opens and reads the command file, setting up operations.  }

      VAR
        terminal_input: boolean;
        done: boolean;
        index: String_Length_Range;
        Command_File: Text;
        MCR_Buffer: String_Record;
        Status: integer;
        Bru_Input_Tail: Error_Record_Ptr;
        BRU_Command: Error_Record_Ptr;


      BEGIN { Get_Parameters }
        terminal_input := true; { Assume terminal input }

        WITH MCR_Buffer DO
          BEGIN { With MCR_Buffer }

          GetMCR(MCR_Buffer);
          IF length > 0 THEN
            BEGIN { Pre-parse }
            IF string[length] = '-' THEN
              BEGIN { Continuation line }
              writeln('Bacbru -- *FATAL* Continuation lines not allowed.');
              Return_Status(Error_Status)
              END { Continuation line } ;
            index := 1;

            WHILE string[index] <> SPACE DO
              BEGIN { Get command name }
              IF index <= Max_Command_Name THEN
                Command_Name[index] := string[index]; { Copy command name }
              string[index] := SPACE;
              index := index + 1
              END; { Get command name }

            FOR index := length + 1 TO Max_String_Length DO
              string[index] := SPACE;

            length := len(string) { In case only command name there }
            END; { Pre-parse }

          IF length > 0 THEN
            BEGIN { Open command file }
            reset(Command_File, string, '.cmd', Status);
            IF Status = - 1 THEN
              BEGIN
              writeln('Bacbru - *FATAL* Open failure on command file: ',
                      string: length);
              Return_Status(Error_Status)
              END
            ELSE
              terminal_input := false
            END; { Open command file }

          { At this point, we have an open command file }
          { The lines in the command file are organized }
          { as follows:                                 }
          {      - File specification of log file       }
          {      - BRU command line(s)                  }
          { If any of the arguments are invalid or      }
          { missing, unpredictable or dire results may  }
          { occur.                                      }

          IF terminal_input THEN
            BEGIN { Get log file }
            writeln;
            writeln('Bacbru -- Background BRU Program ', Version);
            writeln;
            write('Enter name of Bacbru output log file: ');
            readln(string);
            IF eof THEN
              Return_Status(Error_Status)
            END { Get log file }
          ELSE
            readln(Command_File, string);

          reset(Logfile, string, '.log/rw/apd', Status); { Open for append }
          IF Status = - 1 THEN
            BEGIN { No log file }
            writeln('Bacbru - *WARNING* Creating new log file: ', string:
                    len(string));
            rewrite(Logfile, string, '.log', Status);
            IF Status = - 1 THEN
              BEGIN { Open failure }
              writeln('Bacbru - *FATAL* Could not open log file');
              Return_Status(Error_Status)
              END { Open failure }
            END; { No log file }
          END; { With MCR_Buffer }

        { Now we read all BRU input lines and buffer them       }
        { into a list of strings headed by BRU_Input_List.      }
        { These strings are then handed to BRU when BRU requests}
        { input from its terminal.                              }

        BRU_Input_List := NIL;
        Bru_Input_Tail := NIL;

        IF terminal_input THEN
          BEGIN
          writeln;
          writeln('Enter a complete set of BRU commands, ',
                  'including necessary qualifiers');
          writeln('and input and output device specifications.');
          writeln;
          writeln('Enter a null line to terminate BRU command input.');
          writeln
          END;

        done := false;

        WHILE NOT done DO
          BEGIN { Get next command }
          new(BRU_Command);
          WITH BRU_Command^.error_string DO
            BEGIN { With }

            IF terminal_input THEN
              BEGIN
              write('BRU command line: ');
              readln(string)
              END
            ELSE
              readln(Command_File, string);

            length := len(string);
            IF length > 0 THEN
              BEGIN { Link command }
              IF BRU_Input_List = NIL THEN
                BRU_Input_List := BRU_Command { Head of list }
              ELSE
                Bru_Input_Tail^.next_error := BRU_Command; { End of list }
              Bru_Input_Tail := BRU_Command;
              Bru_Input_Tail^.next_error := NIL
              END { Link command }
            ELSE IF terminal_input THEN
              done := true; { End of terminal input }

            IF NOT terminal_input THEN
              done := eof(Command_File) { End of command file }
            END { With }
          END; { Get next command }

        close(Command_File)

      END { Get_Parameters } ;


    PROCEDURE Print_Header;

     { This little routine prints an informational packet    }
     { in the log file.                                      }

      VAR
        Time: Date_Time_String;


      BEGIN { Print_Header }
        writeln(Logfile);
        writeln(Logfile);
        Get_Time(Time);
        writeln(Logfile, '---------  Bacbru session begun on ', Time,
                '  ---------');
        writeln(Logfile)
      END { Print_Header } ;


    PROCEDURE Create_VT;

     { This procedure creates and sets up a virtual terminal }
     { for use by offspring tasks. This procedure disables   }
     { intermediate buffering by the VT: driver so that      }
     { I/O overhead is minimized.                            }
     { Additionally, a logical unit is assigned to the VT:   }
     { so that future I/O can be done.                       }

      VAR
        dsw: integer;
        iosb: rsx$iosbptr;


      BEGIN { Create_VT }

        new(iosb);
        Crvt$(VT_Input_Request, VT_Output_Request, VT_Attach_Detach,
              VT_Buffer_Length, dsw);
        IF dsw < 0 THEN
          BEGIN { CRVT$ failure }
          writeln('Bacbru - *FATAL* CRVT$ failure,', ' Directive status = ',
                  dsw);
          Return_Status(Error_Status)
          END; { CRVT$ failure }

        Vtunit := dsw; { Save VT: unit number }

        { Assign a logical unit number to the virtual terminal }

        Alun$(VT_Logical_Unit_No, 'VT', Vtunit, dsw);
        IF dsw < 0 THEN
          BEGIN { ALUN$ failure }
          writeln('Bacbru - *FATAL* ALUN$ failure,', ' Directive status = ',
                  dsw);
          Return_Status(Error_Status)
          END; { ALUN$ failure }

        { Now disable intermediate buffering for VT: }

        Qiow$(IO_STC, VT_Logical_Unit_No, VT_Event_Flag, iosb, 2, 0, 0, 0, 0,
              0, dsw);
        IF (dsw < 0) OR (iosb^[1] < 0) THEN
          QIO_Error(dsw, iosb) { QIO failure }

      END { Create_VT } ;


    PROCEDURE Abort_Enable;

     { This routine specifies the request exit AST   }
     { so that we can clean up things if we are      }
     { aborted. Failure to do this can cause system  }
     { problems, since BRU will not be aborted by    }
     { the system since it is privileged.            }

      VAR
        dsw: integer;


      BEGIN
        Srea$(Abort_AST, dsw);
        IF dsw < 0 THEN
          BEGIN { SREA$ failure }
          writeln('Bacbru - *FATAL* SREA$ failure,', ' Directive status: ',
                  dsw);
          Return_Status(Error_Status)
          END { SREA$ failure }
      END;


    PROCEDURE Spawn_BRU;

     { This routine spawns the BRU task with the command     }
     { line obtained in Get_Parameters. A timeout is also    }
     { initialized: BRU is given 2 hours to do its job;      }
     { If that is not enough, then change the time interval  }
     { and magnitude arguements in the MRKT$A call.          }

      VAR
        BRU_name: radix50;
        intermediate_ptr: ^String_Type;
        Command_ptr: charptr;
        dsw: integer;
        string: ascstr;


      BEGIN
        BRU_name[1] := Rad50('...');
        BRU_name[2] := Rad50('BRU');
        new(BRU_esb);
        Command_ptr := NIL;
        Spwn$(BRU_name, 0, 0, EF_BRU_Completed, BRU_esb, Command_ptr, 0,
              Vtunit, 'VT', dsw);
        IF dsw < 0 THEN
          BEGIN { SPWN$ failure }
          writeln('Bacbru - *FATAL* SPWN$ failure,', ' Directive status: ',
                  dsw);
          Return_Status(Error_Status)
          END { SPWN$ failure } ;
        write('Bacbru - Spawning BRU on VT');
        IF Vtunit < 8 THEN
          write('0', Vtunit: - 1)
        ELSE
          write(Vtunit: - 2);
        writeln(':');
        BRU_Active := true;

        { Determine exact task name for BRU }

        BRU_Exact_Name[1] := BRU_name[2]; { First half is "BRU" }
        string := 'V  ';
        IF Vtunit >= 8 THEN
          BEGIN
          string[2] := chr((Vtunit DIV 8) + 48);
          string[3] := chr((Vtunit MOD 8) + 48)
          END
        ELSE
          string[2] := chr(Vtunit + 48);
        BRU_Exact_Name[2] := Rad50(string); { Finish second half }

        { Set timeout for BRU }

        Mrkt$(EF_BRU_Timeout, 2, 4, dsw);
        IF dsw < 0 THEN
          writeln('Bacbru - *WARNING* Could not mark BRU time;', ' DSW = ',
                  dsw);

      END; { Spawn_BRU }


    BEGIN { Initialize }

      Command_Name := 'BBR'; { Default command name }
      Logfile_Open := false;
      VT_Created := false;
      BRU_Active := false;
      Excluded_chars := [chr(CR), chr(LF)]; { Ignore these chars from BRU }

      Detach; { Detach from TI: device (user's terminal) }
      Get_Parameters; { Open command file and get parameters }
      Print_Header; { Print some one-liners on the log file }
      Abort_Enable; { Enable abort notification }
      Create_VT; { Create the virtual terminal }
      Error_List_Head := NIL;
      Error_List_Tail := NIL;
      Current_Output := NIL;
      Error_Count := 0;
      Filespec_Count := 0;
      Filespec_Buffer.length := 0;
      Spawn_BRU { Start up BRU with timeout }

    END { Initialize } ;


  PROCEDURE Despecify_Abort;

   { Turn off the requested exit AST       }

    VAR
      dsw: integer;


    BEGIN
      Crea$(dsw)
    END;


  FUNCTION Wait_For_Event: Event_Types;

   { This function is called by the main program loop to wait      }
   { for the next asynchronous event to occur.                     }
   {    This is implemented by stopping the task on the logical-or }
   { of the set of event flags that are set by the AST routines.   }

    VAR
      dsw: integer; { Directive status word }
      found: boolean;
      rdafbuf: rsx$rdafbuf; { Buffer to receive event flags }
      event_index: Event_Types_Range; { For loop index }


    BEGIN
      found := false;

      WHILE NOT found DO
        BEGIN { Find a set event flag }
        Stlo$(EF_Group, EF_Mask, dsw); { Stop for an event flag to raise }
        Rdaf$(rdafbuf, dsw); { Read the event flags }

        FOR event_index := Ev_Abort TO Ev_Done DO { Go through event flags }
          IF NOT found THEN
            IF (Mask_Binding[event_index] AND
               rdafbuf[1]) { Bit masking } <> 0 THEN
              BEGIN { Found flag }
              found := true;
              Wait_For_Event := event_index; { Function return value }
              Clef$(EF_Binding[event_index], dsw) { Clear event flag now }
              END { Found flag }
        END { Find a set event flag }
    END;


  BEGIN
    Initialize; { Initiate operations and get BRU running }
    WHILE BRU_Active DO
      BEGIN { Main loop }
      Event := Wait_For_Event; { Get next event code }
      CASE Event OF
        Ev_Input:
          Process_Input; { Virtual terminal input }
        Ev_Output:
          Process_Output; { Virtual terminal output }
        Ev_Attach:
          Process_Attach; { Virtual terminal attach/detach }
        Ev_Timeout:
          Process_Timeout; { Timeout on BRU }
        Ev_Done:
          BRU_Active := false; { BRU done, end loop }
        Ev_Abort:
          BEGIN { Requested exit }
          Abort_BRU; { Shut down BRU (and everything else) }
          Despecify_Abort
          END { Requested exit }
        END { Case }
      END { Main Loop } ;
    Cleanup; { Finish job }
    Return_Status(BRU_esb^[1]) { Return BRU status to parent job }
  END.
