{$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 Where is a file containing the information needed by Bacbru. The format of a command file is as follows: ... Where 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. 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 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.