;	SWEEP.ASM -- Runs program or command across subdirectories
;	=========
;
;		(C) Copyright Charles Petzold, 1985

CSEG		Segment
		Assume	CS:CSEG, DS:CSEG, ES:CSEG, SS:CSEG

		Org	002Ch
Environment	Label	Byte		; Segment of Environment is here

		Org	007Dh
NewParam	Label	Byte		; Parameter to pass to COMMAND

		Org	0080h
OldParam	Label	Byte		; Parameter passed to SWEEP

		Org	0100h
Entry:		Jmp	Begin		; SWEEP.COM Entry Point

;	Most Data (some more at end of program)
;	---------

SweepMessage	db	13,10,'>>> SWEEP >>> '	; The SWEEP message
CurrentDir	db	?,':\'			; ? gets drive letter

		db	'(C) Copyright Charles Petzold, 1985',1Ah

DosVersMsg	db	'Needs DOS 2.0 +$'	; Error Messages
MemAllocMsg	db	'Allocation Problem$'
CommandMsg	db	'SWEEP: COMMAND Problem$'
AbnormalMsg	db	'SWEEP: Abnormal Exit$'

DosVersion	dw	?		; Store DOS Version Number here
BreakState	db	?		; Store original break state here

Comspec		db	'COMSPEC='	; String for Environment search
CommandAsciiz	dd	?		; Address of COMMAND.COM string
ParamBlock	dw	?		; Parameter block for EXEC call
		dw	NewParam,?
		dw	5Ch,?
		dw	6Ch,?

SearchAsciiZ	db	'*.*',0		; Asciiz for Find call
BackOneDir	db	'..',0		; Asciiz for moving back one directory
DtaPointer	dw	DtaAreaBegin	; For nested directory searches
Direction	db	0		; Forward search initially

;	Check DOS Version
;	-----------------

Begin:		Mov	AH,30h			; Check for DOS Version
		Int	21h
		Cmp	AL,2			; See if it's 2.0 or above
		Jae	DosVersOK		; If so, we can proceed

		Mov	DX,Offset DosVersMsg	; Otherwise error message
ErrorExit:	Mov	AH,9			; Print String function call
		Int	21h			; Do it

		Int	20h			; And exit prematurely

DosVersOK:	Xchg	AL,AH			; Get Major Version in AH
		Mov	[DosVersion],AX		; And save whole thing

;	Un-allocate rest of memory 
;	--------------------------

		Mov	SP,Offset StackTop	; Set new stack pointer
		Mov	BX,Offset EndOfProgram	; This is beyond our needs
		Mov	CL,4			; Prepare for shift
		Shr	BX,CL			; Convert to segment form
		Mov	AH,4Ah			; Shrink allocated memory
		Int	21h			; By calling DOS

		Jnc	MemAllocOK		; If no error, we can proceed

		Mov	DX,Offset MemAllocMsg	; Otherwise set up for message
		Jmp	ErrorExit		; Print it and terminate

;	Search for Comspec in Environment
;	---------------------------------

MemAllocOK:	Push	ES			; We'll be changing this
		Mov	BX,Offset Environment	; Segment of Environment
		Mov	ES,[BX]			; Set ES to it
		Assume	ES:Nothing		; And tell the assembler

		Sub	DI,DI			; Start at the beginning
		Mov	SI,Offset ComSpec	; String to search for
		Cld				; Direction must be forward

TryThis:	Cmp	Byte Ptr ES:[DI],0	; See if points to zero
		Jz	NoFindComSpec		; If so, we're dead in water
		
		Push	SI			; Temporarily save these
		Push	DI
		
		Mov	CX,8			; Search string has 8 chars
		Repz	Cmpsb			; Do the string compare

		Pop	DI			; Get back the registers
		Pop	SI

		Jz	FoundComspec		; If equals, we've found it

		Sub	AL,AL			; Otherwise search for zero
		Mov	CX,-1			; For 'infinite' bytes 
		Repnz	Scasb			; Do the search

		Jmp	TryThis			; And try the next string

NoFindComSpec:	Pop	ES			; Get back ES on error
		Mov	DX,Offset CommandMsg	; Set up error message
		Jmp	ErrorExit		; And bow out gracefully

FoundComspec:	Add	DI,8			; so points after 'COMSPEC='
		Mov	Word Ptr [CommandASCIIZ],DI	; Save the address
		Mov	Word Ptr [CommandASCIIZ + 2],ES	; including segment

; 	Set up parameter block for EXEC call
;	------------------------------------

		Mov	[ParamBlock],ES		; Segment of environment
		Mov	[ParamBlock + 4],CS	; Segment of parameter
		Mov	[ParamBlock + 8],CS	; Segment of 1st FCB
		Mov	[ParamBlock + 12],CS	; Segment of 2nd FCB

		Pop	ES			; Restores ES to this segment
		Assume	ES:CSEG			; And make sure MASM knows

;	Fix up new paramater for "/C" String
;	------------------------------------

		Mov	AL,[OldParam]	; Get old character count
		Add	AL,3		; Three more characters in paramater
		Mov	[NewParam],AL	; New number of characters
		Mov	[NewParam + 1],' '		; Next is a blank
		Mov	Word Ptr [NewParam + 2],'C/'	; Then a /C

;	Get the current break state, drive, and subdirectory
;	----------------------------------------------------

		Mov	AX,3300h		; Get Break State
		Int	21h			; By calling DOS
		Mov	[BreakState],DL		; Save it

		Sub	DL,DL			; Set it to OFF
		Mov	AX,3301h		; Set Break State
		Int	21h			; By calling DOS

		Mov	DX,Offset Terminate	; For Ctrl-Break exits
		Mov	AX,2523h		; Set Interrupt 23h vector
		Int	21h			;    through DOS call

		Mov	AH,19h			; Get current drive
		Int	21h			; By calling DOS
		Add	AL,'A'			; Convert to letter
		Mov	[CurrentDir],AL		; And save it

		Mov	SI,Offset StartOffDir	; Repository of directory
		Sub	DL,DL			; Indicate default drive
		Mov	AH,47h			; Get current directory
		Int	21h			; By calling DOS

;	Display SWEEP message with current drive and subdirectory
;	---------------------------------------------------------

MainLoop:	Mov	SI,3 + Offset CurrentDir; Receives directory
		Sub	DL,DL			; Indicate current drive
		Mov	AH,47h			; Current directory call
		Int	21h			; Get it

		Mov	SI,Offset SweepMessage	; String to display
		Cld				; Want direction forward
DirPrintLoop:	Lodsb				; Get the character
		Or	AL,AL			; Check if it's zero
		Jz	NoMoreDirPrint		; If so, branch out
		Mov	DL,AL			; Otherwise set DL to it
		Mov	AH,2			; For Display Output
		Int	21h			; Display the character
		Jmp	DirPrintLoop		; And loop around for the next

NoMoreDirPrint:	Mov	CX,500			; We'll hang out here awhile

StatCheckLoop:	Mov	AH,0Bh			; Set up for keyboard status
		Int	21h			; Allow user to Break out
		Loop	StatCheckLoop		; Do it a few more times

		Cmp	[DosVersion],30Ah	; See if DOS is 3.1 or higher
		Jb	LoadCommand		; If not, skip CR & LF

		Mov	DL,13			; Carriage Return
		Mov	AH,2			; Write to Display
		Int	21h			;   by calling DOS

		Mov	DL,10			; Line Feed
		Mov	AH,2			; Write to Display
		Int	21h			;   by calling DOS

; 	Load COMMAND.COM
; 	-----------------
		
LoadCommand:	Mov	BX,Offset ParamBlock	; ES:BX = parameter block
		Lds	DX,[CommandAsciiz]	; DS:DX = Asciiz of COMMAND
		Sub	AL,AL			; EXEC type zero
		Mov	AH,4Bh			; EXEC function call
		Int	21h			; Load command processor

; 	Return from COMMAND.COM
;	-----------------------

		Mov	AX,CS		; This is the current code segment
		Mov	DS,AX		; Reset DS to this segment
		Mov	ES,AX		; Reset ES to this segment
		Mov	SS,AX		; Reset stack segment to it
		Mov	SP,Offset StackTop	; And reset stack pointer also

;	Avoid problems caused by commands that may change drive or directory
;	--------------------------------------------------------------------

		PushF				; Save EXEC Error Flag

		Sub	DL,DL			; Set Break State to OFF
		Mov	AX,3301h		; Set Break State
		Int	21h			; By calling DOS

		Mov	DL,[CurrentDir]		; Get original drive letter
		Sub	DL,'A'			; Convert to number
		Mov	AH,0Eh			; Select disk
		Int	21h			; Through DOS call

		PopF				; Get back EXEC Error Flag

		Mov	DX,Offset CommandMsg	; Set up possible error message
		Jc	ErrorExit2		; And print if EXEC error

		Mov	DX,2 + Offset CurrentDir; The pre-COMMAND directory
		Mov	AH,3Bh			; Call to change directory
		Int	21h			; Do it

		Jnc	NextLevel		; Continue if no error

		Mov	DX,Offset AbnormalMsg	; Otherwise set up message
ErrorExit2:	Mov	AH,9			; Will print the string
		Int	21h			; Print it

		Jmp	Terminate		; And get out of here

;	Find first or next subdirectory level
;	-------------------------------------

NextLevel:	Mov	DX,[DTAPointer]		; Next nested DTA
		Mov	AH,1Ah			; For DOS call to set DTA
		Int	21h			; Do it

		Cmp	[Direction],0		; Check if we're nesting
		Jnz	FindNextFile		; If not, we're continuing

		Mov	DX,Offset SearchAsciiZ	; We search for *.*
		Mov	CX,10h			; Subdirectory attribute
		Mov	AH,4Eh			; Find first file
		Int	21h			;   by calling DOS

		Jmp	Short TestMatch		; Hop around next section

FindNextFile:	Mov	AH,4Fh			; Find next file 
		Int	21h			;   by calling DOS

TestMatch:	Jc	NoMoreFiles		; If CY flag, at end of rope

		Mov	BX,[DTAPointer]		; Our find stuff is here
		Test	Byte Ptr [BX + 21],10h	; Test if directory attribute
		Jz	FindNextFile		; If not, continue search

		Add	BX,30			; Now points to directory name
		Cmp	Byte Ptr [BX],'.'	; Ignore "." and ".." entries
		Jz	FindNextFile		;   by continuing the search

		Mov	DX,BX			; Now DX points to found dir
		Mov	AH,3Bh			; Set up DOS function call
		Int	21h			; And change directory

		Add	[DtaPointer],43		; New DTA for new level
		Mov	[Direction],0		; I.E., Find first file

		Jmp	MainLoop		; All ready to cycle through

;	No More Files Found -- go back to previous level
;	------------------------------------------------

NoMoreFiles:	Cmp	[DTAPointer],Offset DtaAreaBegin
						; See if back at start
		Jz	Terminate		; If so, that's all, folks

		Sub	[DTAPointer],43		; Back one for previous
		Mov	[Direction],-1		; I.E., will find next file

		Mov	DX,Offset BackOneDir	; The string ".."
		Mov	AH,3Bh			; Call to change directory
		Int	21h			; Change directory to father

		Jmp	NextLevel		; And continue the search

Terminate:	Mov	DX,Offset RestoreDir	; Original subdirectory
		Mov	AH,3Bh			; Call to change directory
		Int	21h			; Do it

		Mov	DL,[BreakState]		; Original break-state
		Mov	AX,3301h		; Change the break-state
		Int	21h			;   by calling DOS

		Int	20h			; Terminate program

RestoreDir	db	'\'			; For eventual restore
StartOffDir	Label	Byte			; Place for original directory
StackBottom	equ	StartOffDir + 64	; Stack area beyond that 
StackTop	equ	StackBottom + 100h	; The top of the stack
DtaAreaBegin	equ	StackTop		;   is also the DTA area
DtaAreaEnd	equ	DtaAreaBegin + 32 * 43	; Can have 32 DTAs of 43 bytes
EndOfProgram	equ	DtaAreaEnd + 15		; This is beyond our needs

CSEG		EndS				; End of the segment
		End	Entry			; Denotes entry point
