page ,132 ; SCCSID = @(#)tbatch2.asm 4.2 85/07/22 ; SCCSID = @(#)tbatch2.asm 4.2 85/07/22 TITLE Batch processing routines part II ;/* ; * Microsoft Confidential ; * Copyright (C) Microsoft Corporation 1991 ; * All Rights Reserved. ; */ ; ; Revision History ; ================ ; ; M020 SR 08/20/89 Changed GetBatByt to check if we ; already reached EOF before trying ; to read from batchfile. Also fixed ; old bug of ds not being setup on an ; error while reading the batchfile. ; ; M037 SR 11/1/90 Bug #1745 & #3438 fixed. On a GOTO, we ; reseek to the beginning of the ; batchfile. Clear the BatchEOF flag ; to indicate that we are no longer at ; EOF. ; .xlist .xcref include comsw.asm include dossym.inc include syscall.inc include comseg.asm include comequ.asm .list .cref DATARES SEGMENT PUBLIC BYTE ;AC000; EXTRN BATCH:WORD EXTRN Batch_Abort:byte EXTRN call_batch_flag:byte EXTRN call_flag:byte EXTRN IFFlag:BYTE EXTRN In_Batch:byte EXTRN Nest:word EXTRN PIPEFILES:BYTE EXTRN RETCODE:WORD EXTRN SINGLECOM:WORD ;;; extrn BatchEOF:byte extrn EchoFlag:byte extrn Next_Batch:word DATARES ENDS TRANDATA SEGMENT PUBLIC BYTE ;AC000; EXTRN BADLAB_PTR:WORD EXTRN BatBufLen:WORD EXTRN IFTAB:BYTE EXTRN SYNTMES_PTR:WORD TRANDATA ENDS TRANSPACE SEGMENT PUBLIC BYTE ;AC000; EXTRN arg:byte ; the arg structure! EXTRN BatBuf:BYTE EXTRN BatBufEnd:WORD EXTRN BatBufPos:WORD EXTRN BATHAND:WORD EXTRN COMBUF:BYTE EXTRN DIRBUF:BYTE EXTRN GOTOLEN:WORD EXTRN if_not_count:word EXTRN IFNOTFLAG:BYTE EXTRN RESSEG:WORD TRANSPACE ENDS TRANCODE SEGMENT PUBLIC BYTE ASSUME CS:TRANGROUP,DS:NOTHING,ES:NOTHING,SS:NOTHING EXTRN cerror:near EXTRN docom1:near EXTRN tcommand:near public $if,iferlev,goto,shift,ifexists,ifnot,forerror,$call Break ; Get one byte from the batch file and return it in AL. End-of-file returns ; and ends batch mode. DS must be set to resident segment. ; AH, DX destroyed. Procedure GETBATBYT,NEAR ASSUME DS:RESGROUP SaveReg test byte ptr [Batch_Abort],-1 jz @f jmp BatEOF @@: TEST Batch,-1 JnZ @f jmp BatEOF @@: PUSH ES MOV ES,Batch ASSUME ES:NOTHING ;M020; ;Check if we have already reached EOF (BatchEOF flag set. Then, we do not ;try to read from the batchfile again. ; cmp es:BatchEOF,0 ;already reached EOF? ;M020 jz not_eof ;no, read batch file ;M020 jmp At_EOF ;yes, no more reads ;M020 not_eof: ;M020 ADD WORD PTR ES:[BatSeek],1 ADC WORD PTR ES:[BatSeek+2],0 POP ES ; ; See if we have bytes buffered... ; MOV AX,CS MOV DS,AX ASSUME DS:TranGroup MOV BX,BatBufPos CMP BX,-1 JNZ UnBuf ; ; There are no bytes in the buffer. Let's try to fill it up. ; MOV DX,OFFSET TranGROUP:BatBuf MOV CX,BatBufLen ; max to read. MOV BX,BatHand MOV AH,READ INT 21h ; Get one more byte from batch file jnc bat_read_ok ;AN022; if no error - continue invoke get_ext_error_number ;AN022; get the error push ds ;AN022; save local segment mov ds,[resseg] ;AN022; get resident segment assume ds:resgroup ;AN022; mov dx,ax ;AN022; put error in DX invoke output_batch_name ;AN022; set up to print the error pop ds ;AN022; assume ds:trangroup ;AN022; invoke std_eprintf ;AN022; print out the error mov byte ptr combuf+2,end_of_line_in;AN022; terminate the batch line for parsing mov byte ptr combuf+3,end_of_line_out ;AN022; terminate the batch line for output ;M020; ;Old bug! We jump to BatEof from here without ds=RESGROUP. Probably, this ;error is never hit (and it shouldn't be) ; mov ds,ResSeg ; ds = RESGROUP ; M020 jmp short bateof ;AN022; terminate the batch file bat_read_ok: ;AN022; MOV CX,AX JCXZ BATEOFDS MOV BatBufEnd,CX XOR BX,BX MOV BatBufPos,BX ; ; Buffered bytes! ; UnBuf: MOV AL,BatBuf[BX] ; get next byte INC BX CMP BX,BatBufEnd ; beyond end of buffer? JB SetBufPos MOV BX,-1 SetBufPos: MOV BatBufPos,BX CMP AL,1AH ; ^Z for termination? jnz GetByteDone ; ;We get here only when we hit an EOF ; BatEOFDS: ;SR; ; HACK!!! A massive hack being put in here to get batch processing to work ;properly on EOF. Previously, a CR was returned and batch processing turned ;off the moment we hit an EOF. Unfortunately, if the last line had no CR-LF, ;batch processing is turned off before the last line is processed and so ;this line would never be executed. ; To fix this, a new flag BatchEOF has been introduced. This flag is ;set to 4 if there is no CR-LF before the EOF -- this is determined by looking ;at the buffer contents. If there is no LF ( we assume that presence of LF ;indicated a CR-LF combination), then we set BatchEOF to 4 and return a ;fake CR to the caller. This decrements BatchEOF. On the next call to this ;routine, BatchEOF is decremented to 2 and a fake lF is returned. On the ;third call, BatchEOF becomes zero and batch processing is turned off, ;now that the last line has been processed. If the EOF is the first char read into the buffer ;during this call, and there was a CR-LF previously, we are going to fake ;another redundant CR-LF. There is no work-around I can think of. ; I would love to restructure this entire routine and its caller to ;make the flow really easy to understand but I guess this will have to wait. ; push es mov es,ResSeg ;SR; ; If we had already set the BatchEOF flag on a previous call (BatchEOF == 2 ;or BatchEOF == 1 now), then do not do the LF check. ; mov es,es:Batch cmp es:BatchEOF,0 jnz crpresent inc es:BatchEOF ;match the dec following mov bx,BatBufEnd cmp BatBuf[bx-1],0ah ;was a LF present? je crpresent ;yes, no need to fake it add es:BatchEOF,3 ;BatchEOF == 4 to fake CR-LF crpresent: ;;; pop es ASSUME DS:TranGroup MOV DS,ResSeg ASSUME DS:ResGroup ;SR; ; The shift operation is done here to replace the decrement. This is because ;we can jump to this label directly from above when bogus calls are made to ;this routine even after batch processing is turned off. The shift ensures ;maintains the following invariance : 4 -> 2; 2 -> 1 ; 1 -> 0; 0 -> 0. Thus, ;it is used as a decrement and also as a NOP to just fall through on bogus ;calls. ; We turn batch processing off if BatchEOF == 1 or BatchEOF == 0. ;BatchEOF == 1 when we fall through from BatEOFDS and BatchEOF == 0 on a ;direct jump to BATEOF. If BatchEOF == 4, we return a fake CR-LF without ;turning batch processing off. ; At_EOF: ;new label added ;M020 shr es:BatchEOF,1 ;decrement the flag jz turn_off ;zero,turn batch off cmp es:BatchEOF,1 jz ret_lf ;BatchEOF was 2, return LF ; ;BatchEOF == 4, indicates return fake CR now and fake LF next. ; mov al,0dh ;return fake CR. pop es jmp short GetByteDone ret_lf: mov al,0ah ;return fake LF pop es jmp short GetByteDone turn_off: pop es BATEOF: invoke BatchOff ;turn batch processing off CALL BATCLOSE ;;; mov BatchEOF,0 ;make sure BatchEOF = 0 ;SR; BugBug ; There is a good reason why this carriage return is being returned here. ;This was part of the old code. Because, ;of the way the caller is structured, a fake CR has to be returned again on ;EOF to ensure the termination of the caller's loop. If echo is on, this ;results in an extra linefeed after the batchfile is run if the last line of ;the batchfile already had a CR-LF. ;NB: Do not confuse this with the faked CR. The fake CR-LF was to mark ;the end-of-line. This CR is to mark the end-of-file. ; MOV AL,0dH ; If end-of-file, then end of line test byte ptr [Batch_Abort],-1 mov byte ptr [Batch_Abort],0 jz Cont_Get_Byt mov di,offset TRANGROUP:COMBUF+2 ; reset pointer to beginning of buffer xor cx,cx ; zero line length jmp short GetByteDone Cont_Get_Byt: CMP [SINGLECOM],0FFF0H ; See if we need to set SINGLECOM JNZ GetByteDone CMP NEST,0 ;G See if we have nested batch files JNZ GETBYTEDONE ;G Yes - don't exit just yet MOV [SINGLECOM],-1 ; Cause termination GetByteDone: RestoreReg return EndProc GetBatByt break <$If - conditional execution> assume ds:trangroup,es:trangroup IFERRORP: POP AX IFERROR: FORERROR: MOV DX,OFFSET TRANGROUP:SYNTMES_ptr JMP CERROR $IF: ; ; Turn off any pipes in progress. ; push ds ;AN004; save local DS mov ds,[resseg] ;AN004; get resident segment assume ds:resgroup ;AN004; cmp [PIPEFILES],0 ;AN004; Only turn off if present. jz IFNoPipe ;AN004; no pipe - continue invoke PipeDel ;AN004; turn off piping IFNoPipe: ;AN004; pop ds ;AN004; get local DS back assume ds:trangroup ;AN004; MOV [IFNOTFLAG],0 mov [if_not_count], 0 MOV SI,81H IFREENT: invoke SCANOFF CMP AL,0DH JZ IFERROR MOV BP,SI MOV DI,OFFSET TRANGROUP:IFTAB ; Prepare to search if table MOV CH,0 IFINDCOM: MOV SI,BP MOV CL,[DI] INC DI JCXZ IFSTRING JMP SHORT FIRSTCOMP IFCOMP: JNZ IF_DIF ;AC000; FIRSTCOMP: LODSB MOV AH,ES:[DI] INC DI CMP AL,AH JZ IFLP OR AH,20H ; Try lower case CMP AL,AH IFLP: LOOP IFCOMP IF_DIF: ;AC000; LAHF ADD DI,CX ; Bump to next position without affecting flags MOV BX,[DI] ; Get handler address INC DI INC DI SAHF JNZ IFINDCOM LODSB CMP AL,0DH IFERRORJ: JZ IFERROR invoke DELIM JNZ IFINDCOM invoke SCANOFF JMP BX IFNOT: NOT [IFNOTFLAG] inc [if_not_count] JMP IFREENT ; ; We are comparing two strings for equality. First, find the end of the ; first string. ; IFSTRING: PUSH SI ; save away pointer for later compare XOR CX,CX ; count of chars in first string FIRST_STRING: LODSB ; get character CMP AL,0DH ; end of line? JZ IFERRORP ; yes => error invoke DELIM ; is it a delimiter? JZ EQUAL_CHECK ; yes, go find equal sign INC CX ; remember 1 byte for the length JMP FIRST_STRING ; go back for more ; ; We have found the end of the first string. Unfortunately, we CANNOT use ; scanoff to find the next token; = is a valid separator and will be skipped ; over. ; EQUAL_CHECK: CMP AL,'=' ; is char we have an = sign? JZ EQUAL_CHECK2 ; yes, go find second one. CMP AL,0DH ; end of line? JZ IFERRORPj ;AC004; yes, syntax error LODSB ; get next char JMP EQUAL_CHECK ; ; The first = has been found. The next char had better be an = too. ; EQUAL_CHECK2: LODSB ; get potential = char CMP AL,'=' ; is it good? jnz iferrorpj ; no, error ; ; Find beginning of second string. ; invoke SCANOFF CMP AL,0DH jz iferrorpj POP DI ; ; DS:SI points to second string ; CX has number of chars in first string ; ES:DI points to first string ; ; Perform compare to elicit match ; REPE CMPSB JZ MATCH ; match found! ; ; No match. Let's find out what was wrong. The character that did not match ; has been advanced over. Let's back up to it. ; DEC SI ; ; If it is EOL, then syntax error ; CMP BYTE PTR [SI],0DH JZ IFERRORJ ; ; Advance pointer over remainder of unmatched text to next delimiter ; SKIPSTRINGEND: LODSB NOTMATCH: CMP AL,0DH IFERRORJ2: JZ IFERRORJ invoke DELIM JNZ SKIPSTRINGEND ; ; Signal that we did NOT have a match ; MOV AL,-1 JMP SHORT IFRET iferrorpj: jmp iferrorp ; ; The compare succeeded. Was the second string longer than the first? We ; do this by seeing if the next char is a delimiter. ; MATCH: LODSB invoke DELIM JNZ NOTMATCH ; not same. XOR AL,AL JMP SHORT IFRET IFEXISTS: ifexist_attr EQU attr_hidden+attr_system moredelim: lodsb ; move command line pointer over invoke delim ; pathname -- have to do it ourselves jnz moredelim ; 'cause parse_file_descriptor is dumb mov DX, OFFSET TRANGROUP:dirbuf trap set_dma mov BX, 2 ; if(0) [|not](|1) exist[1|2] file(2|3) add BX, [if_not_count] mov AX, OFFSET TRANGROUP:arg.argv invoke argv_calc ; convert arg index to pointer mov DX, [BX].argpointer ; get pointer to supposed filename mov CX, ifexist_attr ; filetypes to search for trap Find_First ; request first match, if any jc if_ex_c ; carry is how to determine error xor AL, AL jmp short ifret if_ex_c: mov AL, -1 ; false 'n' fall through... IFRET: TEST [IFNOTFLAG],-1 JZ REALTEST NOT AL REALTEST: OR AL,AL JZ IFTRUE JMP TCOMMAND IFTRUE: invoke SCANOFF MOV CX,SI SUB CX,81H SUB DS:[80H],CL MOV CL,DS:[80H] MOV [COMBUF+1],CL MOV DI,OFFSET TRANGROUP:COMBUF+2 CLD REP MOVSB MOV AL,0DH STOSB ; ; Signal that an IF was done. This prevents the redirections from getting ; lost. ; PUSH DS MOV DS,ResSeg ASSUME DS:RESGROUP MOV IFFlag,-1 POP DS ASSUME DS:TRANGROUP ; ; Go do the command ; JMP DOCOM1 iferrorj3: jmp iferrorj2 IFERLEV: MOV BH,10 XOR BL,BL GETNUMLP: LODSB CMP AL,0DH jz iferrorj3 invoke DELIM JZ GOTNUM SUB AL,'0' XCHG AL,BL MUL BH ADD AL,BL XCHG AL,BL JMP SHORT GETNUMLP GOTNUM: PUSH DS MOV DS,[RESSEG] ASSUME DS:RESGROUP MOV AH,BYTE PTR [RETCODE] POP DS ASSUME DS:TRANGROUP XOR AL,AL CMP AH,BL JAE IFRET DEC AL JMP SHORT IFRET break ; ; Shift the parameters in the batch structure by 1 and set up the new argument. ; This is a NOP if no batch in progress. ; Procedure Shift,NEAR assume ds:trangroup,es:trangroup MOV DS,[RESSEG] ASSUME DS:RESGROUP MOV AX,[BATCH] ; get batch pointer OR AX,AX ; in batch mode? retz ; no, done. MOV ES,AX ; operate in batch segment MOV DS,AX ASSUME DS:NOTHING,ES:NOTHING ; ; Now move the batch args down by 1 word ; MOV DI,BatParm ; point to parm table LEA SI,[DI+2] ; make source = dest + 2 MOV CX,9 ; move 9 parameters REP MOVSW ; SHIFT down ; ; If the last parameter (the one not moved) is empty (= -1) then we are done. ; We have copied it into the previous position ; CMP WORD PTR [DI],-1 ; if last one was not in use then retz ; No new parm ; ; This last pointer is NOT nul. Get it and scan to find the next argument. ; Assume, first, that there is no next argument ; MOV SI,[DI] MOV WORD PTR [DI],-1 ; Assume no parm ; ; The parameters are CR separated. Scan for end of this parm ; SKIPCRLP: LODSB CMP AL,0DH JNZ SKIPCRLP ; ; We are now pointing at next arg. If it is 0 (end of original line) then we ; are finished. There ARE no more parms and the pointer has been previously ; initialized to indicate it. ; CMP BYTE PTR [SI],0 retz ; End of parms MOV [DI],SI ; Pointer to next parm as %9 return EndProc Shift ; ; Skip delim reads bytes from the batch file until a non-delimiter is seen. ; returns char in AL, carry set -> eof ; Procedure SkipDelim,NEAR ASSUME DS:ResGroup,ES:NOTHING TEST Batch,-1 JZ SkipErr ; batch file empty. OOPS! CALL GetBatByt ; get a char invoke Delim ; check for ignoreable chars JZ SkipDelim ; ignore this char. clc return SkipErr: stc return EndProc SkipDelim break $Call ; CALL is an internal command that transfers control to a .bat, .exe, or ; .com file. This routine strips the CALL off the command line, sets ; the CALL_FLAG to indicate a call in progress, and returns control to ; DOCOM1 in TCODE to reprocess the command line and execute the file ; being CALLed. $CALL: ; strip off CALL from command line ASSUME DS:trangroup,ES:trangroup push si push di push ax push cx mov si,offset trangroup:combuf+2 invoke scanoff ;get to first non-delimeter add si,length_call ;point to char past CALL mov di,offset trangroup:combuf+2 mov cx,combuflen-length_call ;get length of buffer rep movsb ;move it pop cx pop ax pop di pop si ; set call flag to indicate call in progress push ds mov ds,[resseg] ASSUME DS:resgroup,ES:resgroup mov call_flag, call_in_progress mov call_batch_flag, call_in_progress ; ; Turn off any pipes in progress. ; cmp [PIPEFILES],0 ; Only turn off if present. jz NoPipe invoke PipeDel NoPipe: pop ds ret break Goto GOTO: assume ds:trangroup,es:trangroup MOV DS,[RESSEG] assume ds:resgroup TEST [BATCH],-1 retz ; If not in batch mode, a nop XOR DX,DX PUSH DS MOV DS,Batch MOV WORD PTR DS:[BatSeek],DX ; Back to start MOV WORD PTR DS:[BatSeek+2],DX ; Back to start ;M037 ; Clear EOF indicator because we have reseeked to the beginning of the file. ; mov ds:BatchEOF,0 ; clear eof indicator ;M037 POP DS GotoOpen: invoke promptBat MOV DI,FCB+1 ; Get the label MOV CX,11 MOV AL,' ' REPNE SCASB JNZ NOINC INC CX NOINC: SUB CX,11 NEG CX MOV [GOTOLEN],CX ; ; At beginning of file. Skip to first non-delimiter char ; CALL SkipDelim JC BadGoto CMP AL,':' JZ CHKLABEL LABLKLP: ; Look for the label CALL GETBATBYT CMP AL,0AH JNZ LABLKTST ; ; At beginning of line. Skip to first non-delimiter char ; CALL SkipDelim JC BadGoto CMP AL,':' JZ CHKLABEL LABLKTST: TEST [BATCH],-1 JNZ LABLKLP BadGoto: CALL BATCLOSE ;SR; ; At this point we are terminating without freeing up any nested batch ;segments i.e if the error occurred within a called batch file. This routine ;will traverse the linked list of batch segments and free all of them. ; call free_batch ;free up nested batch segments PUSH CS POP DS MOV DX,OFFSET TRANGROUP:BADLAB_ptr JMP CERROR ; ; Found the :. Skip to first non-delimiter char ; CHKLABEL: CALL SkipDelim JC BadGoto MOV DI,FCB+1 MOV CX,[GOTOLEN] JMP SHORT GotByte NEXTCHRLP: PUSH CX CALL GETBATBYT POP CX GotByte: INVOKE TESTKANJ ;AN000; 3/3/KK JZ NOTKANJ1 ;AN000; 3/3/KK CMP AL, ES:[DI] ;AN000; 3/3/KK JNZ LABLKTST ;AN000; 3/3/KK INC DI ;AN000; 3/3/KK DEC CX ;AN000; 3/3/KK JCXZ LABLKTST ;AN000; 3/3/KK PUSH CX ;AN000; 3/3/KK CALL GETBATBYT ;AN000; 3/3/KK POP CX ;AN000; 3/3/KK CMP AL, ES:[DI] ;AN000; 3/3/KK JMP SHORT KNEXTLABCHR ;AN000; 3/3/KK NOTKANJ1: ;AN000; 3/3/KK OR AL,20H CMP AL,ES:[DI] JNZ TRYUPPER JMP SHORT NEXTLABCHR TRYUPPER: SUB AL,20H CMP AL,ES:[DI] KNEXTLABCHR: ;AN000; 3/3/KK JNZ LABLKTST NEXTLABCHR: INC DI LOOP NEXTCHRLP CALL GETBATBYT cmp [GOTOLEN],8 ; Is the label atleast 8 chars long? jge gotocont ; Yes, then the next char doesn't matter CMP AL,' ' JA LABLKTST gotocont: CMP AL,0DH JZ SKIPLFEED TONEXTBATLIN: CALL GETBATBYT CMP AL,0DH JNZ TONEXTBATLIN SKIPLFEED: CALL GETBATBYT ;SR; ; The BatchEOF flag is set in GetBatByt to indicate that we are faking a ;CR-LF for the last line. On a goto, this flag has to be cleared, because ;BatchEOF == 1 now, after returning a CR-LF. The next call to GetBatByt ;to get the EOF has not been made yet because we encountered the Goto. On ;all other cases, EOF will be hit while trying to read the next line and ;we are fine. ; push es mov es,Batch mov es:BatchEOF,0 ;invalidate fake CR-LF flag pop es CALL BatClose return Procedure BatClose,NEAR assume ds:resgroup MOV BX,CS:[BATHAND] CMP BX,5 JB CloseReturn MOV AH,CLOSE INT 21h CloseReturn: mov byte ptr [In_Batch],0 ; reset flag return EndProc BatClose ; ; Open the BATCH file, If open fails, AL is drive of batch file (A=1) ; Also, fills internal batch buffer. If access denied, then AX = -1 ; Procedure BatOpen,NEAR ASSUME DS:RESGROUP,ES:TRANGROUP PUSH DS MOV DS,[BATCH] ASSUME DS:NOTHING MOV DX,BatFile MOV AX,OPEN SHL 8 INT 21h ; Open the batch file JC SETERRDL MOV DX,WORD PTR DS:[BatSeek] MOV CX,WORD PTR DS:[BatSeek+2] POP DS ASSUME DS:RESGROUP MOV [BATHAND],AX MOV BX,AX MOV AX,LSEEK SHL 8 ; Go to the right spot INT 21h MOV BatBufPos,-1 ; nuke batch buffer position return SETERRDL: MOV BX,DX invoke get_ext_error_number ;AN022; get the extended error mov dx,ax ;AN022; save extended error in DX MOV AL,[BX] ; Get drive spec SUB AL,'@' ; A = 1 POP DS STC ; SUB mucked over carry return EndProc BatOpen ; ;Free_batch : This routine traverses the linked batch segments freeing all ;the batch and FOR segments until all of them are freed. It also restores ;the old state of the EchoFlag. ; ; ENTRY: ds = RESGROUP ; ; EXIT: All batch & FOR segments freed. ; EchoFlag restored to old state before batch process. ; ; REGISTERS AFFECTED: bx, cx free_batch proc near assume ds:RESGROUP,es:nothing push es mov bx,Next_Batch or bx,bx jz fb_ret ClearBatch: mov es,bx ; get batch segment mov bx,es:BatForPtr ; get old FOR segment cmp bx,0 ; is a FOR in progress je no_bat_for ; no - don't deallocate push es ; mov es,bx ; yes - free it up... mov ah,DEALLOC ; int 21h ; pop es ; restore to batch segment No_Bat_For: mov cl,es:BatEchoFlag ; get old echo flag mov bx,es:BatLast ; get old batch segment mov ah,DEALLOC ; free it up... int 21h mov Batch,bx ; get ready to deallocate next batch dec nest ; is there another batch file? jnz ClearBatch ; keep going until no batch file mov EchoFlag,cl ;restore echo status mov Batch,0 ;no batch process in progress fb_ret: pop es ret free_batch endp TRANCODE ENDS END