;       SCCSID = @(#)d_execp.asm	6.1 91/02/15
;      SCCSID = @(#)d_execp.asm	1.5 90/01/22
; ****************************************************************************
; *									     *
; *			  IBM/Microsoft Confidential			     *
; *									     *
; *		    Copyright (c) IBM Corporation  1987, 1990		     *
; *		    Copyright (c) Microsoft Corp.  1987, 1990		     *
; *			      All Rights Reserved			     *
; *									     *
; ****************************************************************************
;***  d_execp.asm - API stub routine DosExecPgm
;
;       Member of API library
;       Bill Mershon
;       Copyright MicroSoft Corporation, 1986
;       25-Apr-1986
;
;       library routine for API simulator
;
;       Called from dos 2.x,3.x compatible dos 5.x applications
;
;       Special Information
;
;       Modification History
;
;       18-Jul-86 (billme): make slight modifications in the comments
;       30-Jul-86 (billme): change "INVALID_FUNCTION" to "INVALID_DATA"
;       for invalid values given for AsyncIndic and TraceIndic.
;       07-Aug-86 (billme): minor performance improvements
;       13-Aug-86 (billme): changes to implemement dcr 346 ... for this,
;       fapi simply includes the arguments and promptly ignores them
;       14-Aug-86 (billme): comment out change until 9/1
;       07-Oct-86 (billme): implement dcrs 346,553,558,431,559
;       30-Oct-86 (billme): add dbcs support (see #DBCS#)
;       28-Nov-86 (billme): clean up code and repair problem of dos 5
;       programs execing dos 3 programs in which the null terminator of
;       the command line args were being clobbered since the length
;       was too short - I added one to the length to include the CR at
;       the end.
;
;       06-Jan-87 (billme): Replace initial pushes and final pops
;       with calls to __api_begin and __api_end resp.  This will
;       reduce the size of FAPI and allow support for signals.
;
;       20-Jan-87 (billme): Replace 'blank' with '"blank"' which is
;       really null due to a dcr which specified that the first
;       delimiter in the command line is a null rather than a blank.
;
;       18-Mar-87 (chernow): uncomment dec bx in build of command line
;       as per mikem to fix ptm 3365.
;
;
;       04-May-87 (robertc): Install hooks for DOSErrorClass Support.
;       All int 21h errors are passed through api_get_extended to get the
;       extended errorcode and to provide the class, action, and locus for
;       the dynamic error FIFO queue.
;
; @@    8-May-87 (Tarng): Check filename/pathname is in the 8.3 range.
;
; @@   28-May-87 (CWY):   If the PATH name has '.' then pass the name
;                         along as is. The new change is in
;                         api_build_path routine.
;
; @@    11-June-87 (Tarng): PTM 5484.
;
; @@    10-July-87 (giacomoa) Save / restore DTA around EXECPGM int 21 call
; @@                          (ptm5847)
;
; @@    21-July-87 (Tarng): 1) Save DTA in the beginning of the API.
;       2) Set DTA before issue FINDFIRST.
;
; @@    22-July-87 (giacomoa): change __PSP to __api_PSP to avoid name
; @@    conflict with slibc.lib (COBOL compiler problem)
;
; @@    28-July-87 (Tarng): Include hidden/system/archive files in FINDFIRST.
;
; @@    28-September-87 (chernow): Save address of parents environment while
; @@    new environment is being set up for the child process.
;
; @@    19-October-87 (underwood): change search path so child process can
; @@    be found anywhere in the parent's environment.
;
; @@    18-January-88 (robertc): save and restore ss and sp around
; @@    the int 21h 4bh call.  (p1655)
;
; @@    27-January-88 (robertc): save and restore bp,es,ds  around
; @@    the int 21h 4bh call.  (p1752)

IFDEF api_debug_pause
        %OUT *** DEBUG *** int 3 pauses included
ENDIF


INCLUDE struc.inc                       ;@@
INCLUDE dos2equ.inc
INCLUDE basemaca.inc
INCLUDE error.inc
INCLUDE api.inc
INCLUDE curdir.inc

        EXTRN   __api_begin:near
        EXTRN   __api_end:near
        EXTRN   name_check:far          ;@@ added support for 8.3 filename


SYSTEM_FLAG     equ     27h
MAX_CMD_LEN     equ     126     ; max length of command string

APIBUF  segment byte common 'CODE'

__api_buffer    db      MAXPATHLEN dup(?)

APIBUF  ends


APIBUFF2  segment byte common 'CODE'

__api_new_buff2    db      MAX_CMD_LEN dup(?)

APIBUFF2  ends


extrn   __api_PSP:word  ; @@


MAXENVsizeB     EQU     0EFFFh                  ; 32k

        EXTRN   api_testdbcs:far
        EXTRN   api_testdbcs_b:far
        EXTRN   api_testdbcs_bst:far
        EXTRN   api_testdbcs_bdec:far
        EXTRN   api_dbcs_findchar:far
        EXTRN   api_get_extended:far    ;@@ added support for DOSErrorClass

_TEXT   segment byte public 'CODE'

PathStr         db      'PATH='
ExecLen         dw      ?
path_index      dw      ?

com_ext         db      ".COM"
exe_ext         db      ".EXE"
add_point       dw      ?
exe_flag        db      ?
ss_save         dw      ?
sp_save         dw      ?
DTAarea         DTAFINDSTRUC <>         ; DTA area used in the find calls

_TEXT   ENDS


;***    DOSEXECPGM - stub to emulate DOSEXECPGM function call
;
;       simulates DOS 5.0 call from DOS 2.x,3.x environment
;
;       errorcode = unsigned far pascal
;       DosExecPgm(ptr_Buffer, BufLength, SyntrCode, ptr_Arg, ptr_Env,
;               ptr_PID, ptr_Pgm);
;
;       ENTRY:
;               ptr_Buffer - pointer to error output buffer (ignored)
;               BufLength - length of error output buffer (ignored)
;               SyntrCode - sync/trace code - must be 0
;               ptr_Arg - pointer to arguments to program to exec
;               ptr_Env - pointer to environment strings
;               ptr_PID - pointer to PID (set to null)
;               ptr_Pgm - pointer to pathname of program to exec
;
;       RETURN:
;               AX = 0 if no error, else error code (see DOS 5 spec)
;
;       EXIT:
;
;       USES:
;               AX
;
;       WARNING:
;               may return error code from Dos 2.x, 3.x (see dos manuals)
;               *ptr_PID is not altered (as it is in DOS 5)
;
;       EFFECTS:
;               none
;

        title   DOSEXECPGM - stub to emulate DOSEXECPGM function call
;
;       EXTRN   DOSEXECPGM:FAR
;
;       PUSH    @ BYTE ptr_Buffer
;       PUSH    WORD BufLength
;       PUSH    WORD SyntrCode
;       PUSH    @ BYTE ptr_Arg
;       PUSH    @ BYTE ptr_Env
;       PUSH    @ DWORD ptr_PID
;       PUSH    @ BYTE ptr_Pgm
;       CALL    DOSEXECPGM
;

_TEXT   segment byte public 'CODE'

        assume  CS:_TEXT,DS:NOTHING

ADR_OF_ENV_SEG  EQU     2ch     ; offset from start of pcp
CR              EQU     0dh     ; carriage return
SPACE           EQU     020h    ; blank space
NULL            EQU     0h      ; null
parent_env_seg  DW      ?       ; @ environment segment of parent process
env_seg          dw      ?       ; environment segment and offset
env_off          dw      ?
memory          DW      ?       ; save pointer to allocated memory
copyflag        DB      ?       ; true if memory was allocated else false
as_flag         DB      0       ; true if addr of arg string saved

param_block:
pb_envseg       DW      ?       ; segment pointing to environment strings
pb_ptr_cmdline  DD      ?       ; pointer to command line
pb_ptr_5c_fcb   DD      ?       ; pointer to 5c offset in PCP
pb_ptr_6c_fcb   DD      ?       ; pointer to 6c offset in PCP

localpsp        dw      ?       ; local copy of psp

as_blank_off    DW      ?       ; offset of blank in argument string (count)
as_null_off     DW      ?       ; offset of null term in argument string (CR)
as_segment      DW      ?       ; segment of argument string
dummyargs       DB      SPACE   ; length of dummy argument string
                DB      0       ; with NULL termination
                DB      0       ; and CR termination

MAX_ENV_LEN     equ     03ffh   ; max length of environment string

;; save segment and offset of user's ptr_pgm
savsegptr       dw      ?
savoffptr       dw      ?

;; @@ data for saving current DTA
old_DTA_off     dw      ?
old_DTA_seg     dw      ?

        public  DOSEXECPGM

ptr_Buffer      equ     [BP+26]
BufLength       equ     [BP+24]
SyntrCode       equ     [BP+22]
ptr_Arg         equ     [BP+18]
ptr_Env         equ     [BP+14]
ptr_PID         equ     [BP+10]
ptr_Pgm         equ     [BP+6]


DOSEXECPGM      proc    far
        call    __api_begin     ; init bp and save bp,ds,bx,cx,dx,si,di,es
                                ; required to save: bp,bx,cx,dx,si,di,ds,es

        mov     ax,cs:__api_PSP ; @@
        mov     cs:localpsp,ax

        mov     ds,ax                   ; @@ segment of psp of parent
        mov     bx,ADR_OF_ENV_SEG       ; @@ offset to address of env.
        mov     bx,WORD PTR ds:[bx]     ; @@ get address of parent env.
        mov     parent_env_seg,bx       ; @@ save the parent environment

        push    cs
        pop     ds

        ; clear direction for all string manipulations to follow
        cld

        ; move the user's ptr_pgm value to another buffer for manipulations
        lds     si,ptr_pgm              ; address to be saved
        mov     cs:savsegptr,ds         ; save user segment of ptr_pgm
        mov     cs:savoffptr,si         ; save user offset of ptr_pgm

        mov     ax,SEG APIBUFF2         ; segment of buffer to be used for
                                        ; ptr_pgm manipulations
        mov     es,ax
        mov     di,offset APIBUFF2:__api_new_buff2; offset of buffer to be used
                                                ; for ptr_pgm manipulations
another:
        lodsb                           ; move string @ptr_pgm to _api_buff2
        stosb                           ;
        or      al,al                   ; check if we are at end of string
        jnz     another                 ; continue to move string if not at end

        ; change the ptr_pgm on the stack to _api_buff2
        mov     ax,offset APIBUFF2:__api_new_buff2; move the offset to the stack
        mov     [bp+6],ax                       ;
        mov     [bp+8],es                       ; move the segment to the stack

        push    cs
        pop     ds

        ;; @@ save current DTA

        mov     ah, D2_GetDTA           ; @@
        int     21h                     ; @@
        mov     old_DTA_off, bx         ; @@ ES:BX => current DTA
        mov     old_DTA_seg, es         ; @@

        ;; @@ set current DTA
        push    ds                              ; save ds value
        mov     ah, D2_SetDTA                   ; Set a new disk transfer
        mov     dx, SEG _TEXT                   ; address for the find first
        mov     ds, dx                          ; call.
        mov     dx, offset _TEXT:[DTAarea]
        int     21h
        pop     ds                              ; restore ds value

        ; make sure SyntrCode is zero
        mov     ax,ERROR_INVALID_DATA
        cmp     word ptr SyntrCode,0
        jz      exec1                   ; SyntrCode is zero

        jmp     NormalExit              ;@@ added support for DOSErrorClass
exec1:

; load parameter block for call to execute program (function 4Bh)

        ; copy ARGUMENT pointer to parameter block

        ; NOTE: we assume the argument block begins with the name of
        ; the function (and/or a path name) followed by a "blank"...
        ; we replace the "blank" with the length of the string following
        ; the "blank" and provide the result as the argument string
        ; to the dos 2.x execute program call

        lds     si,ptr_Arg              ; DS:SI <- pointer to argument string
        mov     ax,ds
        or      ax,si                   ; if pointer is null
        jz      exec2dx                 ; then use dummy string
                                        ; else proceed with "fix" to cmd string

; verify the filename in 8.3 range
        xor     ah,ah                   ;@@ meta char is valid
        call    name_check              ;@@ check name in 8.3 range
        jnc     name_ok                 ;@@ filename is illegal
        jmp     NormalExit              ;@@
name_ok:                                ;@@

        ; find the first "blank" or NULL in the argument string
        mov     cx,MAX_CMD_LEN          ; CX <- max possible length
exec2a:
        lodsb
; #DBCS#                                ; test dbcs, bump, and dec cx
        call    api_testdbcs_bdec       ; if ill formed DB char
        jz      exec2ab                 ; then return error
        jnc     exec2b                  ; else if not DB char then continue
        jcxz    exec2c                  ; else if cx reaches zero then drop out
        jmp     short exec2a            ; else loop
exec2ab:
        mov     ax,ERROR_INVALID_DATA

        jmp     NormalExit              ;@@ added support for DOSErrorClass
exec2b:

; Note the following is a kludge until there is time to think
; of something better.  The problem is that if the user erroneously
; uses a blank as a separator in the command line, we will crash
; since we are expecting a null.  There should be a cleaner way
; to solve this problem, but for now we just detect a blank
; and if so, use a dummy argument string.

        cmp     al,' '                  ; if BLANK found (we can't handle this)
        jz      exec2dx                 ; then use dummy string
        or      al,al                   ; if NULL found
        jz      exec2c                  ; then check for end of string
        loop    exec2a                  ; if CX is not zero, continue, else
exec2c:
        lodsb                           ; AL <- next byte
        or      al,al                   ; if next byte is NOT zero
        jnz     exec2e                  ; then use given string

exec2dx:
        push    cs                      ; else use dummy string
        pop     ds                      ; DS:SI <- dummy argument string
        mov     si,offset _TEXT:dummyargs + 2
exec2e:
        dec     si                      ; SI <- address of FIRST null
        dec     si
        push    ds
        pop     es
        push    si                      ; move DS:SI to ES:DI
        pop     di
        or      byte ptr ds:[si],1      ; make first null non null
                                        ;   (for scasb below)

        ; ES:DI now points to the command string to use...
        ; - save segment of argument string
        ; - save pointer to first "blank"
        ; - search for null terminator and compute the length of the string
        ; - save pointer to null terminator
        ; - replace null terminator with CR terminator (for DOS 2.x execpgm)
        ; - replace 1st byte of parm w/ length of string (for DOS 2.x execpgm)

        mov     as_flag,1               ;@@set flag of saved addr of arg string
        mov     as_segment,es           ; save segment of argument string
        mov     as_blank_off,di         ; save offset of first "blank"
        ; compute length of command string (search for null terminator)
        mov     cx,MAX_CMD_LEN          ; CX <- max length permissible
        mov     bx,cx                   ; BX <- CX for difference computation
        xor     ax,ax                   ; prepare search for 0 (AL <- 0)
        repne   scasb                   ; search for 0
        sub     bx,cx                   ; BX <- length of cmd str
        dec     bx                      ; length does not count space
        dec     bx     ;@@PTM 3365      ; length does not null terminator
        dec     di                      ; point di to null terminator
        mov     as_null_off,di          ; save offset of null terminator
        ; prepare command line and store CR terminator and length
        mov     BYTE PTR es:[di],CR     ; replace null terminator with CR
        ; note: si still points to 1st byte of parm string
        mov     BYTE PTR es:[si],bl     ; replace 1st byte w/ length of string

        mov     WORD PTR pb_ptr_cmdline,si ; store command line pointer
        mov     WORD PTR pb_ptr_cmdline+2,es

        ; copy pointers to 5ch + PSP and 6ch + PSP

        mov     cx,localpsp             ; CX <- segment of PSP
        mov     WORD PTR pb_ptr_5c_fcb,05ch
        mov     WORD PTR pb_ptr_5c_fcb+2,cx
        mov     WORD PTR pb_ptr_6c_fcb,06ch
        mov     WORD PTR pb_ptr_6c_fcb+2,cx

        mov     copyflag,0              ; reset copy flag

        ; copy ptr to env_seg ... DOS 2.0 requires paragraph alignment
        ; if the given ENVIRONMENT pointer is paragraph aligned, use it
        ; otherwise allocate some memory and copy the environment there

        les     di,ptr_Env              ; ES:DI <- pointer to env string

        ; If environment pointer is zero, we use the parents environment.
        ; Executing a program is complicated by the question of what type
        ; of program is it and what type of program is doing the executing.
        ; There are four cases to consider;  A exesc B where A is either
        ; DOS2/3 or DOS5stub and B is either DOS2/3 or DOS5stub.
        ; Especially due to the "DOS5 execs DOS5" possibility, instead
        ; of sending 0 to the DOS2/3 exec routine to ask for the parents
        ; environment, we force a copy of just the environment
        ; (minus the command string which follows in the stub case) to
        ; a new location and provide this as the environment string
        ; to the DOS2/3 loader.  This avoids the potential for 'cascading'
        ; in which the stub loader extends (instead of replaces) the
        ; command string.

        mov     ax,es                   ; ES:DI points to environment
        or      ax,di                   ; if env pointer is NOT null
        jnz     exec2_95                ; then proceed with alignment check
                                        ; else (ptr is zero) prepare to copy
                                        ; (recall CX contains ptr to psp)
        mov     es,cx                   ; ES <- pointer to psp
        mov     bx,ADR_OF_ENV_SEG       ; BX <- address (within psp) of env seg
        mov     ax,es:[bx]              ; AX <- seg address of environment seg
        mov     es,ax                   ; ES <- seg address of environment seg
        xor     di,di                   ; ES:DI <- pointer to env seg
        jmp     short exec2_97          ; force copy to new location
exec2_95:
        mov     ax,di
        shr     ax,1                    ; merge most of di into es
        shr     ax,1
        shr     ax,1
        shr     ax,1
        mov     bx,es
        add     bx,ax
        mov     es,bx
        and     di,0fh                  ; if env seg is paragraph aligned
        jz      exec5                   ; then continue
                                        ; else allocate space and copy
        ; compute length of environment string
        ; (search for double-0 terminator)
exec2_97:

IFDEF api_debug_pause
        int     3       ; included ONLY IF api_debug_pause is defined
ENDIF

        mov     env_seg,es               ; save es:di
        mov     env_off,di
        mov     cx,MAX_ENV_LEN          ; CX <- max possible length
        mov     bx,cx                   ; BX <- CX for difference computation
        xor     ax,ax                   ; prepare search for 0 (AL <- 0)
exec3:
        repne   scasb                   ; search for 0
        jcxz    exec4                   ; envirnoment string too long
        dec     cx
        scasb                           ; if next byte is NOT 0
        jnz     exec3                   ; then keep searching
exec4:
        sub     bx,cx                   ; BX <- length of env str

        ; allocate the required memory
        mov     cx,bx                   ; CX <- length of env str
        shr     bx,1                    ; convert bx to paragraphs
        shr     bx,1
        shr     bx,1
        shr     bx,1
        inc     bx                      ; bump BX to be sure we have enough
        mov     ah,D2_AllocMem
        int     21h                     ; allocate memory
        jc      ErrorExit               ; dos error encountered
        mov     memory,ax               ; save memory allocation pointer
        or      copyflag,1              ; set copy flag

        ; copy env string into allocated memory
        mov     ds,env_seg               ; DS:SI <- pointer to env string
        mov     si,env_off
        mov     es,ax                   ; ES:DI <- pointer to allocated mem
        xor     di,di

        ; Note that cx must be at least 2 since 'scasb' was executed twice
        ; and cx started out at more than 2.  Therefore, decrementing
        ; cx by 2 cannot possibly wrap (which would be a disaster).
        ; Also, since we have counted both null terminators, we can
        ; reduce cx by 2 (to account for cx being the max, which is
        ; possible) without impunity.
        dec     cx                      ; account for 'too long' string
        dec     cx                      ; (will store termination by hand)
        rep     movsb                   ; copy env string to new home
        mov     word ptr es:[di], 0     ; store termination bytes (0)
exec5:
        mov     env_seg,es              ; store environment segment
        mov     pb_envseg,es            ; store environment segment
        lds     dx, ptr_Pgm             ; DS:DX <- pointer to prog name

        call    api_build_path          ; if find file along path fails
        or      ax,ax                   ; then return error

        jnz     NormalExit              ;@@ added support for DOSErrorClass

        mov     ax, cs
        mov     es, ax
        mov     bx, offset param_block  ; ES:BX <- pointer to param block
        xor     ax,ax                   ; AL <- load and exec command
        mov     ah,D2_ExecProgram
        push    bp                      ;@@ save registers
        push    ds
        push    es
        mov     cs:ss_save,ss           ;@@ save stack info
        mov     cs:sp_save,sp

        int     21h                     ; if error detected

        mov     ss,cs:ss_save           ;@@ restore stack info
        mov     sp,cs:sp_save
        pop     es                      ;@@ restore registers
        pop     ds
        pop     bp

        jc      ErrorExit               ; then exit
        mov     ah,D2_Wait              ; else wait for result
        int     21h
        lds     bx,ptr_PID


;       mov     ds:[bx+2],ax            ; store result in PID
;       xor     ax,ax                   ; clear error
;       mov     ds:[bx],ax              ; store zero termination code


        mov     ds:[bx],ah              ; store termination code (LSB)
        xor     ah,ah                   ; (upper byte of exit code)
        mov     ds:[bx+1],ah            ; (and of termination code)
        mov     ds:[bx+2],ax            ; store exit code
        xor     ax,ax                   ; clear error

        jmp     NormalExit              ;@@ Added support for DOSErrorClass
ErrorExit:                              ;@@ added support for DOSErrorClass
        call    api_get_extended        ;@@ added support for DOSErrorClass
NormalExit:                             ;@@ added support for DOSErrorClass

        ;; @@ restore old DTA

        push    ax                      ; @@ save possible error code
        mov     ax, old_DTA_seg         ; @@
        mov     ds, ax                  ; @@
        mov     dx, old_DTA_off         ; @@ DS:DX => old DTA
        mov     ah, D2_SetDTA           ; @@
        int     21h
        pop     ax                      ; @@ restore possible error code

        mov     dx,ax                   ; save possible error
        push    cs                      ; restore ds
        pop     ds
        mov     al,copyflag
        and     al,al                   ; if copyflag is false
        jz      exec6                   ; then exit
        mov     es,memory               ; else free allocated memory
        mov     ah,D2_FreeMem
        int     21h                     ; if no error returned
        jnc     exec6                   ; then exit
        call    api_get_extended        ;@@ ptm 5484
        mov     dx,ax                   ; else store error in DX
exec6:
        cmp     as_flag,0               ; @@ Need to restore?
        je      exec65                  ; @@ No, jump
        mov     es, as_segment          ; restore "blank" and null term
        mov     di, as_blank_off
        mov     BYTE PTR es:[di],NULL  ; restore "blank"
        mov     di, as_null_off
        mov     BYTE PTR es:[di],NULL   ; restore null

exec65:                                 ; @@
        mov     ax,dx                   ; restore possible error


        ; restore the user's ptr_pgm on the stack
        mov     ds,cs:savsegptr         ; move the segment to the stack
        mov     si,cs:savoffptr         ; move the offset to the stack
        mov     [bp+6],si               ;
        mov     [bp+8],ds               ;

        call    __api_end       ; check sigs & restore es,di,si,dx,cx,bx,ds,bp
        ret     24      ; clear stack of entry parameters (bytes)
DOSEXECPGM      endp


;**     api_build_path - Build the pathname of the exec'ed file
;
;       This procedure is builds the file's pathname in __api_buffer,
;       searching as necessary along the environment's path.
;
;       ENTRY
;               DS:DX == pointer to program/path name
;       EXIT
;               DS:DX == pointer to program/path name (possibly api_buffer)
;       USES
;               AX,BX,CX,SI,DI,ES
;
;       NOTE
;               This routine contains path "cracking" code that has been
;               adapted to DBCS support.


api_build_path  PROC    NEAR

; Search the given name for path separators.  If any are found,
; then pass the name along as is.

; WARNING! This loop will walk forever if no NULL terminator is found.

        mov     si,dx                   ; DS:SI <- pointer to name
        xor     ah,ah                   ;@@ meta char is valid
        call    name_check              ;@@ check name in 8.3 range
        jnc     abp01                   ;@@ filename is legal
        jmp     abp99                   ;@@

abp01:                                  ; WHILE  (Exec_name[i++] != NULL)
        lodsb                           ; {
; #DBCS#                                ; test dbcs and bump
        call    api_testdbcs_b          ; if ill formed DB char
        jz      abp04                   ; then return error
        jc      abp01                   ; else if DB char then loop

;@@     cmp     al,'.'                  ; stop before extension
;@@     je      abp06                   ;

        .IF  < al EQ '.'  >             ; @@ if extension
             push  ax                   ; @@ save the current char
             xor   bx,bx                ; @@ clear bx
             loop1:
                      .IF < <byte ptr ds:[si+bx]> EQ  NULL > ;@@ end string?
                           pop     ax    ; @@
                           jmp     abp06 ; @@ do not find the PATH name
                      .ELSE
                              mov  al, byte ptr ds:[si+bx]   ;@@
                              call api_testdbcs_b       ;@@ test dbcs and bump
                              jz   loop2                ;@@ ill formed DB char
                              jc   loop2                ;@@ DB char
                              .IF  < al EQ '\'>         ;@@ PATH exists
                                   pop  ax              ; @@
                                   jmp  abp03           ; @@
                              .ELSE                     ; @@
             loop2:                inc  bx              ; @@ increment index
                                   jmp  loop1           ; @@
                              .ENDIF                    ; @@
                      .ENDIF                            ; @@
        .ENDIF                                          ; @@

        cmp     al,'\'                  ;   IF (Path_separator)
        je      abp03                   ;       THEN return

        cmp     al,':'
        jne     abp05


abp03:
        jmp     abp97                   ; return no error
abp04:
        mov     ax,ERROR_INVALID_DATA
        jmp     abp99
abp05:
        or      al,al
        jnz     abp01                   ; }

abp06:
        dec     si                      ; do NOT count '.' or '\0' in length
        sub     si,dx                   ; SI <- strlen(Exec_name)
        mov     ExecLen,si              ; save lenth of exec name

; try to find raw file name with COM or EXE extension in current directory

        xor     bx,bx                   ; BX <- 0 (length of path in api_buf)
        mov     cx,si                   ; CX <- length of name
        mov     si,dx                   ; DS:SI <- pointer to program name
        call    api_find_exec           ; search for either COM or EXE file
        or      ax,ax                   ; if no error returned
        jz      abp03                   ; then return no error

; Find "PATH=" string in the environment
                                        ; @@ use the parents environment to
                                        ; @@ search for child
        mov     cx,parent_env_seg       ; @@ if environment seg is zero
        jcxz    abp08                   ; then return error
        mov     es,cx
        xor     di,di                   ; else ES:DI <- start of environment

        push    cs
        pop     ds                      ; DS <- segment for PathStr

;       search for PATH in the environment
;
;       (DS:SI) <- name of variable we're looking for
;       (ES:DI) <- current search position in environment

abp07:
        mov     cx,5                    ; search for "PATH="
        mov     si,offset _TEXT:PathStr ; DS:SI <- pointer to "PATH="
        repe    cmpsb                   ; strcmp(ds:si, es:di)
        je      abp11                   ; found the path
        dec     di                      ; backup to failing character
        mov     cx,MAXENVsizeB          ; "unlimited" scan (32k)
        xor     al,al
        repne   scasb                   ; find start of next env. var.
        cmp     byte ptr es:[di],al     ; end of environment?  (2 null bytes)
        jne     abp07                   ;  no, check for path string
abp08:
        jmp     abp91                   ; return error FILE NOT FOUND

;       we have found the environment variable holding the path name
;       and ES:DI points just beyond "PATH="

abp11:
        mov     si,di
        push    es
        pop     ds                      ; DS:SI <- first PATH member

        mov     cs:path_index,SI        ; save current index of PATH

        xor     ah,ah                   ;@@ meta char is valid
        call    name_check              ;@@ check name in 8.3 range
        jnc     abp15                   ;@@ filename is legal
        jmp     abp99                   ;@@
abp15:
        mov     ax, seg APIBUF
        mov     es, ax
        mov     di, offset APIBUF:__api_buffer  ; ES:DI <- pointer to api_buf

;       (ES:DI) = beginning of path name buffer
;       (DS:SI) = path name in the PATH= attribute in env seg
;
;       Extract the path name from the PATH= attribute in the env
;       seg character by char.  If a zero or ';' is encountered,
;       leave the loop.
;
;       Ignore blank spaces and copy any other character
;       into the buffer.  By the time we encounter a delimeter,
;       we may have a path name to use.

; #DBCS#
        xor     cx,cx                   ; clear "last char DB" flag
abp17:
        lodsb                           ; get next character from PATH
; #DBCS#                                ; test dbcs, bump, and store
        call    api_testdbcs_bst        ; if ill formed DB char
        jz      abp04                   ; then return error
        jnc     abp18                   ; else if not DB char then continue
        inc     cx                      ; set flag
        jmp     short abp17
abp18:

        or      AL,AL                   ; end of PATH?
        jz      abp19                   ;  Yes, may have one more string to go
        cmp     AL,";"                  ; Delimiter?
        je      abp19                   ;  Yes, Try string we have so far
; #DBCS#
        xor     cx,cx                   ; clear "last char DB" flag

        cmp     AL," "                  ; Blank?
        je      abp17                   ;  Yes, ignore
        stosb                           ; copy this one
        jmp     short abp17             ; go get next character

;       (DS) -> env seg
;       (SI) advanced to end of path name we copied in the loop above
;       (ES) -> path name buffer
;       (DI) = 0 if no path name was copied OR..
;       (DI) advanced after the current path name was copied from env seg
;       if no path name was copied into ES:DI, we have no more
;       tries for find file ... report error

;       if some path name was copied, make sure that it terminates
;       with a slash

abp19:
        mov     bx,offset APIBUF:__api_buffer   ; BX <- address of buffer
        cmp     di,bx                           ; if DI did not move
        je      abp91                           ; then return error

        mov     AL,"\"
        cmp     BYTE PTR ES:[DI-1],al   ; need to add slash?
        je      abp21                   ;  No, it's already there
        stosb                           ; add a slash

;       note that when we are here we have SI pointing at the
;       next path name in the PATH= attribute in env seg...
;       save (SI)

abp21:
        sub     di,bx                   ; DI <- length of path string
        mov     path_index,SI           ; save index of current PATH
        lds     si,DWORD PTR ptr_Pgm    ; DS:SI <- get name to copy over
        mov     cx,ExecLen              ; CX <- length of program name
        xchg    bx,di                   ; BX <- length of path string
        call    api_find_exec           ; search for either COM or EXE file
        or      ax,ax                   ; if no error returned
        jz      abp97                   ; then return no error

        mov     SI,path_index           ; @@ continue in the parents environment
        mov     DS,parent_env_seg       ; DS:SI <- pointer to next path
        cmp     BYTE PTR DS:-1[SI],0    ; Was that the end of path?
        jne     abp15                   ;  No, keep trying

abp91:                                  ; set carry (again?)
        mov     AX,ERROR_FILE_NOT_FOUND ; Find or PATH error will give this
        jmp     short abp99             ; return the error
abp97:
        xor     ax,ax                   ; clear error
abp99:
        ret

api_build_path  ENDP


;**     api_find_exec - search for an exec file using COM and EXE extensions
;
;       Take a given name and length (not including extension) and
;       a length of the prefix in __api_buffer.  Append the name to
;       __api_buffer and call this the "prefix".  Add the "COM" extension
;       to the prefix and search.  If this file is found, return success,
;       otherwise try the same thing with "EXE" and return the result.
;
;       ENTRY
;               DS:SI == pointer to program name
;               CX = length of name (without extension)
;               BX = length of path (including final "\") in __api_buffer
;       EXIT
;               if success
;                       AX == 0
;                       __api_buffer contains path and name to execute
;               else
;                       AX == ERROR_FILE_NOT_FOUND
;       USES
;               AX,BX,CX,DX,SI,DI,DS,ES

api_find_exec   PROC    NEAR
        mov     ax, seg APIBUF
        mov     es, ax
        mov     di, offset APIBUF:__api_buffer  ; ES:DI <- pointer to api_buf
        add     di,bx                           ; ES:DI <- pointer to end
        rep     movsb                           ; add on name
        mov     add_point,di                    ; save addition point

        mov     si, offset _TEXT:com_ext        ; SI <- com extension
        mov     exe_flag,0                      ; have not checked exe yet
afe1:
        push    cs
        pop     ds                              ; DS <- segment of "com_ext"
        movsw
        movsw                                   ; add extension (4 bytes)
        xor     al,al
        stosb                                   ; add null terminator

        push    es
        pop     ds
        mov     dx, offset APIBUF:__api_buffer  ; DS:DX <- pointer to api_buf

        mov     cx,SYSTEM_FLAG                  ; search for all but volume
                                                ; and directory
        mov     ah,D2_FindFirst
        int     21h                             ; if search succeeds
        jnc     afe97                           ; then return without error

        xor     exe_flag,1                      ; if exe already tested
        mov     ax,ERROR_FILE_NOT_FOUND
        jz      afe99                           ; then return "not found"

        mov     di,add_point                    ; DI <- addition point
        jmp     short afe1
afe97:
        xor     ax,ax                           ; clear error
afe99:
        ret

api_find_exec   ENDP

_TEXT   ends

        end

