        TITLE TAIL - A FILTER FOR MSDOS2
        PAGE 55,132
;**********************************************************************
;       THIS PROGRAM PRINTS THE LAST FEW LINES OF A TEXT FILE.
;
;       USAGE:  TAIL -n filename
;               -n is optional, limits output to n lines (20 default)
;
;       BY: JON DART
;           DEPARTMENT OF ANTHROPOLOGY
;           UCSD C-001
;	    LA JOLLA, CA 92093
;
;	VERSION 1.7: 10-JAN-87 HANDLES AMBIG. FILE NAMES, MULTIPLE FILES
;	VERSION 1.6: 14-AUG-86 HANDLES -n UP TO 65535, NOW BUFFERS OUTPUT
;	VERSION 1.5: 06-JUL-86 CHANGES TO MEMORY ALLOC, + NOW ASSEMBLES UNDER
;		     MASM 4.0
;       VERSION 1.4: 04-FEB-86 FIXED BUG, NOW READS FROM STDIN CORRECTLY
;       VERSION 1.3: 16-JAN-86 MORE KOSHER MEMORY USAGE, NOW .EXE FILE
;       VERSION 1.2: 27-SEP-85 USES STD. INPUT IF NO FILE NAME GIVEN
;
;       CONVERTED FROM CP/M-80 TO MSDOS, 16-SEP-85
;
;       VERSION 1.1: 15-MAR-85
;       VERSION 1.0: 15-JAN-85
;
;   TO BUILD TAIL.EXE:
;	MASM TAIL,TAIL,NUL,NUL
;	LINK TAIL,TAIL,NUL,ASM -C0x300

INBUFSIZE EQU   8192                    ;SIZE OF INPUT BUFFER
OUTBUFSIZE EQU	512			;SIZE OF OUTPUT BUFFER
MAXARGS	EQU	40			;MAX. # COMMAND LINE ARGUMENTS
PRGSIZE	EQU 	800H			;MAX. SIZE OF PROGRAM + FIXED DATA

        .XLIST
        INCLUDE ASCII.DEF
        INCLUDE MSDOS2.DEF
	INCLUDE MACROS.DEF
        .LIST

ERROR   MACRO   ERRNUM                  ;SHOW ERROR MESSAGE
	MOV	AX,DATA
	MOV	DS,AX
        MOV     DX,OFFSET MSG&ERRNUM
	CALL	ERRORMSG
        ENDM

;       MEMORY DEFINITIONS:
;
DATA    SEGMENT BYTE PUBLIC
ENDTXT  DW      1 DUP (?)               ;OFFSET TO END OF TEXT IN INPUT BUFFER
NREAD   DW      1 DUP (?)               ;NUMBER OF BYTES READ
SIZEHI  DW      1 DUP (?)               ;HI WORD OF FILE SIZE
SIZELO  DW      1 DUP (?)               ;LO WORD OF FILE SIZE
POSHI	DW	1 DUP (?)		;HI WORD OF FILE POSITION
POSLO	DW	1 DUP (?)		;LO WORD OF FILE POSITION
NUMARGS	DW	1 DUP (?)		;NUMBER OF COMMAND LINE ARGUMENTS
INHANDLE DW     0                       ;INPUT FILE HANDLE
                                        ;(STD. INPUT BY DEFAULT)
NUMLIN  DW      20                      ;NUMBER OF LINES TO LIST
NUMCNT	DW	1 DUP (?)		;COUNTS NUMBER OF LINES TO GO
ONEFILE	DB	1			;FLAG, SET 0 WHEN >1 FILE
ATSTART DB	0			;FLAG, SET <>0 WHEN AT START OF FILE
AMTTOREAD DW	INBUFSIZE		;AMOUNT TO READ ON NEXT READ OP
OUTNDX	DW	0			;INDEX TO NEXT FREE SLOT IN OUTPUT BUFFER
MSG1    DB      CR,LF,"Can't open: ",0
MSG1E   DB      CR,LF,0
MSG2    DB      CR,LF,"tail: read error.",CR,LF,0
MSG3    DB      CR,LF,"tail: not enough memory for buffer",cr,lf,0
MSG4	DB	CR,LF,"tail usage: tail -n file1 file2 ...",CR,LF,0
MSG5	DB	CR,LF,"tail: too many command line arguments",CR,LF,0
MSG6	DB	CR,LF,"tail: unknown pathname ",0
DATA    ENDS

STACK   SEGMENT PARA STACK
        DB      512 DUP (?)
STACK   ENDS

;	UNINITIALIZED DATA:

INBUF	EQU	0			;INPUT BUFFER
OUTBUF	EQU	INBUF + INBUFSIZE	;OUTPUT BUFFER
PREFIX	EQU	OUTBUF + OUTBUFSIZE	;DRIVE/DIRECTORY PREFIX
SPATH	EQU	PREFIX + 65		;SEARCH PATH
FILENAME EQU	SPATH +  65		;FILE NAME
ARGPTRS EQU	FILENAME + 65		;POINTERS TO COMMAND LINE ARGUMENTS
ARGBUF	EQU	ARGPTRS + (2*MAXARGS)	;BUFFER FOR COMMAND LINE ARGUMENTS
DTA	EQU	ARGBUF + 300		;DATA TRANSFER AREA FOR DOS
MAXMEM	EQU	DTA+128

CODE    SEGMENT BYTE PUBLIC
        ASSUME  CS:CODE,DS:DATA,ES:DATA,SS:STACK
        EXTRN   SKIPSP:NEAR,DTOBIN:NEAR,CPYCNT:NEAR
        EXTRN   ERRORMSG:NEAR,TYPTX:NEAR,COUT:NEAR
	EXTRN	FIXPATH:NEAR,GETARGS:NEAR,TYPE_UFN:ABS,TYPE_UNK:ABS

ENTRY:
        TEST_DOS2                       ;TEST FOR DOS 2.0, EXIT IF DOS 1
        MOV     BX,((MAXMEM+PRGSIZE) SHR 4)+1
	MOV	AH,SET_BLOCK
	INT	DOS			;SET PROGRAM+BUFFER SIZE
	JNB	MEMOK			;IF OK
	ERROR	3			;NOT ENOUGH MEMORY
        JMP     EXIT2
MEMOK:  
	MOV     AX,DS			;GET DATA SEG
	ADD	AX,PRGSIZE		;ADD OFFSET TO BUFFER
	MOV	ES,AX			;SET EXTRA SEG TO POINT TO BUFFERS
        MOV     BX,OFFSET (80H)         ;GET BYTE COUNT FOR COMMAND LINE
        CMP     BYTE PTR [BX],0         ;TEST IT
        JNE     L_1                     ;IF NOT 0
        JMP     START			;NO ARGUMENTS, USE STDIN
L_1:
        PUSH    BX
        MOV     DL,BYTE PTR [BX]
        MOV     DH,0
        ADD     BX,DX
        INC     BX
        MOV     BYTE PTR [BX],0         ;PUT 0 BYTE AT END OF COMMAND LINE
        POP     BX
        INC     BX			;POINT TO START OF COMMAND LINE
	MOV	SI,OFFSET ARGPTRS
	MOV	DI,OFFSET ARGBUF
	MOV	CX,MAXARGS
	CALL	GETARGS			;COLLECT COMMAND LINE ARGUMENTS
	JNB	L_2			;IF OK
        ERROR   5	                ;TOO MANY ARGUMENTS
        JMP     EXIT2
L_2:
	MOV	AX,DATA
	MOV	DS,AX			;SET DATA SEG TO FIXED DATA
	MOV	WORD PTR NUMARGS,CX	;SAVE # OF ARGUMENTS
	CMP	CX,0
	JNE	L_2A			;IF SOME ARGUMENT
	JMP	START			;NO ARGUMENTS, USE STDIN
L_2A:
	MOV	SI,OFFSET ARGPTRS
	MOV	BX,WORD PTR ES:[SI]	;POINT TO 1ST ARGUMENT
	MOV	AL,BYTE PTR ES:[BX]	;GET 1ST CHAR.
        CMP     AL,'-'
        JE      L_3                     ;IF SWITCH SPECIFIED
        JMP     GETFILES
L_3:
        INC     BX
        MOV     CL,0
        PUSH    BX
CD:     MOV     AL,BYTE PTR ES:[BX]     ;HAVE NUMBER, COUNT NUMBER OF DIGITS
        CMP     AL,'0'
        JC      NODGT
        CMP     AL,'9'+1
        JNC     NODGT
        INC     BX
        INC     CL
        JMP     SHORT CD
NODGT:
        POP     BX
        MOV     CH,0                    ;CL HOLDS DIGIT COUNT
	PUSH	DS
	MOV	AX,ES
	MOV	DS,AX
        CALL    DTOBIN                  ;CONVERT NUMBER TO BINARY
	POP	DS
        JNC     L_4                     ;IF OK
	JMP	SHORT USE
L_4:
        CMP     AX,0                    ;IS NUMBER 0?
        JNE     NOT0                    ;NO.
        JMP     EXIT2                   ;YES, IT IS, JUST EXIT
NOT0:
        MOV     WORD PTR NUMLIN,AX      ;STORE # LINES
	JMP	SHORT GETFILES
USE:
        ERROR   4	                ;TELL USER HOW TO USE
        JMP     EXIT2
START:	
	CALL	TAIL
	JMP	EXIT2
GETFILES:
	MOV	SI,OFFSET ARGPTRS
	MOV	CX,WORD PTR NUMARGS	;GET # OF ARGUMENTS
	CMP	CX,1			;>1 ARG?
	JE	ONLY1			;NOPE
	MOV	BX,WORD PTR ES:[SI]	;ES:BX POINTS TO FILE NAME
	CMP	BYTE PTR ES:[BX],'-'	;SWITCH SPECIFIED?
	JE	ARGSW			;YES
	MOV	BYTE PTR ONEFILE,0	;>1 ARG AND NO SWITCH
	JMP	SHORT ARGLUP
ARGSW:	CMP	CX,2			;SWITCH SPECIFIED AND >2 ARGS?
	JLE	ONLY1			;NO
	MOV	BYTE PTR ONEFILE,0	;CLEAR "ONE FILE" FLAG
ONLY1:
ARGLUP:	MOV	BX,WORD PTR ES:[SI]	;ES:BX POINTS TO FILE NAME
	CMP	BYTE PTR ES:[BX],'-'	;SWITCH?
	JE	SKIPSW			;SKIP IT
	PUSH	CX
	PUSH	SI
	CALL	DOARG
	POP	SI
	POP	CX
SKIPSW:
	ADD	SI,2
	LOOP	ARGLUP
	JMP	EXIT2	

;	DOARG = PROCESS 1 ARGUMENT
;	ES:BX POINTS TO IT

DOARG	PROC 	NEAR
	PUSH	DS			;SAVE DATA SEG.
	MOV	AX,ES
	MOV	DS,AX			;SET DATA SEG TO BUFFER SEG
	MOV	AH,SET_DTA
	MOV	DX,OFFSET DTA
	INT	DOS			;SET DTA
        MOV	CX,OFFSET SPATH
        MOV	DX,OFFSET PREFIX
        PUSH    BX
        CALL    FIXPATH                 ;PARSE PATHNAME
	POP	BX
	POP	DS
	CMP	AL,TYPE_UNK
	JE	BADNAME
	CMP	AL,TYPE_UFN
	JE	GOTNAME			;IF UNAMBIG. FILE NAME
	MOV	BYTE PTR ONEFILE,0	;AMBIG. FILE NAME OR DIR, FLAG IT
	JMP	GOTNAME
BADNAME:
	MOV	DX,OFFSET MSG6
	CALL	ERRORMSG		;BAD FILE NAME, SHOW MSG.
	PUSH	DS
	MOV	AX,ES
	MOV	DS,AX
	MOV	DX,BX
	CALL	ERRORMSG		;SHOW FILE THAT CAUSED IT
	POP	DS
	RET
GOTNAME:
	PUSH	DS			;SAVE DATA SEG.
	MOV	AX,ES
	MOV	DS,AX			;SET DATA SEG TO BUFFER SEG
        MOV     DX,OFFSET SPATH         ;POINT TO SEARCH PATH
        MOV     CX,31H                  ;SET SEARCH ATTRIBUTES
        MOV     AH,FIND_FIRST
        INT     DOS                     ;SEARCH FOR 1ST MATCH
	POP	DS
	JB	BADNAME			;IF NOTHING FOUND
GOTFILE:
	PUSH	DS			;SAVE DATA SEG.
	MOV	AX,ES
	MOV	DS,AX			;SET DATA SEG TO BUFFER SEG
	MOV	SI,OFFSET PREFIX
	MOV	DI,OFFSET FILENAME
	MOV	CX,65
	CALL	CPYCNT			;COPY PREFIX TO FILE NAME AREA
        DEC     DI                      ;BACK UP OVER NULL
        MOV     SI,OFFSET (DTA+30)      ;POINT TO FILE NAME WE FOUND
        CMP     [SI],BYTE PTR '.'       ;DOES IT START WITH .? -
        JNE     NOTDOT                  ;- NO
	POP	DS
	JMP	SHORT SKIPFILE		;YES, JUST SKIP IT
NOTDOT:
	CALL	CPYCNT			;COPY FILE WE FOUND AFTER PREFIX
	POP	DS
	CALL	DOFILE			;DO 1 FILE
SKIPFILE:
        MOV     AH,FIND_NEXT
        INT     DOS                     ;FIND NEXT MATCH, IF ANY
        JC      NOMORE                  ;IF NONE
        JMP     GOTFILE                 ;GOT ONE, BACK TO TOP OF LOOP
NOMORE:
	RET
DOARG	ENDP

DOFILE  PROC	NEAR
	PUSH	DS
	MOV	AX,ES
	MOV	DS,AX
        MOV     DX,OFFSET FILENAME      ;DX POINTS TO FILE NAME
        MOV     AL,READ_ACCESS
        MOV     AH,DOS2_OPEN
        INT     DOS                     ;TRY TO OPEN FILE
	POP	DS
	JB	BADOPN			;IF ERROR
	MOV	WORD PTR INHANDLE,AX	;SAVE FILE HANDLE
	CMP	BYTE PTR ONEFILE,1	;ONE FILE ONLY?
	JE	NOSHOW			;YES, DON'T SHOW NAME
	CALL	SHOWFILE		;SHOW FILE NAME
NOSHOW:
	CALL	TAIL			;SHOW TAIL OF FILE
	RET
BADOPN:
        MOV     DX,OFFSET MSG1
        CALL    ERRORMSG		;CAN'T OPEN FILE
	PUSH	DS
	MOV	AX,ES
	MOV	DS,AX
	MOV	DX,OFFSET FILENAME
	CALL	ERRORMSG		;SHOW NAME
	POP	DS
        MOV     DX,OFFSET MSG1E
        CALL    ERRORMSG		;SHOW CR/LF
	RET
DOFILE	ENDP

;	SHOW FILE NAME

SHOWFILE PROC	NEAR
	MOV	AX,"="
	MOV	CX,5
BARS:
	CALL	WRITEBYTE
	LOOP	BARS
	MOV	AX,SPACE
	CALL	WRITEBYTE
	MOV	BX,OFFSET FILENAME
SHOWF:	MOV	AL,BYTE PTR ES:[BX]
	CMP	AL,0
	JE	ENDF
	CALL	WRITEBYTE
	INC	BX
	JMP	SHOWF	
ENDF:	MOV	AL,SPACE
	CALL	WRITEBYTE
	MOV	AL,'='
	MOV	CX,5
BARS2:
	CALL	WRITEBYTE
	LOOP	BARS2
	MOV	AL,CR
	CALL	WRITEBYTE
	MOV	AL,LF
	CALL	WRITEBYTE
	RET
SHOWFILE ENDP

;	AFTER A FILE HAS BEEN OPENED, THIS PROCEDURE OUTPUTS ITS "TAIL"

TAIL	PROC	NEAR
	MOV     AX,DATA
        MOV     DS,AX                   ;SET DATA SEG TO OUR DATA
	MOV	BYTE PTR ATSTART,0	;CLEAR "AT START" FLAG
	MOV	WORD PTR AMTTOREAD,INBUFSIZE  ;SET INITIAL AMOUNT TO READ
	MOV	AX,WORD PTR NUMLIN	;GET # LINES TO SHOW
	MOV	WORD PTR NUMCNT,AX	;INIT COUNT
        MOV     BX,WORD PTR INHANDLE    ;GET FILE HANDLE
        MOV     DX,0
        MOV     CX,0
        MOV     AL,2
        MOV     AH,LSEEK
        INT     DOS                     ;SEEK TO END OF FILE
        MOV     WORD PTR SIZEHI,DX
        MOV     WORD PTR SIZELO,AX      ;SAVE FILE SIZE
        CMP     DX,0
        JNE     BIG                     ;IF HI WORD >0
        CMP     AX,0
        JNE     L_11
        JMP     CLOSEFILE               ;IF FILE SIZE 0, JUST EXIT
L_11:
        CMP     AX,INBUFSIZE
        JC      SMALL                   ;IF FILE SIZE < BUFFER SIZE
BIG:
        SUB     AX,INBUFSIZE
        JNB     NOBORROW
        DEC     DX
NOBORROW:
        MOV     CX,DX                   ;CX = MSW OF OFFSET
        MOV     DX,AX                   ;DX = LSW OF OFFSET
        MOV     BX,WORD PTR INHANDLE
        MOV     AL,0
        MOV     AH,LSEEK
        INT     DOS                     ;MOVE FILE POINTER BACK BY BUFSIZ
	MOV	WORD PTR POSHI,DX	;SAVE HI WORD OF POSITION
	MOV	WORD PTR POSLO,AX	;SAVE LO WORD OF POSITION
        JMP     FILLBUF
SMALL:
        MOV     CX,0
        MOV     DX,0
        MOV     AL,0
        MOV     BX,WORD PTR INHANDLE
        MOV     AH,LSEEK
        INT     DOS                     ;REWIND TO START OF FILE
	MOV	BYTE PTR ATSTART,1	;SET "AT START" FLAG
FILLBUF:
        MOV     CX,WORD PTR AMTTOREAD   ;CX = # BYTES TO READ
        MOV     BX,WORD PTR INHANDLE    ;BX = FILE HANDLE
        PUSH    DS
        MOV     AX,ES
        MOV     DS,AX                   ;MAKE DATA SEG POINT TO BUFFER
        MOV     DX,OFFSET INBUF         ;OFFSET TO BUFFER FROM ES
        MOV     AH,READ
        INT     DOS                     ;FILL BUFFER FROM FILE
        POP     DS                      ;RESTORE DATA SEG.
        JNB	READOK			;IF NO ERROR
	JMP	BADREAD			;IF READ ERROR
READOK:
        MOV     WORD PTR NREAD,AX       ;SAVE # BYTES READ
        MOV     CX,AX
        MOV     BX,0
SRCEND:
        MOV     AL,BYTE PTR ES:[BX]
        CMP     AL,CTRL$Z
        JE      FOUND                   ;IF ^Z FOUND
        INC     BX
        LOOP    SRCEND
FOUND:
        MOV     WORD PTR ENDTXT,BX      ;SAVE ADDRESS OF END OF TEXT
                                        ;(ACTUALLY, OFFSET FR. BUFFER SEGMENT)
	JMP	SHORT SRC1
SRC:	DEC	BX
SRC1:
        CMP     BX,0                    ;AT START OF BUFFER?
        JE      NEWBUF			;YES, BACK UP FURTHER (IF POSSIBLE)
        MOV     AL,BYTE PTR ES:[BX]     ;GET BYTE
        CMP     AL,CR			;IF NOT CR,
	JNE	SRC			;LOOP

	MOV	AX,1			;FOUND CR,
	SUB	WORD PTR NUMCNT,AX	;COUNT 1 LINE
	JNB	SRC			;LOOP IF COUNT STILL >0
        ADD	BX,2                    ;START OF RIGHT LINE FOUND, SKIP CR/LF
	JMP	BEGIN			;BEGIN OUTPUT

NEWBUF: CMP	BYTE PTR ATSTART,0	;SEE IF WE CAN BACK UP MORE
	JNE	BEGIN			;NO, WE CAN'T
	MOV	AX,WORD PTR POSLO	;GET LO WORD OF FILE POSITION
	SUB	AX,OFFSET INBUFSIZE	;SUBTRACT BUFFER SIZE
	MOV	BX,WORD PTR POSHI	;GET HI WORD OF FILE POSITION
	SBB	BX,0			;IF 'C' FLAG SET, SUBTRACT 1
	JB	TOOFAR			;IF <1 BUFFER LEFT TO GO
	MOV	CX,BX
	MOV	DX,AX			;OFFSET IN CX:DX
	MOV	BX,WORD PTR INHANDLE
	MOV	AL,0
	MOV	AH,LSEEK
	INT	DOS			;MOVE FILE POINTER BACK
	MOV	WORD PTR POSHI,DX
	MOV	WORD PTR POSLO,AX	;SAVE NEW POSITION	
	JMP	FILLBUF			;GO FILL BUFFER AGAIN

;	COME HERE WHEN <1 FULL BUFFER REMAINS TO BE READ

TOOFAR:	MOV	AX,WORD PTR POSLO
	MOV	WORD PTR AMTTOREAD,AX	;SET AMT TO READ = AMOUNT UNREAD
	MOV	CX,0	
	MOV	DX,0			;MOVE TO START OF FILE
	MOV	BX,WORD PTR INHANDLE
	MOV	AL,0
	MOV	AH,LSEEK
	INT	DOS			;SEEK TO START
	MOV	BYTE PTR ATSTART,1	;SET "AT START" FLAG
	JMP	FILLBUF			;GO FILL BUFFER

;	READY TO OUTPUT LINE(S)

BEGIN:
        MOV     DX,WORD PTR ENDTXT      ;GET END OF TEXT IN DX
SHOW:   CMP     DX,BX                   ;DISPLAY TEXT - TOP OF LOOP
        JE	ENDBUF 			;UNTIL END OF BUFFER
        MOV     AL,BYTE PTR ES:[BX]
        CALL    WRITEBYTE
	CMP	AL,CTRL$Z		;QUIT IF ^Z WRITTEN
	JE	DONE
        INC     BX
        JMP     SHOW
ENDBUF:	
        MOV     CX,INBUFSIZE   		;CX = # BYTES TO READ
        MOV     BX,WORD PTR INHANDLE    ;BX = FILE HANDLE
        PUSH    DS
        MOV     AX,ES
        MOV     DS,AX                   ;MAKE DATA SEG POINT TO BUFFER
        MOV     DX,0                    ;OFFSET TO BUFFER FROM ES
        MOV     AH,READ
        INT     DOS                     ;FILL BUFFER FROM FILE
        POP     DS                      ;RESTORE DATA SEG.
        JB	BADREAD			;IF ERROR
	MOV	DX,AX			;SAVE BYTES READ
	CMP	AX,0
	JE	DONE			;DONE IF 0
	MOV	BX,0			;BX = OFFSET TO START OF BUFFER
	JMP	SHOW			;OUTPUT SOME MORE

BADREAD:
        MOV     DX,OFFSET MSG2
        CALL    ERRORMSG
DONE:
CLOSEFILE:
        MOV     BX,WORD PTR INHANDLE
        MOV     AH,DOS2_CLOSE
        INT     DOS                     ;CLOSE FILE
	CALL	FLUSH			;FLUSH ANYTHING IN OUTPUT BUFFER
	RET
TAIL	ENDP

EXIT2:
        MOV     AH,EXIT
        INT     DOS                     ;EXIT TO DOS

; 	WRITE A BYTE TO THE OUTPUT
;
WRITEBYTE PROC 	NEAR
	PUSH	BX
	PUSH	CX
	PUSH	DX
	MOV	BX,WORD PTR OUTNDX
	MOV	BYTE PTR ES:[BX+OUTBUF],AL
	INC	BX
	MOV	WORD PTR OUTNDX,BX
	CMP	BX,OUTBUFSIZE
	JL	NOTFUL
	CALL	FLUSH
NOTFUL:
	POP	DX
	POP	CX
	POP	BX
	RET
WRITEBYTE ENDP

;	FLUSH - WRITE OUTPUT BUFFER 

FLUSH	PROC	NEAR
	PUSH	DS
	PUSH	AX
	MOV	CX,WORD PTR OUTNDX
	MOV	AX,ES
	MOV	DS,AX
	MOV	DX,OFFSET OUTBUF
	MOV	BX,1
	MOV	AH,WRITE
	INT	DOS		;WRITE TO OUTPUT
	POP	AX
	POP	DS
	MOV	WORD PTR OUTNDX,0
	RET
FLUSH	ENDP

CODE    ENDS
        END     ENTRY

