* DFREE.ASM:	An assembler disk free space lister.  I came up with this
*		as a protest against INFO.  I don't CARE how many blocks
*		are free, I want to know how much space I've got in BYTES,
*		considering everything else tells me file sizes in bytes,
*		etc., etc.
*
* Author:
*		James E. Cooper Jr.
*		113 Collier Pl.  Apt. 1B
*		Cary, NC  27513
* Notes:
*		Working version finished 12/14/86 jec
*		Added printing of volume name 12/16/86 jec
*
* Included equate files.
* -----------------------------------------------------------------------
	NOLIST 
	INCLUDE	"exec/types.i"
	INCLUDE	"exec/libraries.i"
	INCLUDE	"libraries/dos.i"
	INCLUDE	"libraries/dosextens.i"
	INCLUDE	"exec/memory.i"
	LIST

* External references
* -----------------------------------------------------------------------

	XREF		_AbsExecBase

	EXTERN_LIB	OpenLibrary
	EXTERN_LIB	Write
	EXTERN_LIB	Output
	EXTERN_LIB	Info
	EXTERN_LIB	Lock
	EXTERN_LIB	UnLock
	EXTERN_LIB	CurrentDir
	EXTERN_LIB	AllocMem
	EXTERN_LIB	FreeMem

* Local Equates
* ------------------------------------------------------------------------
TRUE	EQU	-1
FALSE	EQU	0

* Macros
* ------------------------------------------------------------------------

* Calls to dos.library and exec library
SysCall	MACRO
	CALLLIB	_LVO\1
	ENDM

ExeCall MACRO
	LINKLIB	_LVO\1,_AbsExecBase
	ENDM

* The print routine and argument setup
PRINT	MACRO
	lea.l	\1(PC),A0
	move.l	d5,d1
	jsr	print
	ENDM

* The code segment
* -------------------------------------------------------------------------
	RORG	0			; Relocatable code.

	jsr	argtrim			; clean up command line
	MOVEM.L	D0/A0-A2,-(SP)		; Save command line (OpenLibrary trashes).
	moveq	#0,d0

	;------ get Exec's library base pointer:
	LEA.L	DOSName(PC),A1		; name of dos library
	move.l	#LIBRARY_VERSION,d0

	ExeCall OpenLibrary
	MOVE.L	D0,A6			; Save library pointer (always in A6).
	BNE.S	gotdos

	; Should really issue an alert here...
	moveq	#RETURN_FAIL,D0		; give up
	bra	FINISHED

gotdos:

	; REGISTER USAGE:
	;	A0	= scratch after command line used.
	;	A5	= InfoData address
	;	A6	= dos library base pointer
	;	D1-3	= AmigaDOS scratch argument registers
	;	D4	= current directory lock
	;	D5	= output handle
	;	D6	= root lock flag

*	Obtain the output handle (needed for write)
	SysCall	Output
	MOVE.L	D0,D5			; Save for the write

	MOVEM.L	(SP)+,D0/A0-A2		; restore command line etc.

*	Setup D6 as the restore directory flag
	moveq	#FALSE,D6

*	Now check if we have an argument. If we have, attempt to lock it.
*	Otherwise, go for current directory.
	cmpi.b	#0,(A0)			; a null string for an argument ?
	beq	CURDIR			; yes

	; Lock to specified directory. Place name in D1, access mode in D2
	move.l	a0,d1
	move.l	#ACCESS_READ,d2
	SysCall	Lock
	move.l	d0,d4			; remember for later	
	beq	nolock			; did we get a lock?
	bra	uselock			; for the Examine

CURDIR:
	; Get lock to current directory. The most bulletproof way of doing this
	; (due to bugs in the V1.1 RAM: handler) is to make the root directory
	; the current one, because CurrentDir will return the old lock value.
	moveq	#0,D1
	SysCall CurrentDir		; got the lock
	move.l	d0,d4			; for later
	moveq	#TRUE,d6		; remember to cd

uselock:
	; A lock in D1 and a InfoData address in D2 are all that are needed.
	; We allocmem the InfoData to assure alignment. 

	move.l	#id_SIZEOF,D0		; size of the block
	move.l	#MEMF_PUBLIC,D1		; memory requirement

	MOVEM.L	D2-D7/A0-A6,-(SP)	; in case AllocMem trashes (it does...)
	ExeCall AllocMem		; args D0, D1 returns into D0
	MOVEM.L	(SP)+,D2-D7/A0-A6	; restore registers
	move.l	d0,a5			; save it for later
	beq	nomem			; but did it work?

	move.l	d0,d2			; InfoData address
	move.l	d4,d1			; the lock

	SysCall Info			; see what the lock corresponds to.

	cmpi.l	#FALSE,d0		; don't need to cd in cleanup code
	beq	noinfo
	move.l	d2,a0			; infodata pointer into a0
	movem.l	a0-a7/d0-d7,-(SP)	; save registers temporarily
	movem.l	a0,-(SP)		; a0 an extra time because we kill it
	PRINT	VOLUME			; start of output message
	movem.l	(SP)+,a0		; get a0 back
	tst.l	id_VolumeNode(a0)	; ...RAM: doesn't have a Volume
	bne.s	df1			; ...Node, so returns 0.
	moveq	#3,d3			; ...special case of RAM:
	move.l	#RAM,d2			; ......so print RAM as volume
	bra.s	df2
df1:
	move.l	id_VolumeNode(a0),d0	; here goes a mess caused by
	asl.l	#2,d0			; ...using BPTRs to show where
	move.l	d0,a0			; ...things are.  We need all
	move.l	40(a0),d0		; ...of this mess just to find
	asl.l	#2,d0			; ...the Volume Name for the
	move.l	d0,a0			; ...requested drive.
	moveq	#0,d3			; ...
	move.b	(a0)+,d3		; ...(length)
	move.l	a0,d2			; ...(address)
df2:
	SysCall	Write			; ...and finally print it!
	PRINT	HAS			; middle part of message
	movem.l	(SP)+,a0-a7/d0-d7	; get registers back, at last!
	move.l	id_NumBlocks(a0),d0	; # of blocks on disk
	sub.l	id_NumBlocksUsed(a0),d0	; # already used
	mulu	(id_BytesPerBlock+2)(a0),d0	; use lower word (won't work
						; if BytesPerBlock >64K!)
	jsr	bindec			; convert to ASCII
	PRINT	ASCNUM			; print it out
	bra	cleanup			; and quit.

nomem:
	PRINT	S_NOMEM
	bra	afterfree

noinfo:
	PRINT	S_NOINFO
	bra	cleanup

nolock:
	PRINT	S_NLOCK
	bra	FINISHED
	
cleanup:
	; start off by freeing allocated memory.
	move.l	a5,a1
	MOVEM.L	D1-D7/A0-A6,-(SP)	; in case FreeMem trashes 
	move.l	#id_SIZEOF,D0
	ExeCall FreeMem
	MOVEM.L	(SP)+,D1-D7/A0-A6	; restore registers
	
afterfree:

	; May have to throw away two locks and do a cd here
	move.l	d4,d1
	cmpi.l	#FALSE,d6
	beq	nocd

	SysCall CurrentDir		; get back to where we were
	move.l	d0,d1
nocd:
	SysCall UnLock
	move.l	#RETURN_OK,D0	
FINISHED:
	rts
	
* Subroutines
* ------------------------------------------------------------------------

********************************************************************************
* argtrim:	a routine to trim the end of a command line and null terminate
*		it. This is achieved in the following manner:
*		Start at the end and run back until a non-whitespace (nl/blank)
*		character is encountered. If this is a quote, toss it.
*		If the *first* character is a quote, bump the start address by
*		one. No pretense of quote matching here.
*		 Inputs:	address in A0, length in D0
*		 Outputs:	address in A0.
********************************************************************************

argtrim:
	movem.l	d1-d7/a1-a6,-(sp)	; save registers

	cmpi.b	#'"',(a0)		; starts with a quote?
	bne.s	checkline		; no
	subq	#1,d0			; yes - decrement count
	addq	#1,a0			; and bump start address

checkline:
	cmpi.b	#1,D0			; length includes the newline
	bne.s	isline			; yes - there is something there
	move.b	#0,(a0)			; create null string
	bra.s	argfini			; done

isline:
;	strip off any run of blanks on the end...	
	move.l	a0,a1			; computing end address of line
	add.l	d0,a1			; 
	subq	#2,a1			;

toploop:
	cmp.l	a0,a1		
	beq.s	empty			; single char or run of blanks

	cmpi.b	#' ',(a1)
	bne.s	endloop			; finished the scan
	subq	#1,a1			; else back one more
	bra.s	toploop			; and try again

endloop:
	cmpi.b	#'"',(a1)
	beq.s	nullit
nullnext:
	addq	#1,a1
nullit:
	move.b	#0,(a1)
	bra.s	argfini
empty:					; could be blanks or a single char
	cmpi.b	#' ',(a1)
	bne.s	endloop			; or maybe a single quote!
	move.b	#0,(a0)

argfini:
	movem.l	(sp)+,d1-d7/a1-a6	; restore registers
	rts

********************************************************************************
* PRINT:	Passed an EA in A0, and a handle in D1, print the string.
********************************************************************************

print:
	MOVEM.L D0-D7/A0-A6,-(SP)

	; Let's find out how long this string is...
pr1:
	cmpi.b	#' ',(a0)+		; strip leading blanks
	beq.s	pr1			;   for the size, doesn't
	subq	#1,a0			;   affect normal strings
	JSR	strlen			; Addr in A0 returns len in D0
	MOVE.L	D0,D3			; length
	MOVE.L	A0,D2			; Address
	SysCall	Write			; Handle already in D1

	; We don't check error returns from write, because there isn't a whole
	; lot we can do if console writes fail.
	MOVEM.L	(SP)+,D0-D7/A0-A6	; Restore registers
	rts

********************************************************************************
* STRLEN:	How long is a piece of string? Pass a string in A0, the
*		result comes back in D0. 
********************************************************************************

strlen:
	MOVEM.L	D1-D7/A0-A6,-(SP)	; Save registers A0, D1 on stack.

	MOVEQ	#0,D0			; Initialize count

STRLOOP:
	; Investigate current character.
	cmpi.b	#0,(A0)+		; Is it a NUL?
	beq.s	STRFINI
	ADDQ	#1,D0			; Up the count
	bra	STRLOOP

STRFINI:
	MOVEM.L	(SP)+,D1-D7/A0-A6	; Restore registers
	rts

********************************************************************************
* bindec - converts 32-bit binary to ASCII for display
*	hack of "bindec" routine on pg. 428 of "Programming the 68000", by Steve
*	Williams, Sybex Books.	Optimized for unsigned numbers ONLY!
********************************************************************************

bindec:
	movem.l	d0-d2/a0-a1,-(sp)
	move.l	#ASCNUM+9,a0
	tst.l	d0			; test for special case of zero len
	bgt	bd2			; >0, go on
	move.b	#'0',-(a0)		; put "0" in output area, return
	bra	bddone
bd2:
	moveq	#8,d2
bdloop:
	tst.l	d0			; if done, suppress leading zeros
	beq	bddone			; stop
	moveq	#10,d1
	jsr	ldiv
	move.b	d1,-(a0)
	add.b	#'0',(a0)
	dbra	d2,bdloop
bddone:
	movem.l	(sp)+,d0-d2/a0-a1
	rts
	
********************************************************************************
* ldiv - long divide routine
*	serious hack of routine on pg. 428 of "Programming the 68000" by Steve
*	Williams, Sybex Books.	Optimized for unsigned 32 bit divides ONLY!
*	Enter with:
*	D0 = dividend
*	D1 = divisor
*	Exit with:
*	D0 = quotient
*	D1 = remainder
********************************************************************************

ldiv:
	movem.l	d2-d3,-(sp)		; temporary registers
	moveq	#0,d2			; clear quotient initially
ldloop:
	cmp.l	d0,d1			; dividend : divisor
	bgt	ldone			; if gt, don't subtract
	sub.l	d1,d0			; subtract divisor
	addq.l	#1,d2			; increment quotient
	bra	ldloop			; loop again
ldone:
	move.l	d0,d1			; this is remainder
	move.l	d2,d0			; this is quotient
	movem.l	(sp)+,d2-d3		; restore registers
	rts				; return

********************************************************************************
* Data declarations
********************************************************************************

	CNOP	0,4
DOSName		DOSNAME
S_NLOCK		DC.B	'directory or file name not found',10,0
S_NOINFO	DC.B	'Could not get info for disk',10,0
S_NOMEM		DC.B	'No memory',10,0
RAM		DC.B	'RAM'
VOLUME		DC.B	'Volume "',0
HAS		DC.B	':" has ',0
ASCNUM		DC.B	'          bytes free.',10,0
	END
