        PAGE   60,132
        TITLE  Xall - High speed eXeute in ALL subdirs utility
        SUBTTL General Program description
;-----------------------------------------------------------------------------;
; Xall - eXecute any command in current directory plus all subdirectories     ;
;-----------------------------------------------------------------------------;
;    XALL 1.0 þ PCDATA TOOLKIT Copyright (c) 1990 Ziff Communications Co.     ;
;                   PC Magazine þ Wolfgang Stiller                            ;
;                                                                             ;
;-----------------------------------------------------------------------------;
;Purpose:                                                                     ;
; XALL will eXecute any command in the current directory plus all             ;
;      subdirectories below that one.  This program is written for high       ;
;      speed execution (lean and mean!).                                      ;
;-----------------------------------------------------------------------------;
;Syntax:                                                                      ;
;                                                                             ;
;XALL anyDOScommand                                                           ;
;                                                                             ;
;  "anyDOScommand" may be any batch file, program, or DOS command. Any        ;
;                  programs or batch files should be located in a             ;
;                  directory specified in the DOS path (PATH=).               ;
;-----------------------------------------------------------------------------;
;Remarks:                                                                     ;
;  "anyDOScommand" will be executed first in the current directory and        ;
;  then in each subdirectory subordinate to the current directory.  This      ;
;  program has been optimized for speed and efficiency of execution.  If      ;
;  execution is terminated, you will be in the directory currently being      ;
;  processed.  This program does NOT check for execution of other             ;
;  programs that change the drive or directory. (Speed is the goal!)          ;
;  XALL has advanced error recovery and provides clear explanations to        ;
;  PCDATA Toolkit user of what has gone wrong, such as an                     ;
;  invalid COMSPEC environment variable.                                      ;
;                                                                             ;
;  XALL will return the following DOS errorlevels:                            ;
;                                                                             ;
;  00 - normal completion                                                     ;
;  32 - any abnormal occurance - indicates fatal termination.                 ;
;-----------------------------------------------------------------------------;


;-----------------------------------------------------------------;
; Constants:                                                      ;
;-----------------------------------------------------------------;
BOX                  EQU  254             ;Small box character code
CR                   EQU  0Dh             ; Carriage return
LF                   EQU  0Ah             ; Line feed
CRLF                 EQU  0A0D            ; CR+LF in one word
First_time           EQU  00h             ; Constant to indicate first time
Not_First_Time       EQU  0FFh            ;             indicate NOT first time
Subdirectory_attrib  EQU  10h             ; Bit pattern to find directories

CSEG    SEGMENT
        ASSUME  CS:CSEG, DS:CSEG,ES:CSEG,SS:CSEG
        ORG     2Ch                       ;Segment of environment found here
Environment LABEL BYTE

        ORG     7DH                       ;Just before paramter area in PSP
XALL_PARM LABEL  BYTE

        ORG     80h                       ;Normal DOS PSP parameter area
PSP_PARM  LABEL  BYTE

;*****************************************************************************
;** Main program begins here:  XALL - Subdirectory Command eXecuter         **
;**                            ----                                         **
;*****************************************************************************

        ORG     100h                      ;This is a .COM type program
XALL:
        MOV     SP,OFFSET STACK_TOP       ;Create new stack

        MOV     DX,OFFSET XALL_CR_MSG     ;Copyright message
        MOV     AH,09h                    ;DOS print string function
        INT     21h

        MOV     BX,offset Program_top     ;End of program + 15 (segment roundng)
        MOV     CX,4                      ;Prepare to div by 4 (calc # of paras)
        SHR     BX,CL                     ;Convert to segment address
        MOV     AH,4Ah                    ;Release uneeded memory
        INT     21h

        CALL    Get_COMSPEC               ;Locate the compsec in the environment
        CALL    Create_EXEC_parm_BLOCK    ;Create parameter block for DOS EXEC
        CALL    Create_Command_tail       ;Create /C command for COMMAND.COM

;***********************;
; MAIN loop begins here ;
;***********************;
EXEC_Next_Command:                        ;Come here to execute a command
                                          ;in each directory
        CALL    DISPLAY_XALL_MSG          ;Display msg that we are this directry

;---------------------------------------------------------------------------;
; Use DOS EXEC function to invoke COMMAND.COM with user's specified command ;
;              tail from the parameter area at 80h.                         ;
        LDS    DX,[Command_Adr]           ;DS:DX is address of command.com str
        MOV    BX,OFFSET Parm_block       ;ES:BX is adr of parameter block
        MOV    AX,4b00h                   ;DOS EXEC sub-task function
        INT    21h                        ;COMMAND.COM now executing user's cmd

; Return from execution of DOS shell - all registers may be corrupted:
        MOV     AX,CS                     ;CS is only OK register
        MOV     ES,AX
        MOV     DS,AX
        MOV     SS,AX
        MOV     SP,OFFSET Stack_top       ;Reset stack pointer
        JC      Handle_Exec_Error         ;If DOS EXEC function failed handle it

;-----------------------------------------------------------------------------;
;   Now find NEXT (or first) subdirectory below the current one               ;
;-----------------------------------------------------------------------------;
Find_another_SubDirectory:
        MOV     DX,[DTA_Ptr]              ;Tell DOS to use different DTA
        MOV     AH,1Ah                    ;DOS set DTA function
        INT     21h

        CMP     FF_Flag,First_time        ;Is this first time in this directory?
        JNE     Find_next                 ;   IF not, do a find next
        MOV     DX,OFFSET Global_str      ;   Else do an initial find on *.*
        MOV     CX,Subdirectory_attrib    ;       find only subdirectories
        MOV     AH,4Eh                    ;       DOS FIND 1st function
        INT     21h
        JMP     SHORT Validate_directory  ;Skip the FIND NEXT function call

Find_Next:                                ;Find the next directory
        MOV     AH,4Fh                    ;DOS find next function
        INT     21h

Validate_directory:                       ;Check that we found valid directory
        JC      Return_to_Mother          ;If find failed, Return to  mother dir
        MOV     DI,[DTA_Ptr]              ;Get address of current DTA

;     Check the file attribute bits in the DTA (is it a directory or a file?)
        TEST    BYTE PTR [DI+21],Subdirectory_attrib  ;Is this a directory?
        JE      Find_Next                 ;If its not a directory keep looking

        ADD     DI,30                     ;Point to directory name in DTA
        CMP     BYTE PTR [DI],'.'         ;IS it a '.' or '..' dir entry?
        JE      Find_Next                 ;   IF so, ignore it and find next dir

        ADD     [DTA_Ptr],43              ;Go down one level + use a new DTA
        MOV     FF_Flag,First_time ;Set flag to do intial *.* dir find

; Change to new daughter directory:
        MOV     DX,DI                     ;point to directory to change to
        MOV     AH,3Bh                    ;DOS change directory function
        INT     21h

        JMP     SHORT EXEC_Next_Command   ;Go execute command in this new Dir

;-----------------------------------------------------------------------------;
; HANDLE EXEC ERROR - Produce specific error messages for the possible DOS    ;
;                     return codes from the EXEC function which may reasonably;
;                     happen as a result of  command or configuration error.  ;
;-----------------------------------------------------------------------------;
Handle_Exec_Error:                       ;Come here if, DOS EXEC func failed
        CMP     AX,02                     ;= file not found/path invalid ?
        JNE     Handle_Exec_A             ;   If not, check other codes
        MOV     DX, OFFSET EXEC_02_Msg    ;   Else put out appropriate error msg
        JMP     Error_Exit                ;   Print error message and terminate
Handle_Exec_A:                            ;Keep checking other possible codes:
        CMP     AX,05                     ;= Access denied return code?
        JNE     Handle_Exec_B             ;   If not, check other codes
        MOV     DX, OFFSET EXEC_05_Msg    ;   Else put out appropriate error msg
        JMP     Error_Exit                ;   Print error message and terminate
Handle_Exec_B:                            ;Keep checking other possible codes:
        CMP     AX,08                     ;= Isufficient memory return code?
        JNE     Unexpected_error          ;   If not, its an unexpected error..
        MOV     DX, OFFSET EXEC_08_Msg    ;   Else put out appropriate error msg
        JMP     Error_Exit                ;   Print error message and terminate
Unexpected_error:                         ;Put out a general catchall error msg
        MOV     DX,OFFSET XALL_fail_msg   ;message for unexpected errors
        JMP     Error_Exit                ;   Print error message and terminate

;-----------------------------------------------------------------------------;
; R E T U R N  T O   M O T H E R  - No more files in prior directory so we    ;
;                                   return to the previous level and find next;
;-----------------------------------------------------------------------------;
Return_to_Mother:                         ;Go up one level to mother directory
        CMP     [DTA_Ptr],OFFSET DTA_Area ;Are we back in original directory?
        JNZ     Return_to_Prior_Dir       ;  If not, set directory back 1 level
        JMP     Normal_termination        ;  If so, we are all done

Return_to_Prior_Dir:
        MOV     DX,OFFSET DotDot_str      ;Point to ".." string (prev dir)
        MOV     AH,3Bh                    ;DOS change dir function
        INT     21h                       ;Change to prior (mother) directory

        SUB     [DTA_Ptr],43              ;Go back and re-use prior DTA
        MOV     FF_Flag,Not_first_time    ;Set flag so we do a find next rather
                                          ;   than a find first. (re-use DTA!)
        JMP     Find_Another_SubDirectory ;Keep checking for DIRs

        PAGE
        SUBTTL General use subroutines
;***************************************************************************;
;**  G e n e r a l    U s e    S u b r o u t i n e s  -    start here     **;
;***************************************************************************;
;-----------------------------------------------------------------------------;
; Get COMSPEC-search the environment for COMSPEC= string (loc of COMMAND.COM) ;
;-----------------------------------------------------------------------------;
Get_COMSPEC:
        MOV     ES,WORD PTR [Environment] ;Segment address of Environment
        XOR     DI,DI                     ;Start at beginning of segment
        MOV     SI,OFFSET COMPSEC_str     ;Point to "compspec="string
        CLD                               ;Forward direction
        XOR     AX,AX                     ;Constant of zero for comparison
Check_Next_ENV_string:
        CMP     BYTE PTR ES:[DI],0        ;If points to zero, no compspec
        JZ      Missing_COMSPEC           ; FATAL error....
        MOV     DX,DI                     ;Save string pointers
        MOV     BP,SI
        MOV     CX,8                      ;COMSPEC= is 8 characters
        REPZ    CMPSB                     ;Check if we are pointing at comspec=
        MOV     DI,DX                     ;Restore the pointers
        MOV     SI,BP
        JZ      Found_COMSPEC             ;We've got a match
        MOV     CX,200h                   ;Search for a max of 512 bytes
        REPNZ   SCASB                     ;Search for zero byte (AL=0)
        JNZ     Missing_COMSPEC           ; FATAL error....
        JMP     SHORT Check_Next_ENV_string  ;Keep looking for environment strin
Missing_COMSPEC:
        MOV     DX,OFFSET Missing_CS_MSG  ;Message to say comspec= missing
        JMP     Error_Exit
Found_COMSPEC:
        ADD     DI,8                      ;Point to string after COMSPEC=
        MOV     WORD PTR [Command_Adr],DI ;Save address:  offset
        MOV     WORD PTR [Command_Adr+2],ES ;                and SEGMENT too
        RET

;---------------------------------------------------------------;
; Create EXEC Parm block- setup the parameter block for DOS EXEC;
;---------------------------------------------------------------;
Create_EXEC_parm_BLOCK:
        MOV     [Parm_Block],ES           ;Environment segment
        MOV     [Parm_Block+4],CS         ;Segment of parameter
        MOV     [Parm_Block+8],CS         ;Segment of FCB #1
        MOV     [Parm_Block+12],CS        ;Segment of FCB #2
        MOV     AX,CS                     ;Point ES back to data segment
        MOV     ES,AX
        RET

;----------------------------------------------------------------------;
; Create Command tail - setup /C followed by user's program to execute ;
;    This program is later executed by invokation of COMMAND.COM       ;
;----------------------------------------------------------------------;
Create_Command_Tail:
; check program to invoke (parameter at 80h) AKA: PSP parameter
        MOV     AL,PSP_PARM               ;Check # of characters in parameter
        OR      AL,AL                     ;Check if no characters (AL=0)
        JNZ     Parameter_detected        ;  If a parameter passed..
        MOV     DX,OFFSET XALL_Help_MSG   ;If no input,put out syntax msg
        JMP     Error_Exit

Parameter_detected:
; Insert a " /C" in front of the PSP parameter
        ADD     AL,3                      ;Increase length by 3 for " /C"
        MOV     [XALL_Parm],AL            ;Store new character count
        MOV     BYTE PTR[XALL_Parm+1],' ' ;Store blank separator
        MOV     WORD PTR[XALL_Parm+2],'C/';Store /C (after this is actual parm)
        RET


;----------------------------------------------------------------------;
; Display XALL message  - Display name of current directory we are     ;
;    executing the user's command in.                                  ;
;----------------------------------------------------------------------;
Display_XALL_MSG:                         ;Display name of current directory
        MOV     DX, offset In_Dir_MSG     ;Begining of "Xing in:" message
        MOV     CX,12                     ;message has 12 chars
        MOV     AH,40h                    ;DOS Write func
        MOV     BX,1                      ;Handle for std output device
        INT     21h                       ;Write Start message
;  Get the current directory string
        MOV     SI,OFFSET Current_Dir     ;Location to store directory string
        XOR     DX,DX                     ;Default drive
        MOV     AH,47h                    ;DOS Get directory function
        INT     21h
;   Scan the string in Current_Dir to determine its length:
        MOV     DI,SI                     ;Dir string start
        MOV     DX,SI                     ;Save a copy for use later
        XOR     AX,AX                     ;Scan for zero termination of string
        MOV     CX,64                     ;Scan up to 64 characters
        REPNE   SCASB                     ;Find 1st zero byte
        SUB     DI,DX                     ;Length of directory string
        MOV     CX,DI                     ;Characters to display in dir string
;DX = start of dir strng, CX = # of chars in string
        MOV     AH,40h                    ;DOS Write func
        INT     21h                       ;Display the actual directory string
        MOV     DX,OFFSET CRLF_str        ;Prepare to put out carriage ret + LF
        MOV     CX,2                      ;Print 2 characters
        MOV     AH,40h                    ;DOS Write func
        INT     21h
        RET

;---------------------------------------------------------------;
; Error Exit - called with DX pointing to error message to send ;
;---------------------------------------------------------------;
Error_Exit:
        MOV     AH,09h                    ;DOS display string function
        INT     21h

        MOV     DX,OFFSET Hit_Key_Msg     ;Ask user to hit any key
        MOV     AH,09h                    ;DOS display string function
        INT     21H

; Wait for user to hit any key
        XOR     AX,AX
        INT     16h                       ;Wait for user to hit a key

        MOV     AX,4C20h                  ;Terminate with 32 errorlevel
        INT     21h                       ;Terminate


Normal_Termination:
;                                         ;Terminate with zero (DOS) errorLEVEL
        MOV     AX,4C00h
        INT     21h

        SUBTTL  Definition of Data Structures
        PAGE
;-----------------------------------------------------------------;
; Data storage definition:                                        ;
;-----------------------------------------------------------------;
Current_Dir  LABEL  BYTE                  ;Overlay copyright msg with this
XALL_CR_msg  DB 'XALL 1.0 - high speed eXecute ALL subdirectories utility.'
             DB CR,LF
             DB  CR,LF,"MIRDIR 1.0 ",BOX," PCDATA TOOLKIT (c) 1990"
             DB  " Ziff Communications Co.",CR,LF
             DB  "PC Magazine ",BOX," Wolfgang Stiller",CR,LF,"$"
XALL_Help_MSG DB CR,LF,'Syntax is: "XALL anyDOScommand"',CR,LF
             DB '   "anyDOScommand" will then be eXecuted in the current and '
             DB 'all ',CR,LF
             DB '   subordinate subdirectories. The program or batch file to'
             DB ' be executed',CR,Lf
             DB '   must be included in the DOS PATH.$'
Missing_CS_Msg LABEL BYTE                 ;A message starting with comspec strg
COMPSEC_str  DB 'COMSPEC='                ;String in environment to search for
             DB ' missing.',CR,LF,'$'
XALL_Fail_msg DB 'XALL failure.',CR,LF
             DB 'Probably due to bad COMSPEC - no COMMAND.COM.',CR,LF,'$'
EXEC_02_Msg  DB 'Unable to find program.','$'
EXEC_05_Msg  DB 'Program access denied.','$'
EXEC_08_Msg  DB 'Too little free mem.','$'
Hit_Key_Msg  DB CR,LF,LF,'Please hit a key.'
CRLF_Str     DB CR,LF,'$'
In_Dir_MSG   DB CR,LF,'Xing in: \'
COMMAND_Adr  DD 00                        ;Full address COMMAND.COM strng in ENV
Global_str   DB '*.*',0                   ;String for find all files
DotDot_str   DB '..',0                    ;String to return to mother directory

Parm_Block   DW 00                        ;Parameter block for DOS EXEC command
             DW XALL_PARM,0
             DW 5Ch,0
             DW 6Ch,0

FF_flag      DB 0                         ;First Find flag (=0 means find 1st)
DTA_ptr      DW     DTA_AREA              ;Pointer to a 43 char DTA block
DTA_area     LABEL  BYTE                  ;area of 32 43 byte DTAs
STACK_Bottom EQU    DTA_area+ 32*43       ;Lower limit of the stack
STACK_TOP    EQU    STACK_Bottom + 80h    ;Allocate 128 byte stack
Program_top  EQU    Stack_top+15          ;End of prog + 15 for paragr rounding

CSEG         EndS
             END    XALL
