        page ,132
TITLE   Batch processing routines

__TONYI__ = 1		; Fake int for CLOSE and SetVect

;
;                        IBM Confidential
;                        Copyright (C) IBM Corporation 1991-1993
;                        All Rights Reserved.

;
;       Revision History
;       ================
;
;       M006    SR      07/20/90        Changed BatCom to understand batch
;                               segments in UMBs. Check only for
;                               overlap.
;       M017    MD      08/10/90        Eliminate extra DEC, to fix bug #1
;
;       M037    SR      11/1/90 Bug #1745 & #3438 fixed. Fixed ReadBat
;                               to check if we have hit EOF on
;                               batchfile and if so, just clear everything
;                               and return finishing batch processing.
;

.xlist
.xcref
        include comsw.asm
        include dossym.inc
        include syscall.inc
        include comseg.asm
        include comequ.asm
        include doscntry.inc            ;an000;
.list
.cref


DATARES         SEGMENT PUBLIC BYTE     ;AC000;
        EXTRN   BATCH:WORD
        EXTRN   Batch_Abort:byte
        EXTRN   call_batch_flag:byte
        EXTRN   ECHOFLAG:BYTE
        EXTRN   forflag:byte
        EXTRN   forptr:word
        EXTRN   IFFlag:BYTE
        EXTRN   In_Batch:byte
        EXTRN   LTPA:WORD
        EXTRN   Nest:word
        EXTRN   next_batch:word
        EXTRN   nullflag:byte
        EXTRN   PIPEFLAG:BYTE
        EXTRN   RES_TPA:WORD
        EXTRN   SINGLECOM:WORD
        EXTRN   SUPPRESS:BYTE           ;AC000;
DATARES ENDS

TRANDATA        SEGMENT PUBLIC BYTE     ;AC000;
        EXTRN   BADBAT_PTR:WORD
        EXTRN   Extend_buf_ptr:word     ;AC000;
        EXTRN   Extend_buf_sub:byte     ;AN022;
        EXTRN   msg_disp_class:byte     ;AC000;
        EXTRN   NEEDBAT_PTR:WORD
        EXTRN   pausemes_ptr:word       ;AC000;
TRANDATA        ENDS

TRANSPACE       SEGMENT PUBLIC BYTE     ;AC000;
        EXTRN   BatBufPos:WORD
        EXTRN   BATHAND:WORD
        EXTRN   bwdbuf:byte             ;AN022;
        EXTRN   BYTCNT:WORD
        EXTRN   COMBUF:BYTE
        EXTRN   EXECPATH:BYTE
        EXTRN   ID:BYTE
        EXTRN   RCH_ADDR:DWORD
        EXTRN   RESSEG:WORD
        EXTRN   string_ptr_2:word       ;AC000;
        EXTRN   TPA:WORD
        EXTRN   TRAN_TPA:WORD

        extrn   TranSpaceEnd:byte       ; M006
TRANSPACE       ENDS

TRANCODE        SEGMENT PUBLIC BYTE

ASSUME  CS:TRANGROUP,DS:NOTHING,ES:NOTHING,SS:NOTHING

        EXTRN   cerror:near
        EXTRN   tcommand:near

;---------------

TRANSPACE       SEGMENT PUBLIC BYTE     ;AC000;
    extrn   arg:byte                    ; the arg structure!
transpace   ends
;---------------

ifdef __TONYI__
;------------------------------ TONYI ----------------------------------
; Sam Detweiler's damn REXX88 program *DEPENDS* on the value that
; *HAPPENS* to be in ES:BX when COMMAND.COM opens a BAT file.  When 
; we made "open" calls with ES == 0 this hosed REXX88.  Lord knows
; who else is making hosey assumptions like this for Int 21 calls
; comming from COMMAND.COM, so this version preserves all the registers
; that were present (except SP of course).
;-----------------------------------------------------------------------
; This code is a lot uglier than the previous stuff, but it *WILL* do
; the job of avoiding the GP faults.  Hopefully, the 8088's and 286's
; don't get hurt too bad...  It's going to hurt them some...but I think 
; the new "smarter" handle closing logic during BAT file processing
; makes up for this and they wind up being faster anyway in spite of 
; this ugliness.
;------------------------------ TONYI ----------------------------------
FakeInt21 label near			; No need for separate  DS/ES
	public	FakeInt21		;  versions now.  This is a 
FakeInt21_DS label near			;   one-size-fits-all method now.
	public	FakeInt21_DS		; 

	push	ds			; Save DS
	pushf				; PUSHF for simulated interrupt
	push	bx			; Zero   (borrow
	xor	bx, bx			;  out		BX
	mov	ds, bx			;   DS		  for
	pop	bx			;   		     a bit)
	push	cs			; DOS's RET 2 return segment
	call	foobar2			; DOS's RET 2 return offset
; The return from DOS comes back here.........
; The stack has beed "diddled" previously such that our real return
; address is on top now, but we're off by a word.  The RET 2 will
; strip this word off on return.  We NEED to do it this way (rather
; than a simple ADD SP, 2 so that we DON'T touch the flags returned
; by DOS from the Int 21 call!!!
	ret     2			; Return from FakeInt21[_DS]
foobar2: 
	push	ds:[(21h * 4) + 2]	; Segment of Int 21 entry
	push	ds:[(21h * 4)]		; Offset of Int 21 entry
	push	bp			; Get the
	mov	bp, sp			;  original
	mov	ds, [bp+12]		;   DS back
	push	[bp+14]			; Diddle our return on the stack
	pop	[bp+12]			;  (overlay saved DS with ret addr)
	pop	bp			;
	cli				; Ints off
	retf				; Call *Int 21* via RETF

FakeInt2F label near
	public	FakeInt2F

	push	ds
	push	bx
	xor	bx, bx
	mov	ds, bx
	pop	bx
	pushf
	cli
	call	dword ptr ds:[2Fh *4]
	pop	ds
	retn
endif

Break   <PromptBat - Open or wait for batch file>

;
; Open the batch file.  If we cannot find the batch file.  If the media is
; changeable, we prompt for the change.  Otherwise, we terminate the batch
; file.  Leave segment registers alone.
;

Procedure   PromptBat,NEAR
        ASSUME  DS:ResGroup,ES:NOTHING
        invoke  BATOPEN                         ; attempt to open batch file
        retnc
        cmp     dx,error_file_not_found         ;AN022; Ask for diskette if file not found
        jz      Bat_Remcheck                    ;AN022;
        cmp     dx,error_path_not_found         ;AN022; Ask for diskette if path not found
        jz      Bat_Remcheck                    ;AN022;    Otherwise, issue message and exit
        invoke  output_batch_name               ;AN022; set up batch name in bwdbuf
        jmp     short BatDie                    ;AN022;

Bat_Remcheck:                                   ;AN022; Go see if media is removable
        CALL    [RCH_ADDR]                      ; DX has error number
        JZ      AskForBat                       ; Media is removable
;
; The media is not changeable.  Turn everything off.
;
        invoke  ForOff
        invoke  PipeOff
        MOV     IfFlag,AL                       ; No If in progress.
        MOV     DX,OFFSET TRANGROUP:BADBAT_ptr

BatDie:
        call    BatchOff
        PUSH    CS
        POP     DS
        ASSUME  DS:TranGroup
        invoke  std_eprintf                     ;AC022; display message

;
; TCOMMAND resets the stack.  This is the equivalent of a non-local goto.
;
        JMP     TCOMMAND                        ; he cleans off stack

;
; Ask the user to reinsert the batch file
;
ASKFORBAT:
        ASSUME  DS:ResGroup
        PUSH    DS
        PUSH    CS
        POP     DS
        ASSUME  DS:TranGroup
        MOV     DX,OFFSET TRANGROUP:NEEDBAT_ptr  ;AN022;
        invoke  std_eprintf                      ;Prompt for batch file on stderr
        mov     dx,offset trangroup:pausemes_ptr ;AN000; get second part of message
        invoke  std_eprintf                      ;AN000; print it to stderr
        CALL    GetKeystroke
        POP     DS
        ASSUME  DS:ResGroup
        jmp     SHORT PromptBat                                       ;AC000;
EndProc PromptBat

;****************************************************************
;*
;* ROUTINE:     Output_batch_name
;*
;* FUNCTION:    Sets up batch name to be printed on extended error
;*
;* INPUT:       DX - extended error number
;*
;* OUTPUT:      Ready to call print routine
;*
;****************************************************************

public  output_batch_name                       ;AN022;

Output_batch_name    proc near                  ;AN022;

        push    ds                              ;AN022; save resident segment
        mov     ds,[batch]                      ;AN022; get batch file segment
assume  DS:nothing                              ;AN022;
        mov     SI,BatFile                      ;AN022; get offset of batch file
        invoke  dstrlen                         ;AN022; get length of string
        mov     di,offset Trangroup:bwdbuf      ;AN022; target for batch name
        rep     movsb                           ;AN022; move the name

        push    cs                              ;AN022; get local segment
        pop     ds                              ;AN022;
assume  DS:trangroup                            ;AN022;
        mov     extend_buf_ptr,dx               ;AN022; put message number in block
        mov     msg_disp_class,ext_msg_class    ;AN022; set up extended error msg class
        mov     dx,offset TranGroup:Extend_Buf_ptr   ;AN022; get extended message pointer
        mov     string_ptr_2,offset trangroup:bwdbuf ;AN022; point to substitution
        mov     extend_buf_sub,one_subst        ;AN022; set up for one subst
        pop     ds                              ;AN022; restore data segment

        ret                                     ;AN022; return

Output_batch_name    endp                       ;AN022;

Break   <GetKeystroke - get a keystroke and flush queue>

;
; read the next keystroke.  Since there may be several characters in the queue
; after the one we ask for (function keys/Kanji), we need to flush the queue
; AFTER waiting.
;
Procedure   GetKeyStroke,NEAR
;
; read any character at any mode, interim mode or not.
;
        assume  ds:trangroup

        PUSH    DX                                      ;AN000;  3/3/KK
        MOV     AX,(ECS_call SHL 8) OR GetInterimMode   ;AN000;  3/3/KK
        INT     21h                             ;AN000;  3/3/KK
        PUSH    DX                                      ;AN000;  save interim state 3/3/KK
        MOV     AX,(ECS_call SHL 8) OR SetInterimMode   ;AN000;  3/3/KK
        MOV     DL,InterimMode                          ;AN000;  3/3/KK
        INT     21h                             ;AN000;  3/3/KK

        MOV     AX,(STD_CON_INPUT_FLUSH SHL 8) OR STD_CON_INPUT_no_echo
        INT     21h             ; Get character with KB buffer flush
        MOV     AX,(STD_CON_INPUT_FLUSH SHL 8) + 0
        INT     21h

        MOV     AX,(ECS_call SHL 8) OR SetInterimMode   ;AN000;  3/3/KK
        POP     DX                                      ;AN000;  restore interim state 3/3/KK
        INT     21h                             ;AN000;  3/3/KK
        POP     DX                                      ;AN000;  3/3/KK

        return
EndProc GetKeyStroke

Break   <ReadBat - read 1 line from batch file>

;
; ReadBat - read a single line from the batch file.  Perform all substitutions
; as appropriate
;

Procedure   ReadBat,NEAR
        ASSUME  DS:ResGroup,ES:TranGroup
        mov     suppress,yes_echo               ;g initialize line suppress status
        test    byte ptr [Batch_Abort],-1
        jnz     Trying_To_Abort
        mov     byte ptr [In_Batch],1           ; set flag to indicate batch job
;
;M037; Start of changes
; We check here if we have set the flag indicating that the batchfile is at
;EOF. In this case, we do not want to continue with the normal processing.
;We call GetBatByt once more so that the batch segment gets freed up, the
;batch file gets closed etc. and then return as if everything is done.
;
        push    ds
        mov     ds,Batch
        cmp     ds:BatchEOF,0           ; are we at EOF in batchfile
        pop     ds
        jz      contbat         ; no, continue normal processing
        invoke  GetBatByt               ; frees up batchseg
        mov     es:ComBuf+2,al          ; stuff CR into command buffer
                                        ; as a dummy command
        invoke  CrLf2                   ; print a CR-LF
        return                          ; done batch processing
contbat:
;
;M037; End of changes
;

        CALL    PromptBat

Trying_To_Abort:
        MOV     DI,OFFSET TRANGROUP:COMBUF+2

;
; Save position and try to scan for first non delimiter.
;

TESTNOP:
        MOV     AX,DS
        MOV     DS,Batch
        ASSUME  DS:NOTHING
        PUSH    WORD PTR DS:[BatSeek]
        PUSH    WORD PTR DS:[BatSeek+2]         ; save current location.
        MOV     DS,AX
        ASSUME  DS:ResGroup
        invoke  SkipDelim                       ; skip to first non-delim
;
; If the first non-delimiter is not a :  (label), we reseek back to the
; beginning and read the line.
;
        CMP     AL,':'                          ; is it a label?
        POP     CX
        POP     DX                              ; restore position in bat file
        JZ      NopLine                         ; yes, resync everything.
        TEST    [BATCH],-1                      ; are we done with the batch file?
        JZ      RdBat

        CMP     AL, NO_ECHO_CHAR                ;g see if user wants to suppress line
        JNZ     SET_BAT_POS                     ;g no - go and set batch file position
        MOV     SUPPRESS, NO_ECHO               ;g yes set flag to indicate
        jmp     short Rdbat                     ;g go read batch file

SET_BAT_POS:                                    ;g
        PUSH    DS
        MOV     DS,Batch
        ASSUME  DS:NOTHING
        MOV     WORD PTR DS:[BatSeek],DX        ; reseek back to beginning
        MOV     WORD PTR DS:[BatSeek+2],CX
        POP     DS
        ASSUME  DS:ResGroup
        MOV     AX,(LSEEK SHL 8) + 0            ; seek back
ifdef __TONYI__
	call	FakeInt21
else
        INT     21h
endif
        MOV     BatBufPos,-1                    ; nuke batch buffer position
        xor     cx,cx                           ; Initialize line length to zero
        JMP     SHORT RdBat

;
; The first non-delimiter is a :.  This line is not echoed and is ignored.
; We eat characters until a CR is seen.
;

NOPLINE:
        CALL    SkipToEOL
        invoke  GetBatByt                       ; eat trailing LF
        TEST    [BATCH],-1                      ; are we done with the batch file?
        JNZ     TESTNOP                         ; no, go get another line
        return                                  ; Hit EOF

;
; Read a line into the buffer pointed to by ES:DI.  If any %s are seen in the
; input, we are to consider two special cases:
;
;   %0 to %9    These represent replaceable parameters from the batch segment
;   %sym%       This is a symbol from the environment
;

RDBAT:
        invoke  GetBatByt
        inc     cx                              ; Inc the line length

        invoke  testkanj                ; MSKK04 07/14/89
        jz      rdbat1                  ;
        cmp     cx, COMBUFLEN-1         ;
        jae     TooLong                 ; can't start DBCS char at last pos'n
        stosb                           ;
        invoke  GetBatByt               ;
        inc     cx                      ;
        jmp     short SAVBATBYT         ;
rdbat1:                                 ;

        cmp     cx,COMBUFLEN                    ; Is it too long?
        jae     TooLong                         ; Yes - handle it, handle it
;
; See if we have a parameter character.
;
        CMP     AL,'%'                          ; Check for parameter
        JZ      NEEDPARM
;
; no parameter character.  Store it as usual and see if we are done.
;

SAVBATBYT:
        STOSB
        CMP     AL,0DH                          ; End of line found?
        JNZ     RDBAT                           ; no, go for more
;
; We have read in an entire line.  Decide whether we should echo the command
; line or not.
;

Found_EOL:
        SUB     DI,OFFSET TRANGROUP:COMBUF+3
        MOV     AX,DI                           ; remember that we've not counted the CR
        MOV     ES:[COMBUF+1],AL                ; Set length of line
        invoke  GetBatByt                       ; Eat linefeed
        invoke  BATCLOSE
        CMP     SUPPRESS, NO_ECHO               ;G
        JZ      Reset                           ;G
        test    [echoflag],1                    ; To echo or not to echo, that is the
        jnz     try_nextflag

Reset:
        PUSH    CS                              ;  question.  (Profound, huh?)
        POP     DS                              ; Go back to local segment
        retz                                    ; no echoing here...
;
; Echo the command line with appropriate CRLF...
;


try_nextflag:
        cmp     nullflag,nullcommand            ;G was there a command last time?
        jz      No_crlf_print                   ;G no - don't print crlf
        invoke  CRLF2                           ;G  Print out prompt

no_crlf_print:
        invoke  PRINT_PROMPT                    ;G
        PUSH    CS                              ;G change data segment
        POP     DS                              ;G

ASSUME DS:TRANGROUP
        mov     dx,OFFSET TRANGROUP:COMBUF+2    ; get command line for echoing
        invoke  CRPRINT
        invoke  CRLF2
        return
;
; The line was too long.  Eat remainder of input text up until the CR
;
TooLong:
        ASSUME  DS:ResGroup
        cmp     al,0dh                          ; Has the end of the line been reached?
        jz      Ltlcont                         ; Yes, continue
        CALL    SkipToEOL                       ; Eat remainder of line

Ltlcont:
        stosb                                   ; Terminate the command
        jmp     SHORT Found_EOL    ; Go process the valid part of the ;AC000;
;
; We have found a parameter lead-in character.  Check for the 0-9 case first
;

NEEDPARM:
        invoke  GetBatByt                       ; get next character
        CMP     AL,'%'                          ; Check for two consecutive %
        JZ      SAVBATBYT                       ; if so, replace with a single %
        CMP     AL,0Dh                          ; Check for end-of-line
        JZ      SAVBATBYT                       ; yes, treat it normally
;
; We have found %<something>.  If the <something> is in the range 0-9, we
; retrieve the appropriate parameter from the batch segment.  Otherwise we
; see if the <something> has a terminating % and then look up the contents
; in the environment
;
PAROK:
        SUB     AL,'0'
        JB      NEEDENV                         ; look for parameter in the environment
        CMP     AL,9
        JA      NEEDENV
;
; We have found %<number>.  This is taken from the parameters in the
; allocated batch area.
;
        CBW
        MOV     BX,AX                           ; move index into AX
        SHL     BX,1                            ; convert word index into byte ptr
        SaveReg <ES>
        MOV     ES,Batch
;
; The structure of the batch area is:
;
;   BYTE    type of segment
;   DWORD   offset for next line
;   10 WORD pointers to parameters.  -1 is empty parameter
;   ASCIZ   file name (with . and ..)
;   BYTES   CR-terminated parameters
;   BYTE    0 flag to indicate end of parameters
;
; Get pointer to BX'th argument
;
        MOV     SI,ES:BatParm[BX]
        RestoreReg  <ES>
;
; Is there a parameter here?
;
        CMP     SI,-1                           ; Check if parameter exists
        JNZ     Yes_there_is                    ;G Yes go get it
        JMP     RDBAT                           ; Ignore if it doesn't
;
; Copy in the found parameter from batch segment
;

Yes_there_is:
        PUSH    DS
        MOV     DS,Batch
        ASSUME  DS:NOTHING
        dec     cx                              ; Don't count '%' in line length

CopyParm:
        LODSB                                   ; From resident segment
        CMP     AL,0DH                          ; Check for end of parameter
        JZ      EndParam
        inc     cx                              ; Inc the line length
        cmp     cx,COMBUFLEN                    ; Is it too long?
        jae     LineTooL                        ; Yes - handle it, handle it
        STOSB
        JMP     SHORT CopyParm                                        ;AC000;
;
; We have copied up to the limit.  Stop copying and eat remainder of batch
; line.  We need to make sure that the tooLong code isn't fooled into
; believing that we are at EOL.  Clobber AL too.
;

LineTooL:
        XOR     AL,AL
        POP     DS
        ASSUME  DS:RESGROUP
        JMP     SHORT TooLong                                         ;AC000;
;
; We have copied in an entire parameter.  Go back for more
;

EndParam:
        POP     DS
        JMP     RDBat
;
; We have found % followed by something other than 0-9.  We presume that there
; will be a following % character.  In between is an environment variable that
; we will fetch and replace in the batch line with its value.
;

NEEDENV:
        dec     cx                              ;AN070; Don't count "%"
        SaveReg <DS,DI>
        MOV     DI,OFFSET TRANGROUP:ID          ; temp spot for name
        ADD     AL,'0'                          ; reconvert character
        STOSB                                   ; store it in appropriate place
;
; loop getting characters until the next % is found or until EOL
;

GETENV1:
        invoke  GetBatByt                       ; get the byte
        STOSB                                   ; store it
        CMP     AL,0Dh                          ; EOL?
        JNZ     GETENV15                        ; no, see if it the term char
;
; The user entered a string with a % but no trailing %.  We copy the string.
;
        mov     byte ptr es:[di-1],0            ; nul terminate the string
        mov     si,offset TranGroup:ID          ; point to buffer
        pop     di                              ; point to line buffer
        push    cs
        pop     ds
        call    StrCpy
        jc      LineTooL
        pop     ds
        jmp     SavBatByt

GETENV15:
        CMP     AL,'%'                          ; terminating %?
        JNZ     GETENV1                         ; no, go suck out more characters

; M017 - following DEC is wrong, because we replace the % with a = here.
; This was the source of bug #1.
;       dec     cx                              ;AN070; Don't count "%"

        mov     al,'='                          ; terminate  with =
        MOV     ES:[DI-1],al
;
; ID now either has a =-terminated string which we are to find in the
; environment or a non =-terminated string which will not be found in the
; environment.
;
GETENV2:
        MOV     SI,OFFSET TRANGROUP:ID
        PUSH    CS
        POP     DS                              ; DS:SI POINTS TO NAME
        ASSUME DS:TRANGROUP
        PUSH    CX
        INVOKE  FIND_NAME_IN_environment
        ASSUME ES:RESGROUP
        POP     CX
        PUSH    ES
        POP     DS
        assume ds:resgroup
        PUSH    CS
        POP     ES
        ASSUME ES:TRANGROUP
        MOV     SI,DI
        POP     DI                              ; get back pointer to command line
;
; If the parameter was not found,  there is no need to perform any replacement.
; We merely pretend that we've copied the parameter.
;
        JC      GETENV6
;
; ES:DI points to command line being built
; DS:SI points either to nul-terminated environment object AFTER =
;

        ASSUME  ES:NOTHING
        call    StrCpy                          ; (let RdBat handle overflow)
GETENV6:
        pop     ds
        JMP     RDBAT                           ; no, go back to batch file

EndProc ReadBat

;
;   SkipToEOL - read from batch file until end of line
;

Procedure   SkipToEOL,NEAR

        ASSUME  DS:ResGroup,ES:NOTHING

        TEST    Batch,-1
        retz                                    ; no batch file in effect
        invoke  GetBatByt
        CMP     AL,0Dh                          ; eol character?
        JNZ     SkipToEOL                       ; no, go eat another
        return

EndProc SkipToEOL

Break   <Allocate and deallocate the transient portion>

;
; Free Transient.  Modify ES,AX,flags
;

Procedure   Free_TPA,NEAR

ASSUME  DS:TRANGROUP,ES:RESGROUP

        PUSH    ES
        MOV     ES,[RESSEG]
        MOV     ES,[RES_TPA]
        MOV     AH,DEALLOC
ifdef __TONYI__
	call	FakeInt21_DS
else
        INT     21h                     ; Make lots of free memory
endif
        POP     ES

        return

EndProc Free_TPA

;
; Allocate transient.  Modify AX,BX,DX,flags
;

Procedure   Alloc_TPA,NEAR

ASSUME DS:TRANGROUP,ES:RESGROUP

        PUSH    ES
        MOV     ES,[RESSEG]
        MOV     BX,0FFFFH                       ; Re-allocate the transient
        MOV     AH,ALLOC
ifdef __TONYI__
	call	FakeInt21_DS
else
        INT     21h
endif
        PUSH    BX                              ; Save size of block
        MOV     AH,ALLOC
ifdef __TONYI__
	call	FakeInt21
else
        INT     21h
endif
;
; Attempt to align TPA on 64K boundary
;
        POP     BX                              ; Restore size of block
        MOV     [RES_TPA], AX                   ; Save segment to beginning of block
        MOV     [TRAN_TPA], AX
;
; Is the segment already aligned on a 64K boundary
;
        MOV     DX, AX                          ; Save segment
        AND     AX, 0FFFH                       ; Test if above boundary
        JNZ     Calc_TPA
        MOV     AX, DX
        AND     AX, 0F000H                      ; Test if multiple of 64K
        JNZ     NOROUND

Calc_TPA:
        MOV     AX, DX
        AND     AX, 0F000H
        ADD     AX, 01000H                      ; Round up to next 64K boundary
        JC      NOROUND                         ; Memory wrap if carry set
;
; Make sure that new boundary is within allocated range
;
        MOV     DX, [RES_TPA]
        ADD     DX, BX                          ; Compute maximum address
        CMP     DX, AX                          ; Is 64K address out of range?
        JB      NOROUND
;
; Make sure that we won't overwrite the transient
;
        MOV     BX, CS                          ; CS is beginning of transient
        CMP     BX, AX
        JB      NOROUND
;
; The area from the 64K boundary to the beginning of the transient must
; be at least 64K.
;
        SUB     BX, AX
        CMP     BX, 4096                        ; Size greater than 64K?
        JAE     ROUNDDONE

NOROUND:
        MOV     AX, [RES_TPA]

ROUNDDONE:
        MOV     [LTPA],AX                       ; Re-compute everything
        MOV     [TPA],AX
        MOV     BX,AX
        MOV     AX,CS
        SUB     AX,BX
        PUSH    BX
        MOV     BX,16
        MUL     BX
        POP     BX
        OR      DX,DX
        JZ      SAVSIZ2
        MOV     AX,-1

SAVSIZ2:
;
; AX is the number of bytes free in the buffer between the resident and the
; transient with a maximum of 64K-1.  We round this down to a multiple of 512.
;
        CMP     AX,512
        JBE     GotSize
        AND     AX,0FE00h                       ; NOT 511 = NOT 1FF

GotSize:
        MOV     [BYTCNT],AX
        POP     ES

        return

EndProc Alloc_TPA

Break   <BatCom - enter a batch file>

;
; The exec search has determined that the user has requested a batch file for
; execution.  We parse the arguments, create the batch segment, and signal
; batch processing.
;
Procedure   BatCom,NEAR

ASSUME  DS:TRANGROUP, ES:NOTHING

;
; Batch parameters are read with ES set to segment of resident part
;

        MOV     ES,[RESSEG]
ASSUME  ES:RESGROUP
        cmp     es:[call_batch_flag],call_in_progress ;AN043; If in CALL,
        jz      skip_ioset                      ;AN043;   redirection was already set up
        invoke  IOSET                           ; Set up any redirection

skip_ioset:                                     ;AN043;
        CALL    FREE_TPA                        ; G
        cmp     es:[call_batch_flag],call_in_progress ;G
        jz      getecho                         ; G if we're in a call, don't execute
;
; Since BATCH has lower precedence than PIPE or FOR.  If a new BATCH file is
; being started it MUST be true that no FOR or PIPE is currently in progress.
; Don't execute if in call
;
        invoke  ForOff

getecho:
        invoke  PipeOff
        mov     al,EchoFlag                     ; preserve echo state for chaining

        and     al, 1                           ; Save current echo state
        push    ax

        xor     ax,ax                           ;G
        test    es:[batch],-1                   ;G  Are we in a batch file?
        jz      leavebat                        ;G  No, nothing to save
        mov     ax,es:[batch]                   ;G get current batch segment
        cmp     es:[call_batch_flag],call_in_progress  ;G
        jz      leavebat                        ;G
;
;  We are in a chained batch file, save batlast from previous batch segment
;  so that if we're in a CALL, we will return to the correct batch file.
;
        push    es                              ;G
        mov     es,ax                           ;G get current batch segment
        mov     ax,es:[batlast]                 ;G get previous batch segment
        pop     es                              ;G

leavebat:                                       ;G
        push    ax                              ;G keep segment until new one created
        cmp     es:[call_batch_flag],call_in_progress  ;G are we in a CALL?
        jz      startbat                        ;G Yes, keep current batch segment
        call    BatchOff                        ;G No, deallocate old batch segment

;
; Find length of batch file
;

startbat:                                       ;G
        ASSUME  ES:RESGROUP
        MOV     es:[CALL_BATCH_FLAG], 0         ;G  reset call flag
        mov     SI, OFFSET TRANGROUP:EXECPATH

        mov     ax,AppendTruename               ;AN042; Get the real path where the batch file
        int     2fh                             ;AN042;    was found with APPEND
        mov     ah,Find_First                   ;AN042; The find_first will return it
        mov     dx,si                           ;AN042; Get the string
        mov     cx,search_attr                  ;AN042; filetypes to search for
ifdef __TONYI__
	call	FakeInt21
else
        int     21h                     ;AN042;
endif

        invoke  DStrLen
;
; Allocate batch area:
;   BYTE    type of segment
;   WORD    segment of last batch file
;   WORD    segment for FOR command
;   BYTE    FOR flag state on entry to batch file
;   DWORD   offset for next line
;   10 WORD pointers to parameters.  -1 is empty parameter
;   ASCIZ   file name (with . and ..)
;   BYTES   CR-terminated parameters
;   BYTE    0 flag to indicate end of parameters
;
; We allocate the maximum size for the command line and use setblock to shrink
; later when we've squeezed out the extra
;

        MOV     BX,CX                           ; length of file name.
        ADD     BX,0Fh + (SIZE BatchSegment) + COMBUFLEN + 0Fh
                                                ; structure + max len + round up
        SaveReg <CX>
        MOV     CL,4
        SHR     BX,CL                           ; convert to paragraphs
        PUSH    BX                              ;G save size of batch segment
        MOV     AH,ALLOC
ifdef __TONYI__
	call	FakeInt21
else
        INT     21h                     ; Allocate batch segment
endif
        POP     BX                              ;G get size of batch segment
;
; This should *NEVER* return an error.  The transient is MUCH bigger than
; the batch segment.  This may not be true, however, in a multitasking system.
; G This error will occur with nesting of batch files.  We also need to
; G make sure that we don't overlay the transient.
;
        jc      mem_error                       ;G not enough memory - exit
        push    ax                              ;G save batch segment
        add     ax,bx                           ;G get end of batch segment
        add     ax,20h                          ;G add some tpa work area
        mov     bx,cs                           ;G get the transient segment
;
; M006; We cant check just for above. If the batchseg goes into a UMB, the
; M006; batchseg is always above the transient. We need to change this code
; M006; to only check for an overlap
;
        mov     dx,offset TRANGROUP:TranSpaceEnd ; M006
        add     dx,15                           ;round up para; M006
        shr     dx,cl                           ;para size of transient; M006
        add     dx,bx                           ;dx = top of transient; M006

        cmp     ax,bx                           ; M006
        jb      enough_mem                      ; Batchseg below transient
                                                ; enough memory ; M006
        cmp     ax,dx                           ; M006
        ja      enough_mem                      ; Batchseg above transient
                                                ; enough memory ; M006
;
; M006; Batchseg overlaps transient -- insufficient memory
;
        pop     ax                              ; restore ax; M006

;M006;  cmp     ax,bx                           ;G do we end before the transient
;M006;  pop     ax                              ;G get batch segment back
;M006;  jb      enough_mem                      ;G we have enough memory - continue

        push    es                              ;G no we're hitting the transient
        mov     es,ax
        mov     ax,DEALLOC SHL 8                ;G deallocate the batch segment
ifdef __TONYI__
	call	FakeInt21_DS
else
        int     21h
endif
        pop     es

mem_error:
        jmp     no_memory                       ;G Set up for message and exit

enough_mem:
        pop     ax                              ; restore ax; M006

        MOV     [BATCH],AX
        CALL    ALLOC_TPA
;
; Initialize batch segment
;
        RestoreReg  <DX>                        ; length of name
        POP     AX                              ;G  get saved batch segment back
        inc     es:nest                         ;G increment # batch files in progress
        PUSH    ES
        MOV     ES,[BATCH]
ASSUME  ES:NOTHING
        MOV     ES:[BatType],BatchType          ; signal batch file type
        MOV     ES:[batlast],ax                 ;G save segment of last batch file
        push    DS                              ;G
        mov     DS,[resseg]                     ;G set to resident data
ASSUME  DS:RESGROUP
        xor     ax,ax                           ;G
        mov     bl,forflag                      ;G get the current FOR state
        mov     ES:[batforflag],bl              ;G save it in the batch segment
        test    bl,-1                           ;G are we in a FOR?
        jz      for_not_on                      ;G no, for segment set to 0
        mov     ax,forptr                       ;G yes, get current FOR segment
        mov     forflag,0                       ;G reset forflag

for_not_on:
        mov     ES:[batforptr],ax               ;G save FOR segment in batch segment
        XOR     AX,AX
        mov     forptr,ax                       ;G make sure for segment is not active
        mov     bl,echoflag                     ;G
        pop     DS                              ;G

        mov     byte ptr es:[Batechoflag],bl    ;G save echo state of parent
;SR;
; Initialize the new BatchEOF flag we have added to 0
;
        mov     es:BatchEOF,0

        MOV     WORD PTR ES:[BatSeek],AX        ; point to beginning of file
        MOV     WORD PTR ES:[BatSeek+2],AX
;
; Initialize pointers
;
        DEC     AX                              ; put -1 into AX
        MOV     DI,BatParm                      ; point to parm area
        MOV     BX,DI
        MOV     CX,10
        REP     STOSW                           ; Init to no parms
;
; Move in batch file name
;
        MOV     CX,DX
        rep     movsb                           ; including NUL.
;
; Now copy the command line into batch segment, parsing the arguments along
; the way.  Segment will look like this:
;
;   <arg0>CR<arg1>CR...<arg9>CR<arg10>CR...<ARGn>CR 0
;
; or, in the case of fewer arguments:
;
;   <arg0>CR<arg1>CR...<arg6>CR CR CR ... CR 0
;
        MOV     SI,OFFSET TRANGROUP:COMBUF+2
        MOV     CX,10                           ; at most 10 arguments
;
; Look for beginning of next argument
;
EACHPARM:
        invoke  SCANOFF                         ; skip to argument
;
; AL is first non-delimiter.  DS:SI points to char = AL
;
        CMP     AL,0DH                          ; end of road?
        JZ      HAVPARM                         ; yes, no more arguments
;
; If CX = 0 then we have stored the most parm we can.  Skip store
;
        JCXZ    MOVPARM                         ; Only first 10 parms get pointers
;
; Go into allocated piece and stick in new argument pointer.
;
        MOV     ES:[BX],DI                      ; store batch pointer
        ADD     BX,2                            ; advance arg counter
;
; Move the parameter into batch segment
;
MOVPARM:
        LODSB                                   ; get byte
        INVOKE  DELIM                           ; if delimiter
        JZ      ENDPARM                         ; then done with parm
        STOSB                                   ; store byte
        CMP     AL,0DH                          ; if CR then not delimiter
        JZ      HAVPARM                         ; but end of parm list, finish
        JMP     SHORT MOVPARM
;
; We have copied a parameter up until the first separator.  Terminate it with
; CR
;

ENDPARM:
        MOV     AL,0DH
        STOSB
        JCXZ    EACHPARM                        ; if no parameters, don't dec
        DEC     CX                              ; remember that we've seen one.
        JMP     SHORT EACHPARM
;
; We have parsed the entire line. Terminate the arg list
;

HAVPARM:
        XOR     AL,AL
        STOSB                                   ; Nul terminate the parms
;
; Now we know EXACTLY how big the BATCH segment is.  Round up size (from DI)
; into paragraphs and setblock to the appropriate size
;
        LEA     BX,[DI+15]
        MOV     CL,4
        SHR     BX,CL
        MOV     AH,SetBlock
ifdef __TONYI__
	call	FakeInt21_DS
else
        INT     21h
endif

        POP     ES
ASSUME  ES:RESGROUP
        PUSH    ES
        POP     DS                              ; Simply batch FCB setup
ASSUME  DS:RESGROUP
        CMP     [SINGLECOM],-1
        JNZ     NOBATSING
        MOV     [SINGLECOM],0FFF0H              ; Flag single command BATCH job

NOBATSING:
;
; Enter the batch file with the current echo state
;
        pop     ax                              ; Get original echo state
        mov     echoflag,al                     ;g restore it
        JMP     TCOMMAND

;
; The following is executed if there isn't enough memory for batch segment
;

NO_MEMORY:
        assume ds:trangroup,es:resgroup
        pop     dx                              ;g even up our stack
        pop     ax                              ;g
        pop     ax                              ;g
        call    Alloc_tpa                       ;g reallocate memory
        mov     msg_disp_class,ext_msg_class    ;AN000; set up extended error msg class
        mov     dx,offset TranGroup:Extend_Buf_ptr     ;AC000; get extended message pointer
        mov     Extend_Buf_ptr,error_not_enough_memory ;AN000; get message number in control block
        jmp     cerror                          ;g print error message and go...

EndProc BatCom

Procedure   BatchOff

        ASSUME  DS:NOTHING,ES:NOTHING

        SaveReg <AX,ES>
        PUSH    DS                              ;G
        PUSH    BX                              ;G
        MOV     ES,ResSeg
        MOV     DS,ResSeg                       ;G
        ASSUME  ES:ResGroup,DS:Resgroup         ;G
        MOV     AX,Batch                        ; Free the batch segment
        OR      AX,AX
        JZ      nofree

        PUSH    ES
        MOV     ES,AX
        test    [echoflag],1                    ;G Is echo on?
        jnz     echo_last_line                  ;G Yes - echo last line in file
        mov     suppress,no_echo                ;G no - don't echo last line in file

echo_last_line:
        MOV     BL,ES:[BATECHOFLAG]             ;G  Get echo state
        mov     [echoflag],bl                   ;G     and restore it
        MOV     BX,ES:[BATFORPTR]               ;G  Get FOR segment
        MOV     FORPTR,BX                       ;G     and restore it
        MOV     BL,ES:[BATFORFLAG]              ;G  Get FOR flag
        MOV     FORFLAG,BL                      ;G     and restore it
        MOV     BX,es:[batlast]                 ;G  get old batch segment
        MOV     AH,DEALLOC
ifdef __TONYI__
	call	FakeInt21_DS
else
        INT     21h
endif
        POP     ES
        MOV     Next_BATCH,BX                   ;G  reset batch segment
        DEC     es:NEST                         ;G

        XOR     AX,AX
        MOV     Batch,AX                        ; No batch in progress

NoFree:
        POP     BX                              ;G
        pop     ds                              ;G
        RestoreReg  <ES,AX>

        return

EndProc BatchOff


; StrCpy - copy string, checking count in CX against COMBUFLEN
;       Entry : DS:SI ==> source string
;               ES:DI ==> destination string
;               CX = current length of destination string
;       Exit  : string copied, CX updated, Carry set if length limit exceeded
Procedure StrCpy,NEAR
        push    ax
ccycle:
        lodsb
        inc     cx
        cmp     cx,COMBUFLEN
        jb      ccopy
        stc                             ; set carry to signal error
        jmp     short ccend
ccopy:
        stosb
        or      al,al
        jnz     ccycle

ccend:
        dec     cx                      ; discount extra byte
        dec     di                      ; back up pointer
        pop     ax
        return                          ; return carry clear
EndProc StrCpy

TRANCODE    ENDS
            END
