;       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
