[net.sources] Sort DOS directories.

langet@ecn-pc.UUCP (Timothy Lange) (12/23/85)

-- start of code --
page	60, 132
title	Sortdir, pack a directory to save space and sort file names.
;
; This program is based heavily on the program called `packdir' written
; by Ted Mirecki and printed in the February, '85 issue of PC Tech Journal.
;
; I have rewritten part of the code, fixed some bugs in the original, and
; added sections to sort directory entries.  This is a use at your own
; risk program, read the code before you use it!   Timothy Lange.  12/23/85.
;
; Equates.
;
cr	equ	00dh
lf	equ	00ah
space	equ	020h
;
; Macros.
;
doscall macro	func
	mov	ah, func
	int	21h
	endm
;
; Directory entry structure layout.
;
dirent	struc
dname	db	8 dup(?)		; name of file
dext	db	?,?,?			; extension
attr	db	?			; attribute byte
	db	0ah dup(?)		; reserved for DOS
dtime	dw	?			; file update time
ddate	dw	?			; file update date
startcl dw	?			; starting cluster
dsize	dd	?			; file size
dirent	ends
;
;  Normal FCB structure layout.
;
normfcb struc
drive	db	?			; drive id
fname	db	8 dup(?)		; name of file
fext	db	?,?,?			; extension
curblok dw	?			; current block
recsize dw	?			; record size for each read
fsizelo dw	?			; lo-order word of file size
fsizehi dw	?			; hi-order word of file size
fdate	dw	?			; file update date
	db	0ah dup (?)		; reserved for DOS
recinblok	db	?		; record within block
relrec	dw	?			; relative record number
	dw	?
normfcb ends
;
code	segment
	org	80h			; parameter area in PSP
subdata label	byte			; use as buffer for 1 subdir entry
	org	100h
	assume	cs:code, ds:code, es:code, ss:code
begin:	jmp	start
;
; Extended FCB No. 1 will be built from command line parameters passed
; by DOS, and used to look up the sub-directory in its parent.
;
xfcb1	db	0ffh, 0, 0, 0, 0, 0	; FCB extension header
	db	10h			; attribute byte for sub-dirs
fcb1	normfcb<>			; normal FCB after extension
;
; Extended FCB No. 2 will be built by DOS as a result of lookup in
; parent directory, and used open & read sub-directory file.
;
xfcb2	db	0ffh, 0, 0, 0, 0, 0	; FCB extension header
	db	10h			; attribute byte for sub-dirs
fcb2	normfcb<>			; normal FCB after extension
;
; Message strings.
;
diskerr$	db	cr, lf, 'Disk read error', cr, lf, '$'
badfat$ db	cr, lf, 'FAT error: cluster chain not terminated'
	db	cr, lf, '$'
count$	db	' had'
inrec$	db	6 dup (' ')		; space for input rec count digits
	db	' clusters, now has'
outrec$ db	6 dup (' ')		; space for output count digits
	db	cr, lf, '$'
endmsg$ db	cr, lf
nodir$	db	6 dup (' ')		; space for directory count digits
	db	' Sub-Directories processed', cr, lf
freed$	db	6 dup (' ')		; space for freed count digits
	db	' clusters freed', cr, lf, '$'
;
; Miscellaneous data values.
;
cluslen dw	?			; bytes per cluster
fatcount	dw	?		; number of entries in FAT
fcode	db	11h			; function code for find first
fileclus	dw	0		; file size from tracing cluster chain
freed	dw	0			; count of clusters freed
inrec	dw	0			; current input record no.
maxrec	dw	0			; maximum number of records found
outrec	dw	0			; current output record no.
ndirs	dw	0			; count of directories processed
retry	db	3			; 3 retries if read error
savstart	dw	?		; save starting cluster of file
secsper db	?			; sectors per cluster
sorttbl db	20h*200h dup(0)		; data area for sorting directory
temp	db	20h dup (space)		; temp data area for sorting
;
sortdir proc	near
;
; Each called routine returns carry flag clear if no errors,
; set if error.	 If error, then DX points to error message.
; Display the message, then exit to DOS.
;
start:	call	getname			; look for filename on command line
	call	fatread			; read in the FAT
	jc	msg
nextnam:call	open			; look up filename and open it
	jc	eoj			; all done when no more matching names
	call	dirsize			; get file size by tracing clusters
	jc	msg
	call	process			; process the contents of the sub-dir
	call	close			; close the sub-dir file
	jmp	nextnam
eoj:	mov	ax, ndirs		; convert dir count to ASCII
	mov	si, offset code:nodir$
	call	i2asc
	mov	ax, freed		; convert freed count
	mov	si, offset code:freed$
	call	i2asc
	mov	dx, offset code:endmsg$
msg:	doscall 009h			; display string
	doscall 04ch			; terminate
sortdir endp
;
; Test if filename was specified on command line.  If not, build one
; consisting of all wild characters.  Move name to FCB1, set DTA.
;
getname proc	near
	mov	si, 5ch			; point to 1st FCB in prefix
	cmp	byte ptr [si+1], ' '	; test for name from command line
	jne	get1			; name is there
	mov	cx, 0bh			; else insert 11 wild chars
	lea	di, [si+1]		; into name file of FCB1
	mov	al, '?'
	cld
	rep	stosb
get1:	mov	di, offset code:fcb1	; point to FCB no. 1
	mov	cx, 0ch			; CX = length of drive, name and ext
	cld
	rep	movsb			; move name from prefix to FCB 1
	ret
getname endp
;
; Get the FAT characteristics, calculate the size of the FAT, and read
; The FAT into the buffer.  If read error, reset disk & retry 3 times.
;
fatread proc	near
	mov	dl, fcb2.drive		; move drive id into DL
	push	ds			; save the data segment
	doscall 01ch			; get FAT info
	pop	ds			; restore data segment
	mov	secsper, al		; save sectors per cluster
	mov	fatcount, dx		; save number of FAT entries
	cbw				; convert secs per clus to word
	mul	cx			; AX=AX * CX, bytes per cluster
	mov	cluslen, ax		; store it for future use
	mov	ax, fatcount		; restore entry count in AX
	mov	dx, ax			; repeat it in DX
	inc	dx			; add 1 to round up
	shr	dx, 1			; entry count divided by 2
	add	ax, dx			; size in bytes = 1.5 * entry count
	add	ax, cx			; round up to next sector
	dec	ax			;  by adding sector size less 1
	cwd				; convert to dbl word in DX:AX
	div	cx			; AX / CX = FAT length in sectors
	mov	cx, ax			; move sector count to CX
	doscall 019h			; get default drive
	mov	dx, 1			; begin at sector 1
	mov	bx, offset code:fatword ; point to buffer for FAT
f1:	push	ax			; save the registers
	push	bx
	push	cx
	push	dx
	int	25h			; absolute disk read
	jnc	f2			; jump if no disk errors
	popf				; pop flags saved by INT 25H
	pop	dx
	pop	cx
	pop	bx
	sub	ah, ah			; AH = 0 is disk reset function
	int	13h			; disk I/O interrupt
	pop	ax			; restore sector count
	dec	retry			; decrement retry count
	jnz	f1			; try reading again if not zero
	mov	dx, offset code:diskerr$; error exit
	stc
	ret
f2:	popf				; read ok: restore stack
	pop	dx
	pop	cx
	pop	bx
	pop	ax
	clc
	ret
fatread endp
;
; Input:  AH is 11 to find first name, 12 to find subsequent.  If found,
; but not a sub-directory, look for next matching entry, until a
; matching sub-directory is found or there are no more matching entries.
; If no match, exit with carry flag on, else open the file, save starting
; cluster pointer, display its name and turn carry flag off.
;
open	proc	near
	mov	dx, offset code:xfcb2	; set DTA at extended FCB 2
	doscall 1ah
	mov	dx, offset code:xfcb1	; point to exteneded FCB 1
op1:	doscall fcode			; get code 11H or 12H
	cmp	al, 0ffh		; AL=FFH if not found
	jne	op2			; skip if found
	stc				; set carry flag if not found
	ret
op2:	mov	fcode, 12h		; code 12H = find next
	cmp	byte ptr (fcb2+1).attr, 10h	; if found, test if a sub-dir
	jne	op1			; if not, go find next entry
	cmp	byte ptr (fcb2+1).dname, '.'	; if sub-dir, test if period
	je	op1
	mov	ax, (fcb2+1).startcl	; get starting cluster from FCB
	mov	savstart, ax		; save it for future use
	mov	dx, offset code:xfcb2	; point to extended FCB
	doscall 00fh			; open file function
	mov	si, offset code:fcb2.fname	; point to name in open FCB
	mov	cx, 11			; display 11 characters of filename
op3:	mov	dl, [si]		; get filename character
	doscall 002h			; display it
	inc	si			; point to next character in name
	loop	op3
	clc				; indicate no errors
	ret
open	endp
;
; Trace thru FAT, counting clusters in allocation chain.  For each cluster
; add bytes per cluster to file size.  At exit, file size in bytes is
; stored in open FCB, file size in clusters in location FILECLUS.
; Register usage:  DX:AX is dword accumulator for file size
;		   BX is last cluster number
;		   CX contains count of FAT entries
;		   SI points to next FAT entry
;		   DI contains bytes per cluster
;
dirsize proc	near
	mov	bx, savstart		; put starting cluster in BX
	mov	di, cluslen		; bytes per cluster in DI
	mov	cx, fatcount		; loop count in CX
	sub	ax, ax			; zero out file size
	mov	dx, ax			; and cluster count
	mov	fileclus, ax		; and cluster count
d1:	add	ax, di			; update file length
	adc	dx, 0			; carry into high word
	inc	fileclus		; update cluster count
	mov	si, bx			; calc BX * 1.5 in SI
	shr	bx, 1
	pushf				; save the carry flag
	add	si, bx
	mov	bx, fatword[si]		; get next FAT entry
	popf				; restore carry from shift
	jnc	d2			; skip if BX was even
	shr	bx, 1			; if odd, right-just
	shr	bx, 1			;  hi 12 bits of cluster no.
	shr	bx, 1
	shr	bx, 1
d2:	and	bx, 0fffh		; zero out 4 hi bits
	cmp	bx, 0ff8h		; test for end of chain
	jge	d3			; end if cluster is FF8H or above
	loop	d1			; loop if not
	mov	dx, offset code:badfat$ ; if FAT end but not chain end,
	stc				;  then error
	ret
d3:	mov	fcb2.fsizelo, ax	; put file length into FCB
	mov	fcb2.fsizehi, dx
	clc				; clear error flag
	ret
dirsize endp
;
; Read the sub-directory as a data file, 1 entry at a time.  Store them
; in order into table 'sorttbl' for sorting.  Sort table, then write back
; out to disk.
;
process	proc	near
	sub	ax, ax
	mov	inrec, ax		; start at first directory entry
	mov	outrec, ax
	mov	bx, offset code:sorttbl
	mov	si, offset code:subdata ; point to I/O buffer
	mov	dx, si			; set DTA to buffer
	doscall 01ah
	mov	fcb2.recsize, 20h	; insert record size into FCB
	mov	dx, offset code:xfcb2	; point to extended FCB
read2:	mov	ax, inrec		; set record (entry) no. to read
	mov	fcb2.relrec, ax		; put it into FCB's relative rec field
	doscall 021h			; direct access read
	test	al, al			; eof if AL not zero
	jz	okay2
	jmp	loop0
okay2:	cmp	byte ptr subdata, 0	; never used entry?
	je	loop0
	inc	inrec			; update record no. for next read
	cmp	byte ptr subdata, 0e5h	; is entry deleted?
	je	read2			; yes, do not save, go read next
	mov	di, bx			; copy from buffer to sort table
	mov	si, offset code:subdata
	mov	cx, 20h
	cld
	rep	movsb
	add	bx, 20h			; point to next table entry
	inc	outrec
	jmp	read2
;
; Inrec now contains range of records in table, 0 - inrec
;
loop0:	sub	outrec, 1
	mov	bx, inrec		; put total number of records read 
	mov	maxrec, bx		; into maxrec
loop1:	mov	bx, 1			; sorted flag, now true.
	mov	inrec, 0
	mov	dx, offset code:sorttbl
loop2:	mov	di, dx
	mov	si, dx
	add	si, 20h
	mov	cx, 0bh
	cld
	repe	cmpsb
	je	noswap
	sub	di, 1
	sub	si, 1
	mov	al, byte ptr [di]
	cmp	al, byte ptr [si]
	jl	noswap
;
; Swap adjacent table entries.
;
	mov	di, offset code:temp	; move first entry to temp
	mov	si, dx
	mov	cx, 20h
	cld
	rep	movsb
	mov	di, dx			; move second entry to first
	mov	si, dx
	add	si, 20h
	mov	cx, 20h
	cld
	rep	movsb
	mov	di, dx			; move temp to second entry
	add	di, 20h
	mov	si, offset code:temp
	mov	cx, 20h
	cld
	rep	movsb
	mov	bx, 0			; sorted flag to false
noswap: inc	inrec
	mov	ax, inrec
	add	dx, 20h
	cmp	ax, outrec		; done with first pass?
	jl	loop2			; no, keep going
	cmp	bx, 1			; table sorted?
	jne	loop1			; no, do it again
;
; Write out table.
;
	add	outrec, 1
	mov	inrec, 0		; get record no. to write
	mov	ax, inrec
	mov	bx, offset code:sorttbl
	mov	dx, offset code:xfcb2	; point to extended FCB
write2: mov	si, bx
	mov	di, offset code:subdata
	mov	cx, 20h
	cld
	rep	movsb
	mov	fcb2.relrec, ax		; put it into FCB
	doscall 022h			; direct access write
	push	di			; zero out table entry in case
	mov	di, bx			; next directory is shorter
	mov	al, 0
	mov	cx, 20h
	rep	stosb
	pop	di			; finished zeroing out entry
	add	bx, 20h			; increment to next table entry
	inc	inrec
	mov	ax, inrec
	cmp	ax, maxrec		; last entry?
	jle	write2			; if not, read next, else eof
	ret				; return at end of data
process	endp
;
; Close the sub directory file and display counts.
;
close	proc	near
	mov	ax, outrec		; get count of entries written
	test	ax, ax			; make sure some were written
	jz	cl1
	mov	fcb2.relrec, ax		; set record count in FCB
	sub	cx, cx			; write nothing, just set size
	mov	dx, offset code:xfcb2	; point to extended FCB
	doscall 028h			; block write, set size
cl1:	mov	ax, fileclus		; get input cluster count
	add	freed, ax		; update freed cluster count
	mov	si, offset code:inrec$
	call	i2asc			; convert count to ASCII digits
	mov	ax, outrec		; get output record count
	mov	bx, 20h			; bytes per entry
	mul	bx			; DX:AX = output bytes
	div	cluslen			; get output clusters, remdr in DX
	test	dx, dx			; test for remainder
	jz	cl2
	inc	ax			; if remdr, round up to next cluster
cl2:	sub	freed, ax		; total freed = freed + input - output
	mov	si, offset code:outrec$
	call	i2asc			; convert output count to digits
	mov	dx, offset code:count$	; display count msg
	doscall 009h
	inc	ndirs			; increment directory count
	ret
close	endp
;
; I2ASC converts 2 byte integer into 6 byte numeric ASCII string.
; Input:  number to be converted in AX
;	  DS:SI points to string to receive output
; Output: string at DS:SI, right justified, blank padded
;	  if AX = 0, string is 1 zero in rightmost position
;	  if AX < 0, leading minus is floated before 1st digit
;	  DS:DI points to first non-blank in string
;	  all other registers unchanged
;
i2asc	proc	near
	push	es			; save registers
	push	dx
	push	cx
	push	bx
	push	ax
	mov	al, ' '			; blank out string
	mov	dx, ds
	mov	es, dx
	mov	di, si
	mov	cx, 6
	cld
	rep	stosb
	mov	di, si
	add	di, 5			; point at string end
	mov	bx, 0ah			; base ten divisor
	pop	ax			; get binary number into ax
	push	ax
	std				; move backwards thru string
divloop:cwd				; convert to dbl word in DX, AX
	idiv	bx			; quotient in AX, remainder in DX
	test	dl, 80h			; test if remainder negative
	jz	$+4			; skip if positive
	neg	dl			; else get abs of remainder
	or	dx, 30h			; insert ASCII zone
	xchg	ax, dx			; exchange quotient and remainder
	cld
	stosb				; store ASCII character in string
	mov	ax, dx			; restore quotient
	test	ax, 0ffffh		; test if more decimal digits
	jnz	divloop
	pop	bx			; restore integer into BX
	test	bx, 8000h		; test if number negative
	jz	i2exit
	mov	al, '-'			; insert minus sign if negative
	cld
	stosb
i2exit: inc	di			; point at last non-blank
	mov	ax, bx			; restore registers
	pop	bx
	pop	cx
	pop	dx
	pop	es
	cld
	ret
i2asc	endp
;
; Open-ended buffer for holding File Allocation Table.
;
fatword label	word			; process FAT by words
code	ends
	end	begin
-- end of code --
-- 
Tim Lange		Engineering Business Offices
317-494-5338		Rm 120 Engineering Administration Bldg.
Purdue University 	West Lafayette, IN  47907
{decvax|harpo|ihnp4|inuxc|seismo|ucbvax}!pur-ee!langet