.title  TAIL
;%%
;%% [tmk] VFC support added
;%% [tmk] Ability to look at open files added
;%% [tmk] ACCVIO w/ variable files corrected
;%% [tmk] Fix problem with empty files
;%% [tmk] Fix problem with /OUTPUT qualifier not working
;%%
                .ident  /V1-003/
                .subtitle TAIL overall description
;
;*******************************************************************************
;* Title:       TAIL                                                           *
;*                                                                             *
;* Version:     1-001                                                          *
;*                                                                             *
;* Facility:    TAIL -- a unix like Tail program.                              *
;*                                                                             *
;* Abstract:    TAIL is a program to be used to look at the ending lines of a  *
;*              large files.  It works best for LARGE files in the thousand    *
;*              of blocks range.                                               *
;*                                                                             *
;*              What we do is map a section over the file treat it as if it    *
;*              were memory.  If the file is not of the supported type, an     *
;*              error message is signaled.                                     *
;*                                                                             *
;* Environment: This module runs in user mode, no priviliges necessary.        *
;*                                                                             *
;* Author:      Peter A. Portante, 1989                                        *
;*******************************************************************************
;
                .library        /SYS$LIBRARY:LIB.MLB/
                $SSDEF
                $FABDEF
                $NAMDEF
                $RMSDEF
                $SECDEF
;
LINE_LEN = 80
BACKUP_BLKS = 5 * 512
;
                .psect  rms_data, noexe, wrt, pic, noshr, long
;
                .align  long
sec_nam:        $nam    esa = sec_exp_name, -   ; Expaned wildcard name
                        ess = NAM$C_MAXRSS, -
                        rsa = sec_ful_name, -   ; Full file specification, no
                        rss = NAM$C_MAXRSS      ; - wildcards.
;
                .align  long
sec_fab:        $fab    fop = <NAM, UFO>, -     ; Name block processing
                        fna = sec_fil_name, -   ; File name from command line
                        dna = sec_rel_name, -   ; Related file name spec
;%%
;%% [tmk] Ability to look at open files added
;%%
                        fac = <GET>, -
                        shr = <UPI,GET,PUT,DEL,UPD>, -
                        dnm = <.LIS>, -         ; Default file name
                        nam = sec_nam, -        ; Address of Name Block
                        xab = sec_xabfhc        ; List of Extended Attr. Blocks
;
                .align  long
sec_xabfhc:     $xabfhc                         ; Set up file header block
;
                .align  long
out_nam:        $nam    esa = out_ful_name, -   ; Just use the expanded
                        ess = NAM$C_MAXRSS      ; - file name
;
                .align  long
out_fab:        $fab    fop = <NAM, TEF, SQO>,- ; Name block processing
                        fac = <PUT>, -          ; Just write to the file
                        rat = CR, -             ; Carriage ret carriage ctrl
                        fna = out_fil_name, -   ; File name from command line
;%%
;%% [tmk] Fix problem with /OUTPUT qualifier not working
;%%
;%%                     dna = out_rel_name, -   ; Related file name
;%%                     dnm = <.LIS>, -         ; Default file name
                        nam = out_nam           ; Address of Name Block
;
                .align  long
out_rab:        $rab    fab = out_fab, -        ; Address of File Access Block
                        mbf = 16, -             ; Use 16 buffers for writing
                        rop = <WBH>             ; Write Behind option
;
;
;
                .psect  normal_data, noexe, wrt, pic, noshr
;
sec_fil_desc:   .long   NAM$C_MAXRSS
                .address sec_fil_name
sec_fil_name:   .blkb   NAM$C_MAXRSS            ; Original input file name
;
sec_ful_desc:   .long   NAM$C_MAXRSS
                .address sec_ful_name
sec_ful_name:   .blkb   NAM$C_MAXRSS            ; Full input file name
;
sec_exp_desc:   .long   NAM$C_MAXRSS
                .address sec_exp_name
sec_exp_name:   .blkb   NAM$C_MAXRSS            ; Expanded input file name
;
sec_rel_desc:   .long   NAM$C_MAXRSS
                .address sec_rel_name
sec_rel_name:   .blkb   NAM$C_MAXRSS            ; Related file name
;
out_fil_desc:   .long   NAM$C_MAXRSS
                .address out_fil_name
out_fil_name:   .blkb   NAM$C_MAXRSS            ; Original output file name
;
out_ful_desc:   .long   NAM$C_MAXRSS
                .address out_ful_name
out_ful_name:   .blkb   NAM$C_MAXRSS            ; Full output file name
;
;%%
;%% [tmk] Fix problem with /OUTPUT qualifier not working
;%%
;%%out_rel_name:   .blkb   NAM$C_MAXRSS
;
number_string:  .long   NAM$C_MAXRSS            ; Number string from cmd line
                .address number_buffer
number_buffer:  .blkb   NAM$C_MAXRSS
;
input_parm:     .ascid  /INPUT/                 ; Input file name
output_qual:    .ascid  /OUTPUT/                ; Output file name
last_qual:      .ascid  /LAST/                  ; Number of lines to tail
;
number:         .long   0                       ; Number of lines to tail
;
p0_space:       .long   0                       ; Addresses of space allocated
p0_space_end:   .long   0
;
beg_adr:        .long   0                       ; Addresses of file mapped
end_adr:        .long   0
;
already_open:   .byte   0
parsed:         .byte   0
have_file:      .byte   0
printed:        .byte   0
;
udf_desc:       .ascid  /UNDEFINED/             ; Format names
fix_desc:       .ascid  /FIXED/
var_desc:       .ascid  /VARIABLE/
vfc_desc:       .ascid  /VARIABLE with FIXED CONTROL/
stm_desc:       .ascid  /STREAM/
slf_desc:       .ascid  /STREAM LF/
scr_desc:       .ascid  /STREAM CR/
;
format_arr:     .address udf_desc               ; FAB$C_UDF unsupported
                .address fix_desc               ; FAB$C_FIX unsupported
                .address var_desc               ; FAB$C_VAR
;%%
;%% [tmk] VFC support
;%%
                .address vfc_desc               ; FAB$C_VFC
                .address stm_desc               ; FAB$C_STM unsupported
                .address slf_desc               ; FAB$C_STMLF
                .address scr_desc               ; FAB$C_STMCR
;
;
;
                .psect  code, exe, nowrt, pic
;
;*******************************************************************************
;* Tail                                                                        *
;*                                                                             *
;*      This is the main entry point for the tail procedure.                   *
;*                                                                             *
;* Global register use:                                                        *
;*                                                                             *
;* R11 - Section FAB                                                           *
;* R10 - Section XAB FHC                                                       *
;* R9  - Channel to section file                                               *
;* R8  - Search routine to use                                                 *
;* R7  - Scratch                                                               *
;* R6  - Scratch                                                               *
;* R5  - Scratch                                                               *
;* R4  - Scratch                                                               *
;* R3  - Scratch                                                               *
;* R2  - Scratch                                                               *
;*******************************************************************************
;
                .entry  tail, ^m<r2,r3,r4,r5,r6,r7,r8,r9,r10,r11>
;
                clrb    already_open            ; No output file open
                clrb    parsed                  ; No parsing yet
                clrb    have_file               ; Don't have any files yet
                clrb    printed                 ; We havn't printed anything
;
                movab   sec_fab,    r11         ; Section FAB
                movab   sec_xabfhc, r10         ; Section XAB FHC
;
                bsbw    get_cli                 ; Get the command line params
;
;
loop:           bsbw    open_input              ; Could we get a file?
                blbs    r0, 10$
                brb     end_loop
;
10$:            bsbw    open_output             ; Open the output file
                bsbw    map_input               ; Map file into memory
                blbc    r0, 20$
;
                jsb     (r8)                    ; Process the records
;
20$:            bsbw    close_input             ; Close the input file
                brw     loop                    ; Get another file
;
;
end_loop:       tstb    already_open            ; Is there a file open?
                beql    10$
;
                movab   out_fab, r2             ; Save output FAB
                $close  fab = (r2)              ; Close the output file
                blbs    r0, 10$
;
                pushl   FAB$L_STV(r2)
                pushl   FAB$L_STS(r2)
                pushab  out_ful_desc
                pushl   #1
                pushl   #TAIL_CLOSE             ; Signal the error
                calls   #5, g^lib$stop
;
10$:            movl    #1, r0                  ; Return status
                ret
;
;
;
;*******************************************************************************
;* Get_cli                                                                     *
;*                                                                             *
;*      This routine gets the output file name and the number of lines to tail *
;* off the command line.                                                       *
;*******************************************************************************
;
get_cli:        pushab  out_fil_desc            ; Where to store return length
                pushab  out_fil_desc            ; Where string descriptor is
                pushab  output_qual             ; Qualifier to get
                calls   #3, g^cli$get_value     ; Get the value off command line
                blbs    r0, get_lines
;
                pushl   r0
                pushl   #TAIL_NOOUTPUT          ; Signal error
                calls   #2, g^lib$stop
;
get_lines:      pushab  number_string           ; Where to store return length
                pushab  number_string           ; Where string descriptor is
                pushab  last_qual               ; Qualifier to get
                calls   #3, g^cli$get_value     ; Get the value off command line
                blbs    r0, convert
;
                pushl   r0
                pushl   #0
                pushl   #TAIL_NONUMBER          ; Signal error
                calls   #3, g^lib$stop
;
convert:        pushl   #3                      ; Ignores tabs and blanks
                pushl   #4                      ; Want an unsigned longword
                pushab  number                  ; Where to put converted long
                pushab  number_string           ; Where to get convertable long
                calls   #4, g^ots$cvt_tu_l      ; Convert the integer
                blbs    r0, chk_zero
;
                pushl   r0
                pushab  number_string
                pushl   #1
                pushl   #TAIL_ILLNUMBER         ; Signal error
                calls   #4, g^lib$stop
;
chk_zero:       tstl    number
                bneq    done
;
                pushl   #TAIL_NONZERO
                calls   #1, g^lib$stop
;
done:           rsb                             ; End Get_cli
;
;
;
;*******************************************************************************
;* Open_input                                                                  *
;*                                                                             *
;*      This routine opens the input files for processing.  Each time it is    *
;* called it opens the next file in the list specified on the command line.    *
;* When all files have been processed, a 0 is returned in R0.  If an error     *
;* occurs during the file processing, if it is recoverable, we just signal an  *
;* error message and return the next file.  If it is not recoverable we signal *
;* an error and then return a 0 in R0.                                         *
;*******************************************************************************
;
open_input:     tstb    have_file               ; Do we have a file?
                bneq    have_one
;
get_one:        mnegb   #1, have_file           ; Flag, we will have a file now
                movw    #NAM$C_MAXRSS, sec_fil_desc ; Insure maximum length
                pushab  sec_fil_desc            ; Where to store return length
                pushab  sec_fil_desc            ; Where string descriptor is
                pushab  input_parm              ; Qualifier to get
                calls   #3, g^cli$get_value     ; Get the value off command line
                blbs    r0, have_one
;
                clrl    r0                      ; Leave we are done
                rsb
;
have_one:       tstb    parsed                  ; Have we parsed this file name?
                bneq    do_search
;
                mnegb   #1, parsed              ; Flag, we will have parsed
                movb    sec_fil_desc, FAB$B_FNS(r11) ; Save the file name length
                $parse  fab = (r11)             ; Parse out the file name
                blbs    r0, do_search
;
                pushl   FAB$L_STV(r11)
                pushl   FAB$L_STS(r11)
                pushab  sec_fil_desc
                pushl   #1
                pushl   #TAIL_PARSE             ; Parse failed
                calls   #5, g^lib$signal
                clrb    parsed                  ; Clear flags
                clrb    have_file
                brw     get_one                 ; Get another file name
;
do_search:      movab   sec_nam, r0
                movb    NAM$B_ESL(r0), sec_exp_desc ; Fill expanded descriptor
                movb    NAM$B_ESL(r0), sec_rel_desc ; Fill related descriptor
                movb    NAM$B_ESL(r0), FAB$B_DNS(r11)
                movzbl  NAM$B_ESL(r0), r1       ; Save this for default
                movc3   r1, @NAM$L_ESA(r0), @FAB$L_DNA(r11)
;
                $search fab = (r11)             ; Search for the file
                blbs    r0, do_open
;
                clrb    parsed                  ; Clear processing flags
                clrb    have_file
;
                cmpl    r0, #RMS$_NMF           ; No more files to search?
                bneq    20$
                brw     get_one                 ; Get another file name
;
20$:            pushl   FAB$L_STV(r11)
                pushl   FAB$L_STS(r11)
                pushab  sec_exp_desc
                pushl   #1
                pushl   #TAIL_SEARCH
                calls   #5, g^lib$signal        ; Signal search error
                brw     get_one                 ; Get another file name
;
do_open:        movab   sec_nam, r0
                movb    NAM$B_RSL(r0), sec_ful_desc ; Fill full descriptor
                $open   fab = (r11)             ; Open the file
                blbs    r0, chk_file
;
                pushl   FAB$L_STV(r11)
                pushl   FAB$L_STS(r11)
                pushab  sec_ful_desc
                pushl   #1
                pushl   #TAIL_OPEN
                calls   #5, g^lib$signal        ; Signal open error
                brw     do_search               ; Get another file
;
chk_file:       movzwl  FAB$L_STV(r11), r9      ; Save channel of section
                bsbw    check_file              ; Check for accepted file types
                blbs    r0, end_open_in
;
                $dassgn_s -
                        chan = r9               ; Failure, close file
                blbs    r0, 10$
;
                pushl   r0
                pushal  sec_ful_desc
                pushl   #1
                pushl   #TAIL_CLOSE
                calls   #4, g^lib$signal        ; Signal if error closing file
;
10$:            brw     do_search               ; Get another file
;
end_open_in:    rsb                             ; End of Open_input
;
;
;
;*******************************************************************************
;* Close_input                                                                 *
;*                                                                             *
;*      This routine closes the input file and deletes the virtual address     *
;* space used by it.                                                           *
;*                                                                             *
;* R9 - Channel to the section file                                            *
;*******************************************************************************
;
close_input:    $deltva_s -                     ; Delete the virtual address
                        inadr = beg_adr         ; - space used by file
                blbs    r0, 10$
;
;%%
;%% [tmk] Check to see if the error was NO_PRIV. If it was, silently eat it
;%% since it came from a previous error in mapping a null file (can't get a
;%% NO_PRIV on a close otherwise, we'd have seen it on the open as well...
;%%
		cmpl	r0, #SS$_NOPRIV		; No privs?
		beql	10$			; Yup, exit quietly
                pushl   r0
                pushal  sec_ful_desc
                pushl   #1
                pushl   #TAIL_CLOSE             ; Signal the error
                calls   #4, g^lib$signal
;
10$:            $dassgn_s -                     ; Deassign the channel to file
                        chan = r9
                blbs    r0, 20$
;
                pushl   r0
                pushal  sec_ful_desc
                pushl   #1
                pushl   #TAIL_CLOSE             ; Signal the error
                calls   #4, g^lib$signal
;
20$:            rsb                             ; End of Close_input
;
;
;
;*******************************************************************************
;* Open_output                                                                 *
;*                                                                             *
;*      This routine opens the output file for processing.  If the file has    *
;* already been open, we just return success.  For each file we processed, we  *
;* call this routine to make sure the output file is open.  If it isn't        *
;* already we open it.                                                         *
;*                                                                             *
;* R3 - Output FAB                                                             *
;* R2 - Output RAB                                                             *
;*******************************************************************************
;
open_output:    tstb    already_open            ; Did we already open it?
                beql    10$
                rsb
;
10$:            movab   out_fab, r3             ; Save output FAB
                movab   out_rab, r2             ; Save output RAB
                movb    out_fil_desc, FAB$B_FNS(r3) ; Save the given file length
                $parse  fab = (r3)              ; Parse the given file name
                blbs    r0, 20$
;
                pushl   FAB$L_STV(r3)
                pushl   FAB$L_STS(r3)
                pushab  out_fil_desc
                pushl   #1
                pushl   #TAIL_PARSE             ; Report the failure and stop!
                calls   #5, g^lib$stop
;
20$:            $create fab = (r3)              ; Create the output file
                blbs    r0, 30$
;
                pushl   FAB$L_STV(r3)
                pushl   FAB$L_STS(r3)
                pushab  out_ful_desc
                pushl   #1
                pushl   #TAIL_OPEN              ; Report the failure and stop!
                calls   #5, g^lib$stop
;
30$:            $connect rab = (r2)             ; Try to connect a record stream
                blbs    r0, 40$
;
                pushl   RAB$L_STV(r2)
                pushl   RAB$L_STS(r2)
                pushab  out_ful_desc
                pushl   #1
                pushl   #TAIL_OPEN              ; Report the failure and stop!
                calls   #5, g^lib$stop
;
40$:            mnegb   #1, already_open
                rsb                             ; End of Open_output
;
;
;
;*******************************************************************************
;* Check_file                                                                  *
;*                                                                             *
;*      This routine checks the file's organization and record format to make  *
;* sure it is of the supported type.                                           *
;*                                                                             *
;* R11 - input file FAB                                                        *
;* R8  - appropriate search routine to use                                     *
;*******************************************************************************
;
check_file:     cmpb    FAB$B_ORG(R11), -       ; Make sure it's sequential file
                        #FAB$C_SEQ
                beql    10$
;
                pushab  sec_ful_desc
                pushl   #1
                pushl   #TAIL_UNSUPORG
                calls   #3, g^lib$signal        ; Signal unsupported file org
                clrl    r0
                rsb
;
10$:            caseb   FAB$B_RFM(r11), -       ; Check supported formats
                        #FAB$C_UDF, -
                        #FAB$C_MAXRFM - FAB$C_UDF
;
rfm_case:       .word   bad  - rfm_case         ; FAB$C_UDF unsupported
                .word   bad  - rfm_case         ; FAB$C_FIX unsupported
                .word   var  - rfm_case         ; FAB$C_VAR
;%%
;%% [tmk] VFC support
;%%
                .word   vfc  - rfm_case         ; FAB$C_VFC
                .word   bad  - rfm_case         ; FAB$C_STM unsupported
                .word   slf  - rfm_case         ; FAB$C_STMLF
                .word   scr  - rfm_case         ; FAB$C_STMCR
;
bad:            movzbl  FAB$B_RFM(r11), r1      ; Setup format array indexing
                movab   format_arr, r0
;
                pushab  sec_ful_desc
                pushl   (r0)[r1]
                pushl   #2
                pushl   #TAIL_UNSUPRFM
                calls   #4, g^lib$signal        ; Unsupported record format
                clrl    r0                      ; Report error
                rsb
;
var:            movab   var_search, r8          ; Variable print routine
                movl    #1, r0
                rsb
;
;%%
;%% [tmk] VFC support
;%%
vfc:            movab   vfc_search, r8          ; Variable FC print routine
                movl    #1, r0
                rsb
;
slf:            movab   slf_search, r8          ; Stream LF print routine
                movl    #1, r0
                rsb
;
scr:            movab   scr_search, r8          ; Stream CR print routine
                movl    #1, r0
                rsb                             ; End of Check_file
;
;
;
;*******************************************************************************
;* Map_input                                                                   *
;*                                                                             *
;*      This routine maps a given file into memory.                            *
;*******************************************************************************
;
map_input:      clrq    p0_space                ; Clear out return address
;%%
;%% [tmk] Pad the space we ask for as locate_loop was locc'ing off the end of
;%%	  allocated memory and ACCVIO'ing in some cases...
;%%
		movl	XAB$L_EBK(r10), r0	; Scratch copy of file size
		addl2	#4, r0			; Pad it by 2Kb
                $expreg_s -
                        pagcnt = r0, -		; Allocate enough memory
                        retadr = p0_space
                blbs    r0, 10$                 ; Success?
;
                pushl   r0
                pushab  sec_ful_desc
                pushl   #1
                pushl   #TAIL_READ              ; Signal the error
                calls   #4, g^lib$signal
                clrl    r0
                rsb                             ; Leave...
;
10$:            $crmpsc_s -                     ; Map the section
                        inadr = p0_space, -
                        retadr = beg_adr, -
                        flags = SEC$M_EXPREG, -
                        chan = r9, -
                        pagcnt = XAB$L_EBK(r10), -
                        pfc = #5
                blbs    r0, 20$                 ; Success?
;
;%%
;%% [tmk] See if the error was end-of-file. If it was, no need to print an
;%% error message (since the tail of an empty file is nothing).
;%%
		cmpl	r0, #SS$_ENDOFFILE	; End of file?
		beql	5$			; Yup, exit quietly
                pushl   r0
                pushab  sec_ful_desc
                pushl   #1
                pushl   #TAIL_READ              ; Signal the error
                calls   #4, g^lib$signal
5$:             clrl    r0
;
20$:            rsb                             ; Leave...
;
;
;
;*******************************************************************************
;* Search routines                                                             *
;*                                                                             *
;*      These routines page back through the file and located the last N       *
;* records then print them out.  The routines for STREAM_LF and STREAM_CR are  *
;* simple in that they just count backwards the number of LF or CR in file.    **
;* Since VARIABLE records have the length of the record at its beginning we    *
;* cannot start from the end of the file and go backwords using defined        *
;* markers like in STREAM_LF and STREAM_CR.  We ALSO cannot start at the       *
;* beginning since it will incurr too many page faults for LARGE files.  So    *
;* to avoid this, we start looking for the last few records NEAR the end of    *
;* the file.  TRAPSE is a routine that checks to see if we are on a correct    *
;* record byte adding the current word to our address and jumping through the  *
;* file until we hit EOF on the nose.                                          *
;*                                                                             *
;* The routine for VARIABLE records is different, this is the algorithm:       *
;*                                                                             *
;*       Take the number of lines to search, multiply by the an                *
;*       arbitrary line length of LINE_LEN to get number of bytes from         *
;*       the end of file.  This is where we are going to start.  If            *
;*       our starting position is at or before the beginning of the            *
;*       file, we will just TRAPSE from the beginning of the file and          *
;*       then print out the lines.  If we are not at the beginning of          *
;*       the file, we will search for a NULL byte.  R1 contains the            *
;*       starting search position and R0 the starting length.  R1 is           *
;*       also saved in R2.                                                     *
;*                                                                             *
;*       NO NULL BYTE FOUND:                                                   *
;*                                                                             *
;*       Now we must go back further in the file to find one.  But we          *
;*       don't want to search past our current point.  So we subract           *
;*       from our saved position 1536 bytes (3 blocks) and do the search       *
;*       again.  We then resave out current position in R2.                    *
;*                                                                             *
;*       FOUND:                                                                *
;*                                                                             *
;*       If the null byte found is an even address it means it is              *
;*       either part of a record or the first byte in the WORD record          *
;*       count.  So we will assume that it is the record count and             *
;*       TRAPSE through the file to see if this accurately brings us           *
;*       to the end of file.  If it doesn't, we will look for the next         *
;*       null byte in the current section of the file.                         *
;*                                                                             *
;*       ODD ADDRESS:                                                          *
;*                                                                             *
;*       Since the null byte is on an odd address, it could be one of          *
;*       Three things:                                                         *
;*                                                                             *
;*              1) Second byte in WORD record count                            *
;*              2) Byte to pad record to even length                           *
;*              3) Null within record                                          *
;*                                                                             *
;*       First we will check to see if it is the second byte in the            *
;*       WORD record count and TRAPSE through the file to see if we            *
;*       get to EOF correctly.  If it is not that, we then we use the          *
;*       following word as a record count and trapse through the file          *
;*       to see if we get to EOF correctly.  If neither of the above           *
;*       work we go back and find another byte.                                *
;*                                                                             *
;* R11 - Section FAB                                                           *
;* R10 - Section XAB (File Header Characteristics, FHC)                        *
;* R9  - Channel to section file                                               *
;* R8  - Print routine to use                                                  *
;* R7  - TRAPSE saves the total number of records found here                   *
;* R6  - TRAPSE saves the address of the first good record it finds            *
;* R5  - TRAPSE uses the temporarily for counting the number of records        *
;* R4  - Address to start printing                                             *
;* R3  - Address of EOF byte                                                   *
;* R2  - Saved start position                                                  *
;* R1  - Address of NULL byte, or one byte past end of string                  *
;* R0  - Current length of data to look for null byte                          *
;*******************************************************************************
;
;%%
;%% [tmk] VFC support
;%%
vfc_search:     subl3   #1, XAB$L_EBK(r10), r0  ; Number of blocks - EOF block
                mull2   #512, r0                ; Calculate number of bytes in
                movzwl  XAB$W_FFB(r10), r2      ; - the file, R2,R0 are scratch
                addl2   r2, r0                  ; Calculate address of byte
                addl3   beg_adr, r0, r3         ; - after EOF, R2,R0 are scratch
;
                cmpl    r3, beg_adr             ; Anything in file?
                bneq    10$
                rsb                             ; Nothing to do, leave...
;
10$:            clrl    r5                      ; Just for cleanliness
                movl    r3, r6                  ; Position to TRAPSE too.
                clrl    r7                      ; No records found
                movab   vfc_print, r8           ; Save print routine to use
		brb	varcom			; join common code
;
var_search:     subl3   #1, XAB$L_EBK(r10), r0  ; Number of blocks - EOF block
                mull2   #512, r0                ; Calculate number of bytes in
                movzwl  XAB$W_FFB(r10), r2      ; - the file, R2,R0 are scratch
                addl2   r2, r0                  ; Calculate address of byte
                addl3   beg_adr, r0, r3         ; - after EOF, R2,R0 are scratch
;
                cmpl    r3, beg_adr             ; Anything in file?
                bneq    10$
                rsb                             ; Nothing to do, leave...
;
10$:            clrl    r5                      ; Just for cleanliness
                movl    r3, r6                  ; Position to TRAPSE too.
                clrl    r7                      ; No records found
                movab   var_print, r8           ; Save print routine to use
;+
; Now take the number of lines to search, multiply by the an arbitrary line
; length of LINE_LEN to get number of bytes from end of file to start search.
; -
;%%
;%% [tmk] VFC support
;%%
varcom:         mull3   number, #LINE_LEN, r0   ; Figure distance from EOF
                subl3   r0, r3, r1              ; Calculate that address
                movl    r1, r2                  ; Save our position for later
;+
; Now see if are starting postion is the beginning of or before the file.
;-
                cmpl    r1, beg_adr             ; Are we past the beginning?
                bgtr    locate_loop
;
                movl    beg_adr, r1             ; Address to start print
                bsbw    trapse                  ; Trapse through file
                tstl    r4                      ; No enough records found?
                bneq    20$
                movl    beg_adr, r4             ; Print out whole file
20$:            bsbw    print_records           ; Print out the records
                rsb                             ; Leave...
;+
; Now start the search, locate the first null byte from this point
;-
locate_loop:    locc    #0, r0, (r1)            ; Find a null byte
                bneq    found_null              ; r0 <> 0 we found a null byte
;+
; Ok, we didn't find a null, now we must go back more in the file to find one.
; But we don't want to search past our current point.  So we subract from our
; saved position BACKUP_BLKS and do the search again.
;-
                subl2   #BACKUP_BLKS, r2        ; Subtract backup blocks
                cmpl    r2, beg_adr             ; Past beginning of file?
                bgtr    20$
;
                movl    beg_adr, r1             ; Address to start print
                bsbw    trapse                  ; Trapse through file
                tstl    r4                      ; No enough records found?
                bneq    10$
                movl    beg_adr, r4             ; Print out whole file
10$:            bsbw    print_records           ; Print out the records
                rsb                             ; Leave...
;
20$:            movl    #BACKUP_BLKS, r0        ; Length to search
                movl    r2, r1                  ; Setup address to search
                brb     locate_loop             ; Go back in file, search again
;+
; If the null byte found is an even address it means it is either part of a
; record or the first byte in the WORD record count.  So we will assume that
; it is the record count and trapse through the file to see if this accurately
; brings us to the end of file.  If it doesn't we will look for the next one.
;-
found_null:     blbs    r1, 10$                 ; Is it odd or even address?
                bsbw    trapse                  ; Look for EOF correctly
                tstl    r4                      ; If nothing in R4, failure
                beql    locate_loop             ; Go back and search again
;
                bsbw    print_records           ; Print out records
                rsb                             ; Leave...
;+
; The null byte is an odd address.  So that means it is either one of three
; things:
;       1) Second byte in WORD record count
;       2) Byte to pad record to even length
;       3) Null within record
; First we will check to see if it is the second byte in the WORD record count
; and trapse through the file to see if we get to EOF correctly.  If it is not
; that, we then we use the following word as a record count and trapse through
; the file to see if we get to EOF correctly.  If neither of the above work
; we go back and find another byte.
;-
10$:            movab   -1(r1), r1              ; Do first case...
                bsbw    trapse
                tstl    r4
                beql    20$
;
                bsbw    print_records           ; Print out records
                rsb                             ; Leave...
;
20$:            movab   2(r1), r1               ; Do second case...
                bsbw    trapse
                tstl    r4
                bneq    30$
;
                brw     locate_loop             ; Third case, get next null byte
;
30$:            bsbw    print_records           ; Print out records
                rsb                             ; End of Var_search
;
;
;
slf_search:     movzbl  #10, r5                 ; Save terminating byte, LF
                brb     stm_search              ; Do the search
;
scr_search:     movzbl  #13, r5                 ; Save terminating byte, CR
;
stm_search:     subl3   #1, XAB$L_EBK(r10), r0  ; Number of blocks - EOF block
                mull2   #512, r0                ; Calculate number of bytes in
                movzwl  XAB$W_FFB(r10), r2      ; - the file, R2,R0 are scratch
                addl2   r2, r0                  ; Calculate address of byte
                addl3   beg_adr, r0, r3         ; - after EOF, R2,R0 are scratch
                clrl    r6                      ; Clear counter
                movab   stm_print, r8           ; Save print routine to use
;
                cmpl    r3, beg_adr             ; Anything in file?
                bneq    10$
                rsb                             ; Nothing to do, leave...
;
10$:            movab   -1(r3), r4              ; Save address of last byte
                cmpl    r4, beg_adr             ; See if we are not at the end
                bgtr    20$
                rsb                             ; At the beginning, leave.
;
20$:            cmpb    -(r4), r5               ; Look for previous stream term.
                beql    30$
;
                cmpl    r4, beg_adr             ; Don't go past BOF.
                bgtr    20$
;
                bsbw    print_records           ; Hit beginning of file, print
                rsb                             ; Leave...
;
30$:            aoblss  number, r6, 20$         ; Increment record count
;
                incl    r4                      ; We are on the stream term
                bsbw    print_records           ; - move to next byte and print.
                rsb                             ; End of Stm_search
;
;
;
;*******************************************************************************
;* Trapse                                                                      *
;*                                                                             *
;*      This routine jumps through the record counts to see if the starting    *
;* record count takes us to the EOF byte, while keeping track of how many      *
;* records we counted.  If we find that the current assumption for a record    *
;* takes us to EOF we save it for later testing so we don't keep going all the *
;* way to the EOF on large tail operations.  If we are not taken to the EOF    *
;* byte, or the last saved valid record, or the number of records is too       *
;* small, we just clear R4 and RSB to leave.  If we have TOO many records then *
;* we go back through and find the last few we need returning the address of   *
;* the records to print out in R4.  If the number of records is just we return *
;* the address of the records in R4.  If the number of records is too little   *
;* we save the valid record in R6 and clear R4.                                *
;*                                                                             *
;* R1 - Address to start trapesing through memory                              *
;* R3 - Address of EOF byte in file                                            *
;* R4 - Address of current memory location                                     *
;* R5 - record count                                                           *
;* R6 - Address of first valid record found                                    *
;*******************************************************************************
;
trapse:         movl    r1, r4                  ; Save current location
                clrl    r5                      ; Clear counter
;
trapse_loop:    movzwl  (r4)+, r0
                addl2   r0, r4                  ; Move to next record
                blbc    r4, 10$                 ; Even address?
                incl    r4                      ; No, make it even
10$:            incl    r5                      ; We have another record
                cmpl    r4, r6                  ; Compare to EOF
                blss    trapse_loop             ; Still less than?
;
                beql    20$                     ; Did we hit EOF?
                clrl    r5
                clrl    r4
                rsb                             ; Just leave, not good
;
20$:            movl    r1, r6                  ; Save for ending TRAPSE compare
                addl2   r5, r7                  ; Add to total records found
                cmpl    r7, number              ; Check against number of lines
                bgtr    shorten                 ; More than we need, cut down
                beql    success                 ; Exactly what we want
;
                clrl    r5                      ; Clear for cleanliness
                clrl    r4                      ; Not enough records
                rsb                             ; Leave...
;
success:        movl    r1, r4                  ; Records are accurate
                rsb                             ; Leave with print address
;
shorten:        subl2   number, r7              ; Find how many we are over
                movl    r1, r4                  ; Start at beginning again
10$:            movzwl  (r4)+, r0
                addl2   r0, r4                  ; Move to next record
                blbc    r4, 15$                 ; Even address?
                incl    r4                      ; No make it even
15$:            sobgtr  r7, 10$                 ; Until we have number we want
;
                rsb                             ; End of Trapse
;
;
;
;*******************************************************************************
;* Print routines                                                              *
;*                                                                             *
;*      For each of the supported formats, these routines print out the        *
;* records from the memory location in R4 on down.                             *
;*                                                                             *
;* LATER make characters printable.                                            *
;*                                                                             *
;* R0 - Scratch                                                                *
;* R2 - Address of RAB                                                         *
;* R3 - Address of EOF byte                                                    *
;* R4 - Address of current memory location                                     *
;* R5 - Byte to locate for STREAM files                                        *
;*******************************************************************************
;
print_records:  movab   out_rab, r2             ; Save our output RAB
                tstb    printed                 ; Did we print a file before?
                beql    30$
;
                clrw    RAB$W_RSZ(r2)           ; Blank line
                bsbw    put_record              ; Print blank line
                blbs    r0, 10$                 ; Did an error occur?
                rsb                             ; Leave if one did
;
10$:            movzbw  sec_ful_desc, RAB$W_RSZ(r2) ; Save file name length
                movab   sec_ful_name, RAB$L_RBF(r2) ; Save file name address
                bsbw    put_record              ; Print file name
                blbs    r0, 20$                 ; Did an error occur?
                rsb                             ; Leave if one did
;
20$:            clrw    RAB$W_RSZ(r2)           ; Blank line
                bsbw    put_record              ; Print blank line
                blbs    r0, 30$                 ; Did an error occur?
                rsb                             ; Leave if one did
;
30$:            jsb     (r8)                    ; Print out the records
                mnegb   #1, printed             ; We have printed things out
                rsb                             ; End of Print_records
;
;
;
var_print:      movw    (r4)+, RAB$W_RSZ(r2)    ; Save record length
                movl    r4, RAB$L_RBF(r2)       ; Save record address
                bsbw    put_record              ; Print the record
                blbs    r0, 10$                 ; Test for success
                brb     30$
;
10$:            movzwl  RAB$W_RSZ(r2), r0       ; Restore record length
                addl2   r0, r4                  ; Move past this record
                blbc    r0, 20$                 ; Is the record length even?
                incl    r4                      ; If not, make it even.
20$:            cmpl    r4, r3
                blss    var_print
30$:            rsb                             ; End of Var_print
;
;
;
;%%
;%% [tmk] VFC support
;%%
vfc_print:	movw    (r4)+, RAB$W_RSZ(r2)    ; Save record length
		subw2	#2, RAB$W_RSZ(R2)	; minus the fixed control bytes
                movl    r4, RAB$L_RBF(r2)       ; Save record address
		addl2	#2, RAB$L_RBF(r2)	; past the fixed control bytes
                bsbw    put_record              ; Print the record
                blbs    r0, 10$                 ; Test for success
                brb     30$
;
10$:            movzwl  RAB$W_RSZ(r2), r0       ; Restore record length
		addl2	#2, r0
                addl2   r0, r4                  ; Move past this record
                blbc    r0, 20$                 ; Is the record length even?
                incl    r4                      ; If not, make it even.
20$:            cmpl    r4, r3
                blss    vfc_print
30$:            rsb                             ; End of Vfc_print
;
;
;
stm_print:      subl3   r4, r3, r0
                cmpl    #65535, r0
                bgeq    10$
;
                movzwl  #65535, r0
;
10$:            locc    r5, r0, (r4)
                subl3   r4, r1, RAB$W_RSZ(r2)
                movl    r4, RAB$L_RBF(r2)
                addl3   #1, r1, r4
                bsbw    put_record
                blbs    r0, 20$
                brb     30$
;
20$:            cmpl    r4, r3
                blss    stm_print
30$:            rsb                             ; End of Stm_print
;
;
;
;*******************************************************************************
;* Put_record                                                                  *
;*                                                                             *
;*      This routine simply calls SYS$PUT to send the record to the output     *
;* file.  If an error occurs, it is signaled and R0 is cleared.                *
;*                                                                             *
;* R2 - output RAB                                                             *
;*******************************************************************************
;
put_record:     $put    rab = (r2)              ; Write record to file
                blbc    r0, 10$
                rsb
;
10$:            pushl   RAB$L_STV(r2)
                pushl   RAB$L_STS(r2)
                pushab  out_ful_desc
                pushl   #1
                pushl   #TAIL_WRITE
                calls   #5, g^lib$signal
                clrl    r0
                rsb
;
;
;
                .end    tail