        TITLE HEAD - A FILTER FOR MSDOS2
        PAGE 55,132
;******************************************************************************
;       THIS PROGRAM PRINTS THE FIRST FEW LINES OF A TEXT FILE.
;
;       IT READS FROM A FILE IF SPECIFIED, OR FROM THE STANDARD INPUT OTHERWISE
;       IT WRITES TO THE STANDARD OUTPUT.
;
;       USAGE:  HEAD -n file1 file2 ...
;               -n is optional, limits output to n lines (20 default)
;		file names can be ambiguous
;
;       BY: JON DART
;           DEPARTMENT OF ANTHROPOLOGY
;           UCSD C-001
;	    LA JOLLA, CA 92093
;
;	VERSION 1.7, 21-DEC-86: now supports wildcards, multiple files
;	VERSION 1.6, 06-SEP-86: minor cleanup, fixed bug involving failure
;				to set data segment when reading from stdin.
;	VERSION 1.5, 13-AUG-86: handles arbitrary -n (up to 65535)
;				also now buffers output, doesn't expand tabs
;	VERSION 1.4, 06-JUL-86: changes to memory allocation, now assembles
;				under MASM 4.0
;       VERSION 1,3, 04-FEB-86: fixed bug in reading from std. input
;       VERSION 1.2, 16-JAN-86: makes UTIL and CONSOLE external modules
;       VERSION 1.1, 27-SEP-85: defaults to std. input if no file given
;       VERSION 1.0, 19-SEP-85
;
;   TO BUILD HEAD EXE:
;	MASM HEAD,HEAD,NUL,NUL
;	LINK HEAD,HEAD,NUL,ASM -C0x400

INBUFSIZE EQU   8192                    ;SIZE OF INPUT BUFFER
OUTBUFSIZE EQU	512			;SIZE OF OUTPUT BUFFER
MAXARGS EQU	40			;MAX. # CMD. LINE ARGUMENTS
PRGSIZE EQU	1000H			;MAX. SIZE OF CODE + FIXED DATA
M       EQU     BYTE PTR 0[BX]

        .XLIST
        INCLUDE ASCII.DEF
        INCLUDE MSDOS2.DEF
	INCLUDE MACROS.DEF
        .LIST

;       MEMORY DEFINITIONS:
;
DATA    SEGMENT BYTE PUBLIC
INHANDLE DW     0                       ;INPUT FILE HANDLE
                                        ;(DEFAULTS TO STD. INPUT)
NUMARGS DW	1			;NUMBER OF COMMAND LINE ARGUMENTS
OUTNDX	DW	0			;OFFSET TO NEXT SLOT IN OUTPUT BUFFER
NUMLIN  DW      20                      ;NUMBER OF LINES TO LIST
ONEFILE	DB	1			;SET 1 IF ONE UNAMBIG. ARGUMENT		
MSG1    DB      CR,LF,"head: can't open "
MSG1E   DB      CR,LF,0
MSG2    DB      CR,LF,"head: read error on file ",0
MSG3    DB      CR,LF,"head: not enough memory for buffer.",CR,LF,0
MSG4	DB	CR,LF,"head usage: head -n filename",CR,LF,0
MSG5	DB	CR,LF,"head: too many command line arguments",CR,LF,0
MSG6	DB	CR,LF,"head: unknown pathname ",0
DATA    ENDS
        ASSUME  DS:DATA,ES:DATA

;	UNINITIALIZED DATA SPACE:

INBUF   EQU     0                       ;OFFSET OF INPUT BUFFER FROM ES
OUTBUF	EQU	INBUF +  INBUFSIZE	;OUTPUT BUFFER
ARGPTRS EQU	OUTBUF + OUTBUFSIZE	;POINTERS TO ARGUMENTS
ARGBUF  EQU	ARGPTRS+(2*MAXARGS)	;ARGUMENT BUFFER	
SPATH	EQU	ARGBUF +  512		;SEARCH PATH
PREFIX	EQU	SPATH + 65		;SEARCH PREFIX
FILENAME EQU	PREFIX + 65		;COMPOSITE FILE NAME (PREFIX + NAME)
DTA	EQU	FILENAME + 65		;DTA  AREA
MAXMEM	EQU	DTA + 128		;MAX. MEMORY USED

STACK   SEGMENT PARA STACK
        DB 512 DUP (?)
STACK   ENDS
        ASSUME  SS:STACK

CODE    SEGMENT BYTE PUBLIC
        ASSUME  CS:CODE
        EXTRN   COUT:NEAR,ERRORMSG:NEAR
        EXTRN   SKIPSP:NEAR,DTOBIN:NEAR,CPYCNT:NEAR,TYPTX:NEAR
	EXTRN	FIXPATH:NEAR,GETARGS:NEAR
	EXTRN	TYPE_UNK:ABS,TYPE_UFN: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
        MOV     AX,DATA
        MOV     DS,AX
        MOV     DX,OFFSET MSG3
        CALL    ERRORMSG                ;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
        MOV     AX,DATA
        MOV     DS,AX
        MOV     DX,OFFSET MSG5
        CALL    ERRORMSG                ;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:
        MOV     AX,DATA
        MOV     DS,AX
        MOV     DX,OFFSET MSG4
        CALL    ERRORMSG                ;TELL USER HOW TO USE
        JMP     EXIT2
START:	
	CALL	HEAD
	JMP	EXIT2
GETFILES:
	MOV	SI,OFFSET ARGPTRS
	MOV	CX,WORD PTR NUMARGS
	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
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
NONUM:
	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 = PROCESS 1 FILE (NAME IN ES:FILENAME)

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
        JNC     NAMEOK                  ;IF OK
        JMP     SHORT BADOPN
NAMEOK:
        MOV     WORD PTR INHANDLE,AX    ;SAVE FILE HANDLE
	CMP	BYTE PTR ONEFILE,1
	JE	NOSHOW
	CALL	SHOWFILE		;SHOW FILE NAME (ONLY IF >1 SPECIFIED)
NOSHOW:
	CALL	HEAD			;SHOW HEAD OF FILE
	RET
BADOPN:
        MOV     DX,OFFSET MSG1
        CALL    ERRORMSG                ;BAD OPEN, SHOW MSG.
	PUSH	DS
	MOV	AX,ES
	MOV	DS,AX
        MOV     DX,OFFSET FILENAME      ;DX POINTS TO FILE NAME
        CALL    ERRORMSG                ;SHOW IT
	POP	DS
	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

HEAD	PROC	NEAR
	MOV	AX,DATA
	MOV	DS,AX
	MOV	CX,WORD PTR NUMLIN
NEWBUF:
	CALL	FILLBUF			;FILL INPUT BUFFER
	JNB	BEGIN			;IF OK
	JMP	BADREAD
BEGIN:
	CMP	BX,DX			;SEE IF ANYTHING READ 
	JE	CLOSEFILE		;NO, ASSUME EOF

SHOW:   CMP     BX,DX                   ;DISPLAY TEXT - TOP OF LOOP
        JGE     NEWBUF			;IF END OF BUFFER
        MOV     AL,BYTE PTR ES:[BX]	;GET CHAR. FROM BUFFER
        INC     BX			;BUMP POINTER
	CALL	WRITEBYTE		;WRITE CHAR. TO STDOUT
        CMP     AL,CTRL$Z		;TEST FOR ^Z
	JE	CLOSEFILE		;IF FOUND ^Z
        CMP     AL,CR                   ;IS CR?
        JNE     SHOW                    ;NOPE
        LOOP    SHOW                    ;YES, COUNT A LINE

;	LINE COUNT REACHED

	CMP	BX,DX			;EXACTLY AT END OF BUFFER?
	JNE	NOTEND			;NO
	CALL	FILLBUF			;YES, JUST OUR LUCK, FILL IT AGAIN
	JNB	NOTEND
	JMP	BADREAD			;IF ERROR
NOTEND:
        MOV     AL,BYTE PTR ES:[BX]	;GET CHAR. AFTER LAST CR
        CMP     AL,LF
        JNE     CLOSEFILE
        CALL    WRITEBYTE               ;SHOW LF IF AFTER LAST CR
	JMP	CLOSEFILE
BADREAD:
        MOV     DX,OFFSET MSG2		;SHOW ERROR
        CALL    ERRORMSG
	PUSH	DS
	MOV	AX,ES
	MOV	DS,AX
	MOV	DX,OFFSET FILENAME	
	CALL	ERRORMSG		;AND FILENAME
	POP	DS
	MOV	DX,OFFSET MSG1E
	CALL	ERRORMSG		;AND CR/LF
CLOSEFILE:
        MOV     BX,WORD PTR INHANDLE
        MOV     AH,DOS2_CLOSE
        INT     DOS                     ;CLOSE INPUT FILE
EXIT1:
	CALL	FLUSH			;CLEAR OUTPUT BUFFER
	RET
HEAD	ENDP

;	FILL BUFFER FROM DISK - RETURN 'C'=1 IF ERROR, BX = POINTER
;	TO START OF BUFFER, DX = POINTER TO LAST CHAR + 1

FILLBUF PROC	NEAR
	PUSH	CX			;SAVE LINE COUNT
        MOV     AX,DATA
        MOV     DS,AX                   ;MAKE SURE DATA SEG. IS SET
        MOV     CX,INBUFSIZE            ;CX = # BYTES TO READ
        MOV     BX,WORD PTR INHANDLE    ;BX = FILE HANDLE
        PUSH    DS
        MOV     AX,ES
        MOV     DS,AX                   ;SET DATA SEG TO WHERE BUFFER IS
        MOV     DX,OFFSET INBUF
        MOV     AH,READ
        INT     DOS                     ;FILL BUFFER FROM FILE
        POP     DS                      ;RESET DATA SEG
	POP	CX			;RESTORE LINE COUNT
        JC      BAD                     ;IF ERROR
        MOV     BX,OFFSET INBUF
        ADD     AX,BX                   ;COMPUTE ADDR. OF LAST BYTE
        MOV     DX,AX                   ;SAVE IN DX
	CLC
BAD:
	RET
FILLBUF ENDP

; 	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,OFFSET 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

EXIT2:
        MOV     AH,EXIT
        INT     DOS                     ;EXIT TO DOS

CODE    ENDS
        END     ENTRY



