;       SCCSID = @(#)dospath.asm        6.4 91/08/24
;      SCCSID = @(#)dospath.asm 6.4 91/08/24

        TITLE   dospath - DOSSEARCHPATH,DOSSCANENV interfaces

;***    dospath.asm
;
;       Contains the DOSEARCHPATH and DOSSCANENV APIs.
;
;  MODIFICATION HISTORY
;
;   07-Apr-87   IDB    Convert drive letter to number correctly (PTM 3663)
;
;   13-Dec-87   IS     Preserve BX in DOSSEARCHPATH (PTM 1444).
;
;   07-Mar-88   JSF    Fix 1.1 PTM 1979, passing invalid length to
;                       DOSICANONICALIZE.
;
;   07-Sep-88   JSF    1.2 PTM 1893 Correct stack usage in DOSSEARCHPATH
;
;   19-Jul-89   tedm   fix to search next path if fully qualified filename
;                                            is too long for user's buffer
;
;    11/18/93   noto   Define which segment for Page Tuning DLL
;
;   13-Jan-95   ChgTeam  105305 Added ERROR_NOT_DOS_DISK to ValidErrs so the
;                        rest of the DPATH will be searched if a HPFS drive
;                        is in the DPATH before the system directory.

page    ,132
.286p

.xlist
include curdir.inc
include dirent.inc
include bsedos.inc
include basemaca.inc
include osmaca.inc
include realmac.inc                     ; for long conditional jumps
include error.inc
include find.inc
include dc1segs.inc                                             ; DCR 704
include infoseg.inc
include dc1mmpha.inc
.list

segoff  struc
offst   dw      ?
segmt   dw      ?
segoff  ends

IMPLIEDCURR     EQU     1               ; implied current directory flag
PATHREFVAR      EQU     2               ; PathRef -> an environment variable
IGNORENETERR    EQU     4               ; if set, don't abort for net errs

; Reserved bits in control word

CHECKCONTROL    EQU     NOT (IMPLIEDCURR OR PATHREFVAR OR IGNORENETERR)

SHRDATA SEGMENT                                                 ; DCR 704

        extrn   dbcs_data:byte

SHRDATA ENDS

DATA    SEGMENT                                                 ; DCR 704

CDStr   DB      '.',0                   ; current directory string
ValidErrs       Label   WORD
        DW      ERROR_FILE_NOT_FOUND
        DW      ERROR_PATH_NOT_FOUND
        DW      ERROR_NO_MORE_FILES
        DW      ERROR_FILENAME_EXCED_RANGE      ; to support longnames
        DW      ERROR_NOT_DOS_DISK              ; 105305 (see note above)
;
NormalErrCount  EQU     ($ - ValidErrs)/2       ;
;
        DW      ERROR_INVALID_ACCESS
        DW      ERROR_NETWORK_BUSY
        DW      ERROR_TOO_MANY_CMDS
        DW      ERROR_ADAP_HDW_ERR
        DW      ERROR_BAD_NET_RESP
        DW      ERROR_UNEXP_NET_ERR
        DW      ERROR_BAD_REM_ADAP
        DW      ERROR_NETNAME_DELETED
        DW      ERROR_BAD_DEV_TYPE
        DW      ERROR_TOO_MANY_SESS
        DW      ERROR_REQ_NOT_ACCEP
        DW      ERROR_INVALID_PASSWORD
        DW      ERROR_VC_DISCONNECTED

; Errors added by ptr b708025:

        DW      ERROR_REM_NOT_LIST      ; Remote computer is not listening.
        DW      ERROR_BAD_NETPATH       ; The network path cannot be located.
        DW      ERROR_DEV_NOT_EXIST     ; Device does not exist on the network.
        DW      ERROR_BAD_NET_NAME      ; This network name cannot be found.
        DW      ERROR_REQ_NOT_ACCEP     ; Request is not accepted by the net.
        DW      ERROR_NETWORK_ACCESS_DENIED ; Network access is denied.
        DW      ERROR_ACCESS_DENIED     ; Invalid permission to srch this Dir.
NetErrCount     EQU     ($ - ValidErrs)/2

DATA    ENDS

        EXTRN   DOSICANONICALIZE:FAR
        EXTRN   DOSFINDFIRST:FAR
        EXTRN   DOSFINDCLOSE:FAR
        EXTRN   DOSGETENV:FAR
        EXTRN   DOSQCURDIR:FAR
        EXTRN   DOSQCURDISK:FAR

CODE16C_TUNE3    SEGMENT DWORD USE16 PUBLIC 'CODE'
CODE16C_TUNE3    ENDS



CODE16C_TUNE3    SEGMENT                                        ; DCR 704

    ASSUME cs:CODE16C_TUNE3

;**     DOSSEARCHPATH - find a file along a given path
;
;       DOSSEARCHPATH tries to find a file given a file name and a path string
;       where to search for the file. Path strings are ASCIIZ strings in the
;       following format:
;
;               bin;.;a:\bin;c:\word
;
;       that is,they consist of individual paths separated by ';'.
;       Notice that a '.' within the path string will cause the current
;       directory to be searched,irregardless of the control word.
;
;       DOSSEARCHPATH uses the following algorithm:
;
;               if (Control && CHECKCONTROL)
;                       ERROR_INVALID_PARAMETER
;
;               else if (Control && IMPLIEDCURR)
;                       build path name using current directory
;                       try to find file
;                       canonicalize and exit if file found
;
;               else if (Control && PATHREFVAR)
;                       get path string from environment
;
;               else PathRef is pointer to path string
;
;               else if (path string ptr is NULL)
;                       ERROR_FILE_NOT_FOUND
;
;               else while (more paths)
;                       build path name
;                       try to find file
;                       canonicalize and exit if file found
;               else
;                       ERROR_FILE_NOT_FOUND
;
;       ENTRY   WORD Control            ; bit vector which controls behaviour
;               DWORD PathRef           ; path reference
;               DWORD FileName          ; file name to search for
;               DWORD PathBuf           ; working and path area
;     ss:sp ->  WORD BufLen             ; length of PathBuf
;
;       EXIT    SUCCESS: (ax) = 0
;                        PathBuf filled in with path\FileName.
;
;               FAILURE: (ax) = error
;                        PathBuf contains garbage.
;
;       USES    ax,flags
;
;       NOTE    If we are successful in finding the file we return the
;               path,including the drive,from the root. Appended to
;               this path is the file name passed in by the user,even
;               if it contained wild cards. We do this so the results
;               of doing a DOSSEARCHPATH ("foo.*") can,without
;               modification,be passed to DOSFINDFIRST.
;
;               DOSICANONICALIZE will replace '*' wild cards by the
;               appropriate number of '?'s in order to pad to an
;               8.3 file name.

;***DT  preDOSSEARCHPATH - DosSearchPath pre-invocation trace point
;
;    TRACE MINOR = <RAS_DC1_PRE_DOSSEARCHPATH>,
;          TP    = .DOSSEARCHPATH,
;          TYPE  = (PRE,API),
;          GROUP = FS,
;          DESC  = "(OS) DosSearchPath Pre-Invocation",
;          MEM   = (<R3SP_ARGS>+14,D,2),
;          ASCIIZ= (<R3SP_ARGS>+2,I,<SHORTPATHLEN>),
;          ASCIIZ= (<R3SP_ARGS>+6,I,<SHORTPATHLEN>),
;          FMT   = "Control = <FMT_C_WORD>",
;          FMT   = "Path = <FMT_ASCIIZ>",
;          FMT   = "File = <FMT_ASCIIZ>"

Procedure DOSSEARCHPATH,FAR
    ASSUME ds:NOTHING,es:NOTHING,ss:NOTHING

ArgVar    BufLen,WORD
ArgVar    PathBuf,DWORD
ArgVar    FileName,DWORD
ArgVar    PathRef,DWORD
ArgVar    Control,WORD
; note that this stack frame is shared by ChkValidErrs

LocalVar  PathPtr,DWORD         ; ptr. to path string

        EnterProc

        MMPHPreDOSSearchPath            ; Performance Hook (3027h)

        pushf
        SaveReg         <ds,es>
        SaveReg         <bx,cx,si,di>
        cld

        mov     ax,BufLen               ; (ax) = size of work area
        or      ax,ax                   ; Zero length buffer ?
        jnz     dsp10                   ;   No, continue

        mov     ax,ERROR_BUFFER_OVERFLOW ; buffer is too small
        jmp     dsp50                   ; go to exit

dsp10:  dec     ax                      ; (ax) = buffer length - 1
        add     ax,word ptr [PathBuf]   ; (ax) = offset of last byte in buffer
        jnc     dsp15                   ; Buffer doesn't wrap segment

dsp12:  mov     ax,ERROR_INVALID_PARAMETER ; buffer wraps segment
        jmp     dsp50                   ; go to exit

dsp15:
        mov     ax,Control
        and     ax,CHECKCONTROL                 ; check reserved bits for zero
        jnz     dsp12                   ; error


; If IMPLIEDCURR is set in Control then we want to build a path
; using the current directory before looking down PathRef.

        test    Control,IMPLIEDCURR     ; search current dir. first?
        jz      dsp20                   ; no

        push    DATA
        pop     ds
        ASSUME  ds:DATA
        lea     si,CDStr                ; (ds:si) -> "."
        les     di,PathBuf              ; (es:di) -> work area
        ASSUME  es:NOTHING
        mov     cx,BufLen               ; (cx) = size of work area
        call    BuildPath
        or      ax,ax                   ; error?
        LJNZ    dsp50                   ; exit if BuildPath fails on the
                                        ; current directory
        lds     si,FileName
        ASSUME  ds:NOTHING

; (ds:si) -> file name
; (es:di) -> end of path constructed by BuildPath

        mov     bx,BufLen               ; (bx) = length of user buffer
        dec     bx                      ; (bx) = length - 1
        add     bx,word ptr [PathBuf]   ; (es:bx) -> last byte of user buffer
        call    StrCpy                  ; concatenate file name to path
        or      ax, ax                  ; buffer long enough?
        jnz     dsp20                   ; no, search user-given path var

        les     di,PathBuf              ; (es:di) -> path\file
        ASSUME  es:NOTHING
        call    FindFile
        or      ax,ax                   ; file found?
        jz      dsp45                   ; yes
        call    ChkValidErrs            ;
        ljnz    dsp50                   ; error from FindFile (DOSFINDFIRST)

; Get the path string.

dsp20:  lds     si,PathRef              ; (ds:si) -> {env.var. | path string}
        ASSUME  ds:NOTHING

        mov     ax,ERROR_FILE_NOT_FOUND ; prepare for error
        mov     cx,ds
        or      cx,si                   ; is PathRef NULL?
        LJZ     dsp50                   ; yes

        test    Control,PATHREFVAR      ; PathRef -> env. var.?
        jz      dsp30                   ; no

        push    ds                      ; (ds:si) -> env. var.
        push    si
        lea     ax,PathPtr              ; (ss:ax) -> PathPtr
        push    ss
        push    ax
        call    FAR PTR DOSSCANENV
        or      ax,ax                   ; error?
        LJNZ    dsp50                   ; yes

        lds     si,PathPtr              ; (ds:si) -> path string
        ASSUME  ds:NOTHING


; This is the start of the loop where the path string is scanned.
; Path names are built from the separate components in the path string
; and the file name. The loop exits when the file is found,
; or when there are no more paths to search.

; NOTE: the two relevant errors,ERROR_PATH_NOT_FOUND and ERROR_NO_MORE_FILES,
; which can be returned from FindFile are mapped to ERROR_FILE_NOT_FOUND.

dsp30:  mov     ax,ERROR_FILE_NOT_FOUND ; prepare for error
        cmp     BYTE PTR [si],0         ; more paths?
        je      dsp50                   ; no

        les     di,PathBuf              ; (es:di) -> work area
        ASSUME  es:NOTHING
        mov     cx,BufLen               ; (cx) = size of work area
        call    BuildPath
        cmp     ax,ERROR_BUFFER_OVERFLOW      ; not enough space in buffer?
        je      dsp50                   ; yes, go exit
        or      ax,ax                   ; other error?
        jnz     dsp30                   ; yes,try next path
        SaveReg         <ds,si>
        lds     si,FileName             ; (ds:si) -> file name
        ASSUME  ds:NOTHING
        mov     bx,BufLen               ; (bx) = length of user buffer
        dec     bx                      ; (bx) = length - 1
        add     bx,word ptr [PathBuf]   ; (es:bx) -> last byte of user buffer
        call    StrCpy                  ; concatenate file name to path
        or      ax, ax                  ; buffer long enough?
        RestoreReg      <si,ds>         ; ds:si -> next path
        jnz     dsp30                   ; no, search next path

        ASSUME  ds:NOTHING

; es = PathBuf segment

dsp40:  mov     di,PathBufl             ; (es:di) -> path\file to be searched
        call    FindFile
        or      ax,ax                   ; file found?
        jz      dsp45                   ; yes
        call    ChkValidErrs            ;ZR - ignore errors and continue
        jz      dsp30                   ; then continue search
        jmp     SHORT dsp50             ; error from FindFile (DOSFINDFIRST)

; File found.  Canonicalize the name.
; NOTE: We know that DOSICANON is going to copy the source into
;       an internal buffer. Therefore we can cheat and make the
;       source and destination buffers one in the same.

dsp45:  mov     ax,PathBufl
        add     ax,2                    ; point after drive:
        push    PathBufh                ; source
        push    ax
        push    PathBufh                ; destination
        push    ax
        push    ax                      ; backup offset
        add     ax,BufLen
        sub     ax,3                    ; subtract drive offset + index error
        push    ax                      ; destination end
        push    1                       ; permit meta characters in filename
        call    DOSICANONICALIZE
        or      ax,ax                   ; error?
        jz      dsp60                   ; no, jump around clean up

;       error return - there was an error.  AX == error code.  We may
;       have filled the user's buffer with a valid looking path.  Just
;       to be nice we zero the first byte of the buffer.  This will
;       help apps which don't check error codes.

dsp50:  or      BufLen, 0               ; zero length buffer, don't bother
        jz      dsp60
        lds     si, PathBuf
        mov     BYTE PTR [si], 0        ; zero out first byte

dsp60:  RestoreReg      <di,si,cx,bx>
        RestoreReg      <es,ds>
        ASSUME  ds:NOTHING,es:NOTHING
        popf

;***DT  postDOSSEARCHPATH - DosSearchPath post-invocation trace point
;
;    TRACE MINOR = <RAS_DC1_POST_DOSSEARCHPATH>,
;          TP    = .postDOSSEARCHPATH,
;          TYPE  = (POST,API),
;          GROUP = FS,
;          DESC  = "(OS) DosSearchPath Post-Invocation",
;          REGS  = (AX),
;          FMT   = "Return Code = <FMT_WORD>"

        PUBLIC  postDOSSEARCHPATH
postDOSSEARCHPATH:                      ; RAS post-invocation trace
        MMPHPostDOSSearchPath           ; Performance Hook (30A7h)

        LeaveProc                       ; free LocalVar frame
        ret     ?aframe                 ; free ArgVar frame

EndProc DOSSEARCHPATH

;**     FindFile - find a file
;
;       Call DOSFINDFIRST for the path\file specified.
;
;       ENTRY   (es:di) -> path\file to find
;
;       EXIT    if file found
;                       ax = 0
;               else
;                       ax = error_code
;
;       USES    ax,flags
;
;       NOTE:   The functionality of DOSFINDFIRST causes it not to return
;               a valid handle if it does not find a file. Given this we
;               attempt to allocate a handle every time we call it,knowing
;               that if we succeed in finding a file,we will terminate the
;               search.

Procedure FindFile,NEAR
    ASSUME ds:NOTHING,es:NOTHING,ss:NOTHING

LocalVar        ResultBuf,<SIZE ifs_fbuf>
LocalVar        DirHandle,WORD          ; search handle
LocalVar        SearchCnt,WORD          ; number of files searched & found

        EnterProc

        push    es                      ; (es:di) -> path\file
        push    di
        mov     DirHandle,ALLOCSRCH     ; allocate a search handle
        lea     ax,DirHandle            ; (ss:ax) -> DirHandle
        push    ss
        push    ax
        push    attr_all                ; read-only,hidden and system files
        lea     ax,ResultBuf            ; buffer for DOSFINDFIRST
        push    ss
        push    ax
        push    SIZE ifs_fbuf           ; size of ResultBuf
        mov     SearchCnt,1             ; search for 1 file
        lea     ax,SearchCnt            ; (ss:ax) -> SearchCnt
        push    ss
        push    ax
        push    0                       ; RESERVED DWORD 0s
        push    0
        call    DOSFINDFIRST

        or      ax,ax                   ; was search handle allocated?
                                        ; (file found?)
        jnz     ff10                    ; no

        push    DirHandle               ; close the search handle
        call    DOSFINDCLOSE
        xor     ax,ax                   ; DOSFINDFIRST succeeded,that's
                                        ; all we care about
ff10:   LeaveProc
        ret

EndProc FindFile

;**     BuildPath - builds a path
;
;       BuildPath will attempt to construct a path using the path string
;       passed as an argument. If the drive is not specified BuildPath
;       will assume the current drive. If the path string is relative
;       it will assume the current directory as a starting point.
;
;       The path that is built in the buffer is terminated by a '\' but
;       is not NUL terminated. This is done so that a file name can easily
;       be concatenated to the path.
;
;       ENTRY   (ds:si) -> path string
;               (es:di) -> buffer
;               cx = buffer length
;
;       EXIT    (ds:si) -> next path
;               (may point to NUL terminator)
;               e.g. FOO=bar;blat
;                            ^
;                            |
;                          ds:si
;
;               Success:
;                       AX == 0
;                       buffer contains complete path from the root
;                       including the drive
;                       (es:di) -> one character after terminating '\'
;                       e.g. c:\bar\
;                                   ^
;                                   |
;                                 es:di
;               Error:
;                       ax = error_code
;
;       USES    ax, bx, cx, flags

Procedure BuildPath,NEAR
        ASSUME  ds:NOTHING,es:NOTHING

LocalVar        pDest,DWORD             ; -> destination buffer
LocalVar        drive,WORD              ; drive number
LocalVar        dirpathlen,WORD         ; current directory path length
LocalVar        ldm,DWORD               ; logical drive map
LocalVar        startPath,WORD          ; used to determine if root directory
LocalVar        pNext,DWORD             ; -> next path
LocalVar        NicePath,<MAXPATHLEN>   ; formatted path

        EnterProc <es,di>               ; set pDest

        mov     bx, di                  ; (es:bx) -> buffer
        add     bx, cx                  ; (es:bx) -> end of buffer + 1
        mov     startPath, di           ; save beginning of buffer
        add     startPath, 4            ; point to character after a:\'\0'

        TaskContext es,,noassume
        lea     di,NicePath             ; es:di -> NicePath
        call    FixPath                 ; extract one path into NicePath
        mov     ax,ERROR_FILE_NOT_FOUND
        ljc     bup190                  ; error, go exit

; ds:si -> next path

        mov     pNext.segmt,ds          ; save pointer to next path
        mov     pNext.offst,si

; make sure result buffer length is at least 3 (to contain minimum path D:\)
; we purposely test this after calling FixPath so we have pNext
; set up to return to user even if error

        cmp     cx,3                    ; buffer length too short?
        jb      bup90                   ; yes, go return error

        TaskContext ds,,noassume
        mov     si,di                   ; ds:si -> NicePath
        les     di,pDest                ; es:di -> destination buffer

        cmp     BYTE PTR [si + 1],':'   ; does the path have a drive?
        je      bup10                   ; yes

; path has no drive letter; add one to it unless UNC path

        cmp     word ptr [si],'\\'      ; UNC path?
        je      bup40                   ; yes, don't add drive letter

; construct drive: in buffer

        lea     ax,drive                ; (ss:ax) -> drive
        push    ss
        push    ax
        lea     ax,ldm                  ; (ss:ax) -> ldm
        push    ss
        push    ax
        call    DOSQCURDISK             ; get current drive
        or      ax,ax                   ; error?
        ljnz    bup180                  ; yes, return error
        mov     ax,':@'
        add     al,BYTE PTR drive       ; convert to drive:
        stosw                           ; put it into buffer
        jmp     SHORT bup20

bup10:  lodsw                           ; get drive letter and ':'
        and     al,NOT 20h              ; convert drive letter to upper case
        stosw                           ; copy drive: to buffer
        sub     al,'A' - 1              ; convert drive letter to number
        cbw                             ; drive number in
        mov     drive, ax               ; save drive number

bup20:  cmp     BYTE PTR [si],'\'       ; does the path start at the root?
        je      bup40                   ; yes

; DOSQCURDIR does not return the leading '\'

        mov     al,'\'
        stosb                           ; add root dir. to buffer

        push    drive                   ; drive number
        push    es                      ; (es:di) -> buffer
        push    di
        sub     cx,3                    ; account for "drive:\"
        mov     dirpathlen,cx           ; dirpathlen = length of buffer unused
        lea     ax,dirpathlen           ; ss:ax -> dirpathlen
        push    ss
        push    ax
        call    DOSQCURDIR              ; copy current directory into buffer
        or      ax,ax                   ; error?
        jnz     bup180                  ; yes
        repne   scasb                   ; advance di to end by looking for NUL
                                        ; terminator (al = NUL)
        dec     di                      ; step back to NUL

; If the current directory is not the root, we append a '\' at end

        cmp     startPath, di           ; is current directory '\'?
        ja      bup40                   ; yes
        mov     al,'\'
        stosb                           ; add dir. separator to buffer

; When we get here there may already be a drive specifier and current
; directory in the buffer. We now append the path into the buffer.

bup40:  mov     cx,bx                   ; ds:cx -> end of buffer + 1
        sub     cx,di                   ; cx = free bytes remaining in buffer
        jbe     bup90                   ; is this possible?

bup60:  lodsb
        or      al,al                   ; end of string?
        jz      bup100                  ; yes, stop copying

        stosb                           ; copy a char
        loop    bup60                   ; repeat until buffer overflow

bup90:  mov     ax,ERROR_BUFFER_OVERFLOW        ; return error code
        jmp     SHORT bup180                    ; go exit

; append a '\' to path if not already ended with '\'

ifdef CAUTIOUS

; as of oct 3, 1989, DosICanonicalize will compress adjacent '\'s in a path
; except the root path. Findfile also seems to compress adjacent '\'s in a
; path. E.g. if c:\tmp\abc exist, then findfile will also find c:\tmp\\\abc.
; Since we call DosICanonicalize later on, we can just blindly append a '\'
; to the path unless it's a root. If this feature disappear then we need to
; enable the CAUTIOUS code.     --pwu

bup100: cmp     byte ptr es:[di-1],'\'  ; path ends in '\' ?
        jne     bup160                  ; no, go append '\'

; path may end with '\' or with a DBCS char that has a trail byte of '\'

        SaveReg <di>
        sub     di,2                    ; es:di -> byte before '\'
        lea     cx,[di+1]               ; es:cx -> '\'
        sub     cx,pDest.offst          ; cx = number of bytes in path - 1
        call    DBCS_Sure                 ; is it a DBCS lead byte?
        RestoreReg <di>                 ; es:di -> byte after last '\'
        jne     bup170                  ; no, don't append '\'
else

bup100: cmp     startpath,di            ; root?
        ja      bup170                  ; yes, don't append '\'
                                        ; no, append '\'
endif

bup160: mov     al,'\'                  ; terminate path with '\'
        stosb

bup170: xor     ax,ax                   ; no error
bup180: lds     si,pNext                ; ds:si -> next path
    ASSUME  DS:NOTHING
bup190: LeaveProc
        ret

EndProc BuildPath


ifdef CAUTIOUS
;**     DBCS_Sure - determine if a byte amidst a string is a dbcs lead byte
;
;       Testing for a DBCS lead byte is complicated because given a byte
;       without context we can only differentiate one of these two:
;         1) it is not a DBCS lead byte (it must be a DBCS trail byte or a
;            single-byte character)
;         2) it may either be a DBCS lead byte or a DBCS trail byte
;
;       ENTRY   es:di -> byte in question (in the middle of a string)
;               cx     = #bytes from the beginning of the string up to
;                        and including the byte in question
;
;       EXIT    'NZ' - byte in question is a not a DBCS lead byte (could be a
;                      dbcs trail byte, a single-byte character, or DBCS is not
;                      active at all)
;               'ZR' - byte in question is a DBCS lead byte for sure
;
;       USES    ax,cx,si,di,ds,flags
;
;       PSEUDO
;
;       if (dbcs not active)
;         return NZ
;       do {
;         if (es:[di] is not a lead byte)
;           return NZ
;         di--
;         if (di has passed beginning of string)
;           return ZR
;         if (es:[di] is not a lead byte)
;           return ZR
;         di--
;         if (di has passed beginning of string)
;           return NZ
;       } while (1)

Procedure DBCS_Sure
    assume ds:nothing,es:nothing

        push    DATA
        pop     ds
    assume ds:DATA
        cmp     dbcs_data,0             ; DBCS active?
        je      dbt120                  ; no, go return NZ

dbt20:  mov     bl,es:[di]              ; bl = byte in question
        call    DBCS_ok                 ; can this be a lead byte?
        jne     dbt110                  ; no, go return NZ

; could be a lead byte, but also could be a trail byte, so look at the previous
; byte for more clues

        dec     di                      ; backup one byte
        dec     cx                      ; bytes remaining
        jz      dbt110                  ; no more to examine, go return ZR

        mov     bl,es:[di]              ; bl = byte to examine
        call    DBCS_ok                 ; can this be a lead byte?
        jne     dbt100                  ; no, go return ZR

        dec     di                      ; backup one byte
        loop    dbt20                   ; cx = bytes remaining; loop if not 0

        inc     cl                      ; set NZ
        ret

dbt100: xor     al,al
dbt110: ret

dbt120: or      sp,sp                   ; set NZ
        ret
EndProc DBCS_Sure


;**     DBCS_ok - check for DBCS lead byte
;
;       caller must make sure dbcs is active
;
;       ENTRY   bl = byte in question
;               ds = _DATA
;
;       EXIT    'ZR' - if bl may be a lead byte (may also be a trail byte)
;               'NZ' - if bl is not a lead byte
;
;       USES    ax,si

Procedure DBCS_ok,near,local
    assume ds:_DATA,es:nothing

        lea     si,dbcs_data            ; ds:si -> dbcs lead byte range table
        lodsw                           ; al = low limit; ah = high limit

tdb20:  cmp     bl,al                   ; below low range?
        jb      tdb40                   ; yes, not a lead byte, return NZ
        cmp     bl,ah                   ; within hi range
        jbe     tdb50                   ; yes, a lead byte, return ZR

; out of current set of range; check next range

        lodsw                           ; al = low limit; ah = high limit
        or      ax,ax                   ; end of DBCS range table?
        jnz     tdb20                   ; no, keep checking

; end of range table. Byte is not a lead byte

        inc     al                      ; set NZ
tdb40:  ret

tdb50:  xor     al,al                   ; set ZR
        ret

EndProc DBCS_ok
endif ; ifdef CAUTIOUS


;**     FixPath - convert path into nicer format
;
;       Extract one path from the input, apply the following and copy to the
;       destination buffer:
;         a) remove quotes
;         b) result path always terminated by nul
;         c) convert / into \
;
;       This works for DBCS strings.
;
;       ENTRY   ds:si -> semi-colon or null terminated path. Part of the
;                        path may be quoted.
;                        E.g. c:\abc;.;\
;                             "d:\x y";c:\tmp;a:\"x;y";
;               es:di -> buffer to hold result (assume big enough)
;
;       EXIT    if NC - success
;                 result buffer has one nul-terminated, unquoted path
;                 ds:si -> beginning of next path
;                       -> null if no more path
;
;               if CY - error: quoted string not terminated
;
;       USES    si, flags

Procedure FixPath
    assume ds:nothing,es:nothing

        SaveReg <ax,di,bx>

; ah is used as a flag to indicate whether we're inside a quoted string.
; If so, ah = 0. If not, ah = -1.

        xor     ah,ah                   ; ah = 0 means inside quoted string
        xor     bx,bx                   ;### B722853
fip5:   not     ah                      ; toggle quote flag

; fortunately for us " / and null are all invalid DBCS lead and trail byte;
; therefore we don't need to test for DBCS lead characters

fip10:  lodsb                           ; al = char
        cmp     al,'"'                  ; start or end of quoted string?
        je      fip5                    ; go toggle flag

        cmp     al,'/'                  ; forward slash?
        je      fip50                   ; yes, convert to back slash

        or      al,al                   ; end of string?
        jz      fip80                   ; yes, go exit

        or      ah,ah                   ; are we inside quoted string?
        jz      fip40                   ; yes, don't check for ;

        cmp     al,';'                  ; end of path?
        je      fip60                   ; yes

fip40:  inc     bx                      ;### B722853
        cmp     bx,MAXPATHLEN           ;### B722853
        ja      fip85                   ;### B722853
        stosb                           ; save in destination buffer
        jmp     short fip10

fip50:  mov     al,'\'                  ; convert to \
        jmp     short fip40

fip60:  xor     al,al                   ; terminate with null

fip70:  stosb                           ; terminate the path
        sub     ah,1                    ; set carry if inside quoted string
fip75:  RestoreReg <bx,di,ax>
        ret

fip80:  dec     si                      ; ds:si -> nul (no more paths)
        jmp     short fip70
fip85:  stc
        jmp     short fip75
EndProc FixPath

;**     ChkValidErrs - check for the subset of errors that are valid
;
; ****** SHARES THE STACK FRAME OF DOSSEARCHPATH *****************
;
;       ENTRY   AX = error code from FindFile
;
;       EXIT    ZR set -> error can be ignored and search can be continued
;
;       USES    es, di, cx, flags

Procedure       ChkValidErrs,NEAR,LOCAL
        Assume DS:nothing,ES:nothing

        push    DATA
        pop     es
        ASSUME  es:DATA
        mov     di,offset DATA:ValidErrs ; es:di -> valid error table
        mov     cx,NormalErrCount       ;
        test    Control,IGNORENETERR    ;
        jz      cve10                   ;
        mov     cx,NetErrCount          ;
cve10:  repnz   scasw                   ; is the error ignorable ?
        ret

EndProc ChkValidErrs

;**     DOSSCANENV - Scan the environment for a variable
;
;       DOSSCANENV scans the caller's environment segment for the requested
;       environment variable. The environment is assumed to be in the standard
;       format. DOSSCANENV is case sensitive.
;
;       ENTRY   push    @EnvVar         ; env. variable (@ASCIIZ)
;               push    @VarPtr         ; pointer to env. variable (@DWORD)
;
;       EXIT    SUCCESS: ax = 0
;                        VarPtr filled in with pointer to string
;                        e.g. FOO=bar;blat
;                                 ^
;                                 |
;                              VarPtr
;
;               FAILURE: ax = error_code
;
;       USES    ax,flags

;***DT  preDOSSCANENV - DosScanEnv pre-invocation trace point
;
;    TRACE MINOR = <RAS_DC1_PRE_DOSSCANENV>,
;          TP    = .DOSSCANENV,
;          TYPE  = (PRE,API),
;          GROUP = LNK,
;          DESC  = "(OS) DosScanEnv Pre-Invocation",
;          ASCIIZ= (<R3SP_ARGS>+<C_PARM2>,I,<MAXPATHLEN>),
;          MEM   = (<R3SP_ARGS>+<C_PARM1>,D,4),
;          FMT   = "Current Env = <FMT_ASCIIZ>",
;          FMT   = "Env Pointer = <FMT_C_P16>"

Procedure DOSSCANENV,FAR
        ASSUME  ds:NOTHING,es:NOTHING,ss:NOTHING

ArgVar  VarPtr,DWORD
ArgVar  EnvVar,DWORD

LocalVar        EnvSel,WORD             ; env. selector
LocalVar        CmdOff,WORD             ; cmd. offset in env. segment
LocalVar        evLen,WORD              ; length of env. var.

        EnterProc

        MMPHPreDOSScanEnv               ; Performance Hook (140Ah)

        pushf
        SaveReg         <ds,es>
        SaveReg         <cx,si,di>

        cld

        lea     ax,EnvSel               ; (ss:ax) -> EnvSel
        push    ss
        push    ax
        lea     ax,CmdOff               ; (ss:ax) -> CmdOff
        push    ss
        push    ax
        call    DOSGETENV               ; get env. selector
        or      ax,ax                   ; error?
        jnz     dse4                    ; yes

dse1:   les     di,EnvVar               ; (es:di) -> env. var.
        ASSUME  es:NOTHING
        call    Strlen                  ; cx = strlen(EnvVar)
        mov     evLen,cx                ; save length of env. var.
        or      cx,cx                   ; NULL envvar?                  ;b718493
        jnz     dse1a                   ; no                            ;b718493
        mov     ax,ERROR_ENVVAR_NOT_FOUND                               ;b718493
        jmp     dse4                    ; yes                           ;b718493


dse1a:  mov     es,EnvSel               ; es = env. selector            ;b718493
        ASSUME  es:NOTHING
        xor     di,di                   ; (es:di) -> environment
        lds     si,EnvVar               ; (ds:si) -> env. var.
        ASSUME  ds:NOTHING

; This is the beginning of the loop where the env. is scanned.
; We attempt to match the env. variable string followed by an '='.
; If this matches we have found what we were looking for. Otherwise
; we look for the end of the string and try again. The loop exits
; if the env. var is found or we come to the end of the env. segment.
;
; ds = env. var segment
; es = env. segment

dse2:   mov     si,EnvVarl              ; (ds:si) -> env. var.
        mov     cx,evLen                ; cx = strlen (EnvVar)
        repe    cmpsb                   ; strcmp (ds:si,es:di)
        jne     dse2_2                  ; no match

        cmp     BYTE PTR es:[di],'='    ; is the next char an '='?
        je      dse3                    ; yes,env. var. found

dse2_2: dec     di                      ; backup to failing character
        mov     cx,-1                   ; "unlimited" scan (64k)
        xor     al,al                   ; al = NUL
        repne   scasb                   ; find start of next env. var.
        mov     ax,ERROR_ENVVAR_NOT_FOUND
        cmp     BYTE PTR es:[di],0      ; end of environment?  (2nd null byte)
        je      dse4                    ; yes
        jmp     SHORT dse2              ; no,continue looking

dse3:   inc     di                      ; (es:di) -> after '='
        lds     si,VarPtr               ; (ds:si) -> VarPtr
        ASSUME  ds:NOTHING
        mov     [si],di                 ; return es:di to caller
        mov     [si + 2],es
        xor     ax,ax                   ; no error

dse4:

;***DT  postDOSSCANENV - DosScanEnv post-invocation trace point
;
;    TRACE MINOR = <RAS_DC1_POST_DOSSCANENV>,
;          TP    = .postDOSSCANENV,
;          TYPE  = (POST,API),
;          GROUP = LNK,
;          DESC  = "(OS) DosScanEnv Post-Invocation",
;          REGS  = (AX),
;          FMT   = "Return Code = <FMT_WORD>"

        PUBLIC  postDOSSCANENV
postDOSSCANENV:                         ; RAS post-invocation trace
        MMPHPostDOSScanEnv              ; Performance Hook (148Ah)

        RestoreReg      <di,si,cx>
        RestoreReg      <es,ds>
        ASSUME  ds:NOTHING,es:NOTHING
        popf
        LeaveProc
        ret     ?aframe

EndProc DOSSCANENV

;**     StrCpy - copy an ASCIIZ string.
;**     StrNCpy - copy an ASCIIZ string up to a maximum number of bytes.
;
;       StrCpy copies an ASCIIZ string from source to destination.
;       StrNCpy limits the copy to the number of bytes specified.
;
;       It is assumes that a DBCS trailing byte can never be a NUL.
;
;       ENTRY   (ds:si) -> source
;               (es:di) -> destination
;               (es:bx) -> end of buffer
;               cx = number of bytes to copy (StrNCpy only)
;
;       EXIT    StrCpy
;                       string copied
;                       (ds:si) & (es:di) -> after NUL terminator
;               StrNCpy
;                       same as StrCpy, OR
;                       cx bytes copied and si and di advanced by cx
;               if buffer not long enough, return ERROR_NOT_ENOUGH MEMORY
;
;       USES    ax,flags

Procedure StrCpy,NEAR
        ASSUME  ds:NOTHING,es:NOTHING

        mov     cx,NOT 0                ; 'unlimited' copy

Entry   StrNCpy

        jcxz    sc1                     ; No bytes to copy

sc0:    cmp     di,bx                   ; Past end of buffer?
        ja      sc2                     ;   YES, return error
        lodsb
        stosb
        or      di,di                   ; did we wrap around?
        jz      sc0d                    ;  yes, exit the loop

        or      al,al                   ; End of string?
        loopnz  sc0                     ;   No, copy next byte

; NUL terminate the string

sc0d:   mov     BYTE PTR es:[di - 1], 0

sc1:    xor     ax,ax                   ; (ax) = success
        ret

sc2:    mov     ax,ERROR_BUFFER_OVERFLOW
        ret

EndProc StrCpy

;**     StrLen - compute the length of an ASCIIZ string
;
;       ENTRY   (es:di) -> source
;
;       EXIT    cx = length not including NUL
;
;       USES    al,flags

Procedure StrLen,NEAR
        ASSUME  ds:NOTHING,es:NOTHING

        SaveReg         <es,di>

        mov     cx,NOT 0
        xor     al,al           ; al = NUL
        repne   scasb
        not     cx              ; cx = length
        dec     cx              ; don't include NUL

        RestoreReg      <di,es>
        ASSUME  es:NOTHING
        ret

EndProc StrLen

CODE16C_TUNE3   ENDS

END
