;**************************************************************************
;
; NAME		: Du v2.5
;
; TEMPLATE	: Du <dir/filename>,... [FILES] [DIRS] [NOHEAD] [NOTOTAL]
;
; DESCRIPTION	: print disk usage for a given set of directories/files.
;		  If no directory specified then use current dir.
;
; AUTHOR	: Stuart Mitchell
;		  (email: stuart@minster.york.ac.uk)
;
; DATE		: 12-Jul-92
;
; NOTES 	: v1.0 - Initial coding
;		  v2.0 - Accepts wildcards and files/dirs switches
;		  v2.1 - Code tidied up
;		  v2.2 - New tabular output
;		  v2.3 - Can now be made resident (uses the LINK
;			   instruction to grab workspace).
;			 Prints totals if more than 1 arg or
;			   an argument contains a wildcard.
;		  v2.4 - If Lock on current dir is NULL then
;			   uses "sys:"
;                 v2.5 - Now uses DOS 2.0 functions instead of ARP
;                        Added NOHEAD and NOTOTAL switches
;			 Added WB2.0 version string
;
;**************************************************************************


		section du,code

		include "dos/dos.i"
		include "dos/dosextens.i"
		include "dos/dosasl.i"
		include "exec/memory.i"
		include "exec/execbase.i"

_LVOOpenLibrary 	equ	-$228
_LVOCloseLibrary	equ	-$19e
_LVOFreeMem		equ	-$d2
_LVOAllocMem		equ	-$c6

_LVOAllocDosObject	equ	-$e4
_LVOReadArgs		equ	-$31e
_LVOFreeArgs		equ	-$35a
_LVOPutStr		equ	-$3b4
_LVOMatchFirst		equ	-$336
_LVOMatchNext		equ	-$33c
_LVOMatchEnd		equ	-$342
_LVOVPrintf		equ	-$3ba
_LVOPrintFault		equ	-$1da
_LVOGetCurrentDirName	equ	-$234

CALL		macro	(name)
		jsr	_LVO\1(a6)
		endm

r_current_arg	equr	a2
r_anchor	equr	a3
r_dosbase	equr	a4
r_var_base	equr	a5

r_files		equr	d2
r_dirs		equr	d3	; note multiple use
r_options	equr	d3	; this IS OK
r_bytes		equr	d4
r_blocks	equr	d5
r_total_flag	equr	d6
r_rdaargs       equr	d7

;; bitdefs for dir scan - should really be picked up from dosasl.i ...

DIDDIR		equ	3
DODIR		equ	2
ITSWILD		equ	1

;; define offsets into space reserved on the stack by link instruction

LINK_SIZE	equ	56			; size of reserved area

;; running totals

TFILES		equ	-56			; must be in this order!
TDIRS		equ	-52
TBYTES		equ	-48
TBLOCKS		equ	-44

;; arguments for VPrintf()

P_ARG0		equ	-40			; must be in this order
P_ARG1		equ	-36			;
P_ARG2		equ	-32			;
P_ARG3		equ	-28			;
P_ARG4		equ	-24			;

;; vars returned by call to ReadArgs()

ARGS		equ	-20			; char *args[]
DIR_FLAG	equ	-16
FILE_FLAG	equ	-12
HEADER_FLAG	equ	-8
TOTAL_FLAG	equ	-4

;; *********************************************************************

start:		link	r_var_base,#-LINK_SIZE	; reserve 56 bytes on stack

;; open DOS library

open_lib:	move.l	(4).w,a6
		lea	doslib(pc),a1
		moveq.l	#37,d0			; 2.04 +
		CALL	OpenLibrary
		tst.l	d0
		beq	exit
		move.l	d0,a6
		move.l	d0,r_dosbase

;; clear argument array and global vars

                moveq.l	#(LINK_SIZE-4)/4,d0	; loop count
		moveq.l #0,d3
		lea	-LINK_SIZE(r_var_base),a0
clr_loop:       move.l  d3,(a0)+
                dbra	d0,clr_loop

;; parse command line
;;   (d3 already set to NULL)

parse_args:     lea	ARGS(r_var_base),a0	; pointer to arg array
		move.l	a0,d2
		lea	tplate(pc),a0
		move.l	a0,d1
		CALL    ReadArgs

		move.l	d0,r_rdaargs
		beq	closedos

;; output header line

		tst.l	HEADER_FLAG(r_var_base)
		bne.s   1$			; skip if flag specified

		lea	header_bar(pc),a0
		move.l	a0,d1
		CALL	PutStr

;; total flag is incremented for each arg, thus if more than one arg
;; is present then total flag will end up non-zero...

1$		moveq.l	#-1,r_total_flag

		moveq.l	#0,r_options
set_file_flag:	move.l	FILE_FLAG(r_var_base),d0
		beq.s	set_dir_flag		; if zero then option not specified
		moveq.l	#-1,r_options
set_dir_flag:	move.l	DIR_FLAG(r_var_base),d0
		beq.s   test_num_args
		moveq.l	#1,r_options

;; test if any directories/files specified on the command line - if none
;; then do current directory

test_num_args:	tst.l	ARGS(r_var_base)
		beq	do_curr_dir

;; allocate memory for AnchorPath structure

		bsr	alloc_anchor

;; main loop -
;;   for each arg:
;;   while (matching entry found)
;;     if (file)
;;       print info;
;;     else
;;       scan dir & print info;

		move.l  ARGS(r_var_base),r_current_arg
main_loop:	move.l	(r_current_arg)+,d1
		beq	print_total		; no args left (last arg NULL)

		addq.l	#1,r_total_flag		; increment totals count

findfirst:	move.l	r_anchor,d2
		CALL	MatchFirst
		tst.l	d0
		bne	does_not_exist

;; if arg is wildcard then force totals at the end
;; ( ITSWILD bit is set by the call to MatchFirst )

		move.b	ap_Flags(r_anchor),d0
		btst	#ITSWILD,d0		; bit's status --> Z flag
		beq.s	inner_loop		; if bit clear then continue

                moveq.l	#1,r_total_flag 	; make sure totals are printed

inner_loop:	move.l	fib_DirEntryType+ap_Info(r_anchor),d0
		blt.s	do_file

do_dir:         cmp.l	#ST_LINKDIR,d0		; if this a (hard) link
		beq.s	skip			; if so ignore it
		cmp.l	#ST_SOFTLINK,d0		; soft link (file OR dir)
		beq.s	skip

		tst.l	r_options		; if r_options is negative
		blt.s	skip			;   then files only required

		lea	ap_Buf(r_anchor),a0
		bsr	du
		tst.l	d0
		bne.s	break_pressed		; error - break pressed

		bra.s	skip

do_file:        cmp.l   #ST_LINKFILE,d0		; file link?
		beq.s	skip

		tst.l	r_options		; if r_options is positive
		bgt.s	skip			;   then only dirs to be shown

		move.l	fib_Size+ap_Info(r_anchor),d0
		move.l	d0,P_ARG0(r_var_base)
		add.l	d0,TBYTES(r_var_base)

		move.l	fib_NumBlocks+ap_Info(r_anchor),d0
		addq.l	#1,d0			; correct block count to
		move.l	d0,P_ARG1(r_var_base)	;   include file header block
		add.l	d0,TBLOCKS(r_var_base)

		lea	fib_FileName+ap_Info(r_anchor),a0
		move.l	a0,P_ARG2(r_var_base)

		lea	P_ARG0(r_var_base),a0
		move.l	a0,d2
		lea	file_fmt(pc),a0
		move.l	a0,d1
		CALL	VPrintf

skip:		move.l	r_anchor,d1
		CALL	MatchNext
		cmpi.l	#ERROR_BREAK,d0
		beq.s	break_pressed
		tst.l	d0
		beq.s	inner_loop

		bra	main_loop

print_total:	tst.l	r_total_flag		; only print totals if
		ble.s	free_rda		;   flag is >0
		tst.l   TOTAL_FLAG(r_var_base)	; skip totals if NOTOTAL flag
		bne.s	free_rda		;   specified on command line

		lea	total_fmt(pc),a0
		move.l	a0,d1
		lea     TFILES(r_var_base),a0
		move.l	a0,d2
		CALL	VPrintf
		bra.s   free_rda

;; ********************************************************************
;; Output "**BREAK" - Called if CNTL-C detected
;;     Note: drop through to end of program

break_pressed:  move.l	#break_str,d1
		CALL	PutStr

;; ********************************************************************

free_rda:       move.l	r_rdaargs,d1
		CALL	FreeArgs

		bsr	free_asl

closedos:	move.l	r_dosbase,a1
		move.l	(4).w,a6
		CALL	CloseLibrary

exit:		unlk	r_var_base
		moveq.l	#0,d0
		rts

;; **********************************************************************
;; Print error message if object does not exist
;;
;; IN:	d0 - error code from MatchFirst() call (i.e. IoErr() code)
;; 	a6 - DosBase
;; OUT:	<none>

does_not_exist:	move.l	d0,d1
		move.l	-4(r_current_arg),d2	; r_current_arg has been advanced already
		CALL	PrintFault
		bra	main_loop		; try next arg...

;; **********************************************************************
;; Scan directory and print info on directory
;;
;; IN:	a0 - full pathname of required directory
;; 	r_var_base - pointer to top of allocated workspace
;;	a6 - DosBase
;; OUT:	d0 - 0 on success, 1 if interrupted by ^C

du:		movem.l	d2-d7/a2-a4,-(sp)
		move.l	a0,a2
		move.l	a0,P_ARG4(r_var_base)	; save name to print later

		bsr	alloc_anchor
		move.l	r_anchor,d2
		beq     exit_du			; no memory

;; main loop

;; find first file/directory

		move.l	a2,d1			; move pathname
		CALL	MatchFirst
		tst.l	d0
		bne.s   finished		; should never happen...

init_regs:	moveq.l	#0,r_files		; zero results
		moveq.l	#0,r_dirs
		moveq.l	#0,r_bytes
		moveq.l	#0,r_blocks

;; loop around in directory until all subdirectories/files exhausted

du_loop:	move.b	ap_Flags(r_anchor),d1

;; if DirEntryType <0 then we have a file

		move.l	fib_DirEntryType+ap_Info(r_anchor),d0
		blt.s	du_do_file

du_do_dir:	btst	#DIDDIR,d1		; DIDDIR bit set
		beq.s	du_enter_dir		; no ... enter dir

;; have just popped out of a directory, mark as finished & increment count

du_exit_dir:	bclr	#DIDDIR,d1

		addq.l	#1,r_dirs		; bump dir count
		bra.s	du_find_next

;; found a new directory, so enter it at next FindNext

du_enter_dir:	bset	#DODIR,d1
		bra.s	du_find_next

;; this ones a file, so increment count & bytes

du_do_file:	addq.l	#1,r_files
		add.l	fib_Size+ap_Info(r_anchor),r_bytes
		add.l	fib_NumBlocks+ap_Info(r_anchor),r_blocks
		addq.l	#1,r_blocks		; take account of file header block

;; find next file/dir

du_find_next:	move.b	d1,ap_Flags(r_anchor)	; set directory handling

		move.l	r_anchor,d1
		CALL	MatchNext
		cmpi.l	#ERROR_BREAK,d0		; ^C pressed ?
		beq.s	du_break
		tst.l	d0      		; if zero then got something
		beq.s	du_loop

;; print out results (dirname already put in place at start of subroutine)

print_dir:	movem.l r_files/r_dirs/r_bytes/r_blocks,P_ARG0(r_var_base)	 ; flush results

		add.l	r_bytes,TBYTES(r_var_base)
		add.l	r_files,TFILES(r_var_base)
		add.l	r_blocks,TBLOCKS(r_var_base)
		add.l	r_dirs,TDIRS(r_var_base)

		lea	dir_fmt(pc),a0		; format string
		move.l	a0,d1
		lea	P_ARG0(r_var_base),a0	; address of ptrs to args
		move.l	a0,d2
		CALL	VPrintf			; print results

finished:	bsr.s	free_asl
		moveq.l	#0,d0			; set error code
exit_du:	movem.l	(sp)+,d2-d7/a2-a4
		rts

du_break:	bsr.s	free_asl
		moveq.l	#1,d0			; error return code
		bra.s	exit_du

;; **********************************************************************
;; Allocate an ASL AnchorPath structure & extra string storage space
;;
;; IN:	<none>
;; OUT:	d0 - pointer to anchorpath structure, or NULL on failiure

alloc_anchor:	move.l	a6,-(sp)

		move.l	#ap_SIZEOF+256,d0       ; allow room for full pathname
		moveq.l	#MEMF_PUBLIC,d1
		move.l	(4).w,a6
		CALL	AllocMem                ;
		move.l	d0,r_anchor		; save it

;; set up required fields in structure

		move.b	#%00100001,ap_Flags(r_anchor)   ; DOWILD|DODOT
		move.w	#256,ap_Strlen(r_anchor)	; for full path

;; set bits we can break on (^C only)

                move.l	#$00001000,ap_BreakBits(r_anchor)

                move.l	(sp)+,a6
		rts

;; **********************************************************************
;; Free ASL Anchor Chain
;;
;; IN:	a6 - DosBase
;; OUT:	<none>

free_asl:	move.l	r_anchor,d1		; free any memory allocated
		beq.s   null_anchor		; by dos routines

		CALL	MatchEnd

		move.l	a6,-(sp)		; save DosBase
		move.l	r_anchor,a1
                move.l	#ap_SIZEOF+256,d0
		move.l	(4).w,a6
		CALL	FreeMem			; free AnchorPath struct

		sub.l	r_anchor,r_anchor       ; clear register

		move.l	(sp)+,a6
null_anchor:	rts

;; **********************************************************************
;; find name of current directory and use that
;;
;; IN: 	a6 - DosBase
;; OUT:	<none>

do_curr_dir:    move.l	r_rdaargs,d1		; finished with args
		CALL	FreeArgs

		move.l	(4).w,a6
		moveq.l	#MEMF_PUBLIC,d1
		move.l	#1024,d0		; get memory to store
		CALL	AllocMem		; full path of current dir
		tst.l	d0
		beq	closedos
		move.l	d0,a2			; store address

		move.l	r_dosbase,a6
		move.l	d0,d1
		move.l	#1024,d2
                CALL	GetCurrentDirName
		beq.s	use_sys			; failed...

                tst.b	(a2)			; empty buffer?
		beq.s	use_sys			; use "sys:"

		move.l	a2,a0			; get ptr path name

                bra.s	do_cd			; and do it

use_sys:	lea	sys(pc),a0

do_cd:		bsr	du

free_buffer:	move.l	a2,a1			; move buffer address
                move.l  d2,d0			; buffer length
                move.l	(4).w,a6		; get ExecBase

		CALL 	FreeMem

		bra     closedos		; exit program

;; ************************************************************************

		even
ver_str:	dc.b    0,"$VER: Du 2.5 (12.7.92)",0

		even
doslib:		dc.b	"dos.library",0

		even
tplate:		dc.b	"NAME/M,DIRS/S,FILES/S,NOHEAD/S,NOTOTAL/S",0

		even
sys:		dc.b	"sys:",0

		even
break_str	dc.b	"***Break",$a,0

		even
total_fmt	dc.b	"----- ----- -------- ------",$a
		dc.b	"%5ld%6ld%9ld%7ld",$a,0

		even
header_bar	dc.b	"Files  Dirs    Bytes Blocks",$a,0

		even
file_fmt	dc.b	"%20ld%7ld %s",$0a,$0 ; uses %20ld for extra space

		even
dir_fmt		dc.b	"%5ld%6ld%9ld%7ld ",$1b,"[33m","%s",$1b,"[31m",$0a,0

		end
