SCCSID  equ     "@(#)macro.asm  6.10 92/03/11"
PAGE    ,132
TITLE   MACRO - Pathname and macro related internal routines
NAME    MACRO
;       IBM/Microsoft Confidential
;
;       Copyright (c) IBM Corporation 1987, 1989
;       Copyright (c) Microsoft Corporation 1987, 1989
;
;revision log:
;defect:                 comments:                        name:
;76318          Transpath2 traps on invaild input       Gary Dommer
;80608          Allow long UNC names under DOS, but     Skip Nizinski
;               not long local names in FSD_Processname.
;72417          11/27/95                                MLM
;               moved from PTDA to TCB:
;                 DevPt, ptda_pSrchBuf,
;                 WFP_Start, Curr_Dir_End, ExtFCB, Sattrib, Joins, cMeta
;                 WFP_Path_End, PathNameType, Name1
;
;
;       All Rights Reserved
;
;       Global          Local
;
;       $NameTrans
;       w_NameTrans
;       AsgCheckDrive
;       acd_proc
;       Find_DPB
;       InitCDS
;       GetVisDrv
;       GetThisDrv
;                       GetThisDrvSrv
;       GetCDSFromDrv
;       GetCDS
;       DupCDS
;       AddCDS
;       DelCDS
;       NextCDS
;       TransFCB
;       ChkVal
;       TransPath
;       TransPathNoJoinIfSame
;       TransPathNoJoin
;       TransPathNoPCName
;       TransPathNoErrNMP
;       TransPath2
;       Canonicalize
;       SkipBack
;                       CopyComponent
;       ValidateCDS
;                       Join
;       DriveFromText
;       TextFromDrive
;       TextFromDrive1
;       PathPref
;       ScanPathChar
;       w_Canon
;       StrTransPathChar
;       ScanCDS
;       FSCFromCDS
;                       FSD_ProcessName
;                       CheckLastComponent
;                       FindPathEnd
;                       Trunc8dot3
;       _PathCharTrans
;
;
;   OS/2 performs several types of name translation.  First, we maintain for
;   each valid drive letter the text of the current directory on that drive.
;   For invalid drive letters, there is no current directory so we pretend to
;   be at the root.  A current directory is either the raw local directory
;   (consisting of drive:\path) or a remote directory (consisting of
;   \\machine\path.  There is a limit on the point to which a .. is allowed.
;
;   (Real mode only)  The first part of name translation involves SUBST
;   aliasing.  SUBST is nothing more than specifying a part of the tree that
;   appears before the 'root' pointer in the CDS.  It is hidden from the user
;   name space, but not from the logical namespace that the system deals with.
;   SUBST roots are stored in the (reserved) first 26 CDS handles.  This is
;   a special case of current directory.  There is actually more current
;   directory than the user sees (and on real drive too).
;
;   Given a path, OS/2 will then transform this into a real from-the-root
;   (adding the current directory if necessary) path without .  or ..  entries.
;
;   No 8.3 truncation is done by normal name translation.  This is a FAT
;   specific restriction and is done elsewhere.  No meta-character substitution
;   is done either.
;
;   (Real mode only)  A third part involves mapping JOIN aliases.  JOIN is a
;   mapping of a physical disk onto a (1st level) directory on another disk.
;   The current path is examined to see if it falls below a JOINed path.  If
;   it does, then the JOINed path is replaced with the drive letter.  JOINed
;   drives are stored in the (reserved) first 26 CDS handles.
;
;   Remote drives (Redir & FSDs) are handled with a SUBST like structure which
;   maps a pseudo-disk onto a remote location.
;
;   TransPath is the main entry point.  TransPath will take a path and form
;   the real text of the pathname with all .  and .. removed.
;
;   Implementation note:  cdi_end field points to the point in the text
;   string where the user may back up to via ..  It is the location of a
;   separator character.  For the root, it points at the leading \.  For remote
;   assignments it points at the end (nul) of the initial assignment:
;
;   A:\     \\foo\bar       \\foo\bar\blech\bozo
;     ^              ^               ^
; A: -> d: \path\ path\ text
;
;   See CURDIR.INC for more information on CDS structure and usage.
;
.xlist
;
; get the appropriate segment definitions
;
include dosseg.inc
include defdbug.inc
include basemaca.inc
include osmaca.inc
include fnofs.inc
include oldmaca.inc
include osconst.inc
.xcref
include chardd.inc
include dirent.inc
include dossymc.inc
include dpb.inc
include error.inc
include exec.inc
include filesys.inc
include fsc.inc
include meta.inc
include mi.inc
include mult.inc
include protmode.inc
include timer.inc
include vpb.inc
include dbcs.inc
include proc.inc
include pgexport.inc
include vmexport.inc
include tkexport.inc
.cref
.list
.sall
include curdir.inc
include find.inc
include infoseg.inc                      ;  @PAA04757 for SIS_BootDrv

        ModName , FNO_MACRO_ASM

        ReferGlobal     CDSAddr,WORD    ; handle of CDS table
        ReferGlobal     DevPrefix,BYTE  ; device prefix (/DEV/)
        ReferGlobal     NumIO,BYTE      ; Number of physical drives
        ReferGlobal     NumIORipl,BYTE  ; # of reserved drives for RIPL
        ReferGlobal     UNCfscList,BYTE ; offset into DOS segmt for UNCFscList
        ReferGlobal     UNCfscLast,BYTE ; offset of last entry in UNCFscList
        ReferGlobal     DPBHead,DWORD   ; beginning of DPB chain
        ReferGlobal     hVPBFirst,WORD  ; handle for first VPB in list
        ReferGlobal     uchLastDrive,BYTE
        ReferGlobal     SISData,BYTE    ;@PAA04757A - for SIS_BootDrv
        ReferGlobal     DriveLetter,BYTE    ; Drive Letter Table     @99999

        ReferGDT        GDT_FSC,WORD
        ReferGDT        GDT_VPB,WORD

        EXTRN           CharType:BYTE
        EXTRN           PIPEDIRLEN:ABS

        ReferTask       CDSsize,WORD            ; Size of CDS pointed to by ThisCDS

                                                ;72417 removed PTDA entries now
                                                ;      in TCB

        ReferTask       CDS_Handle,WORD         ; handles of current directories

                                                ;72417 removed PTDA entries now
                                                ;      in TCB

        ReferTask       Pid,WORD

                                                ;72417 removed PTDA entry   now
                                                ;      in TCB

        ReferTask       ExtErr_Locus,BYTE       ; Extended Error Locus
        ReferTask       CurrTCB,WORD            ; Current thread control block
        ReferTask       ProcType,BYTE
        ReferTask       LCurDrv,BYTE
        ReferTask       PCurDrv,BYTE
                                                ;72417 removed PTDA entries now
                                                ;      in TCB
        ReferTask       CP_CaseMapTbl,DWORD

        EXTRNFAR        CheckPipeDir
        EXTRNFAR        ScanTasks
        EXTRNFAR        FindChar
        EXTRNFAR        FreeTempBuffer
        EXTRNFAR        GetExtended
        EXTRNFAR        GetTempBuffer
        EXTRNFAR        Rmp_FindN
        EXTRNFAR        Rmp_Add
        EXTRNFAR        Rmp_Rem
        EXTRNFAR        StrLen
        EXTRNFAR        ProbeBuff               ; AWJ67294

        EXTRN           CheckNotPipeDir:NEAR
        EXTRN           CheckPathSyntax:NEAR
        EXTRN           FStrCpy:NEAR
        EXTRN           StrCpy:NEAR
        EXTRN           UCase:NEAR
        EXTRNFAR        CheckThisDevice
        EXTRN           ExmDBCS:NEAR
        EXTRN           FSD_ChDir:NEAR
        EXTRN           FSD_ChDirFree:NEAR
        EXTRN           FuBuff:NEAR
        EXTRN           NameTrans:NEAR
        EXTRN           PackName:NEAR
        EXTRN           SuBuff:NEAR
        EXTRN           WhatVolume:NEAR
        EXTRN           RlseBuffr:NEAR
        EXTRNFAR        Take_Kernel_Sem      ; 72291
        EXTRNFAR        Clear_Kernel_Sem     ; 72291


        FARCODE HIGH2
HIGH2CODE SEGMENT
        ASSUME  SS:TASKAREA

;**     w_NameTrans - Perform a name translation
;
;       This call performs only string substitution and canonicalization,
;       not splicing.  Due to TransPath playing games with devices, we
;       need to insure that the output has drive letter and : in it.
;
;       ENTRY   DS:SI - source string for translation
;               ES:DI - pointer to buffer
;       EXIT
;           Carry Clear
;               Buffer at ES:DI is filled in with data
;               ES:DI point byte after nul byte at end of dest string in buffer
;           Carry Set
;               AX = ERROR_PATH_NOT_FOUND
;
;       USES    AX,BX,CX,DX,SI,DI,BP,DS

        ASSUME  DS:NOTHING,ES:NOTHING,SS:TASKAREA
Procedure   w_NameTrans,<far16>
LocalVar  wnt_openbuf,PMPATHLEN
LocalVar  wnt_dummy,WORD                ; KLUDGE: see gshare.asm
LocalVar  wnt_dummycds,<SIZE curdir>
LocalVar  wnt_searchbuf,SRCHBUFSIZE+1
        EnterProc
        SaveReg <DS,SI,ES,DI>
        lea     di,wnt_searchbuf
                                        ;72417 moved from PTDA to TCB
        mov     di,CurrTCB
        mov     TASKAREA:[di].TCB_pSrchBuf,di ; Initialize ptda_pSrchBuf
                                        ;72417 end
        lea     di,wnt_dummycds
        push    bx                              ;### PTR B720732
        mov     bx,TASKAREA:[CurrTCB]                      ;### PTR B720732
        mov     TASKAREA:[bx].TCBpTmpCDS.Offst,di       ; Initialize TCBpTmpCDS ;### PTR B720732
;### PTR B720732        mov     TASKAREA:[ptda_pCDS],di ; Initialize ptda_pCDS
        lea     di,wnt_openbuf
        mov     TASKAREA:[bx].TCBpOpenBuf,di            ; Initialize TCBpOpenBuf ;### PTR B720732
;### PTR B720732        mov     TASKAREA:[ptda_pOpenBuf],di ; Initialize ptda_pOpenBuf
        pop     bx                              ;### PTR B720732
        call    TransPath               ; do translation (everything)
        RestoreReg  <DI,ES,SI,DS>
        jc      wnt1
        lea     si,wnt_openbuf
        TaskContext DS
        call    StrCpy
        clc
;### PTR B720732wnt1:   mov     SS:[ptda_pOpenBuf],0    ; Clear ptda_pOpenBuf
                                        ;72417 moved from PTDA to TCB
wnt1:
        push    bp
        mov     bp,ss:[CurrTCB]
        mov     ss:[bp].TCB_pSrchBuf,0     ; Clear ptda_pSrchBuf
        pop     bp
                                        ;72417 end
;### PTR B720732        mov     SS:[ptda_pCDS],0        ; Clear ptda_pCDS
        LeaveProc
        RETP
EndProc w_NameTrans


;***    AsgCheckDrive - Check for references to a drive
;
;       Several conditions are checked to determine if there is a
;       reference to a particular drive.
;
;       ENTRY   BX = 1-based drive number
;
;       EXIT    C clear if no error,
;                   AX = ASG_NOREF if no references, else bits set for failures:
;                       ASG_CURDIR   - some process has current dir. there
;                       ASG_CURDRIVE - some process has current drive there
;                       ASG_OPENFILE - an open file reference to the drive
;               C set if error, AX = standard error code
;
;       USES    All except BP

Procedure       AsgCheckDrive,hybrid
LocalVar        RetVal,WORD
LocalVar        DrvNum,WORD
        PMONLY

        EnterProc
        xor     ax,ax
        mov     RetVal,ASG_NOREF        ; initialize return value
        mov     al,bl
        dec     al
        mov     DrvNum,ax
        call    Find_DPB                ; find DPB for drive
        jc      acd4                    ; no DPB, must be logical drive
        mov     cx,ds                   ; set DPB seg pointer for easy compare
        DosContext ds
        mov     ax,hVPBFirst            ; ax => VPB to examine
        mov     ds,cx                   ; restore previous ds
    ASSUME      DS:NOTHING
acd1:
    .errnz      NULLVPB
        or      ax,ax                   ; are we at the end of the list?
        jz      acd4                    ; yes, go and allocate new VPB
        mov     di,ax                   ; (di) = vpb handle
        VPBContext es,di                ; (es:di) -> VPB
        cmp     WORD PTR es:[di].vpb_fsi.vpi_pDPB,si
        jnz     acd3                    ; doesn't refer to this drive
        cmp     WORD PTR es:[di].vpb_fsi.vpi_pDPB+2,cx
        jnz     acd3                    ; doesn't refer to this drive
        cmp     ax,ds:[si.dpb_hVPB]     ; does DPB refer to VPB?
        jnz     acd2                    ; no, report error
        cmp     es:[di].vpb_ref_count,1 ; is DPB only reference?
        jz      acd3                    ; yes, try next VPB
acd2:   or      RetVal,ASG_OPENFILE
        jmp     short acd4

acd3:   mov     ax,es:[di].vpb_flink    ; no, link to next VPB
        jmp     acd1

; VPB check done, now iterate through all PTDAs to check for current drv/dir
acd4:
        MOVFAROFFSET di,acd_proc
        mov     bx,TK_TG_ALL or TK_SF_WORKER ; apply acd_proc to all processes
        CALLFAR ScanTasks
        mov     ax,RetVal
        clc
        LeaveProc
        ret

;***    acd_proc - check whether PTDA references drive in question
;
;       ENTRY   ES - points to PTDA
;
;       EXIT    RetVal - set if current drive or directory reference
;
;       USES    BX, DS
;

Procedure acd_proc, hybrid, local
    ASSUME DS:NOTHING, ES:NOTHING
        DosContext ds
        mov     bx,DrvNum               ; get target drive number
        cmp     bl,es:[PCurDrv]         ; is it this proc's default drive?
        jnz     acdp1
        or      RetVal,ASG_CURDRIVE
acdp1:  add     bx,bx                   ; make drive number word index
        cmp     es:[CDS_Handle+bx],0    ; is there a c.d. there?
        jz      acdp5
        or      RetVal,ASG_CURDIR
acdp5:  ret
EndProc acd_proc
EndProc AsgCheckDrive



Break <Find_DPB - Find a DPB from a drive number>

;***    Find_DPB - Find a DPB that corresponds to a drive number
;
;   ENTRY   AL has drive number A = 0
;
;   EXIT    Carry Set
;               No DPB for this drive number
;           Carry Clear
;               DS:SI points to DPB for drive
;
;   USES    DS,SI
;
Procedure Find_DPB,<near,faronly,far16>
    ASSUME  DS:NOTHING,ES:NOTHING,SS:TASKAREA
        PMONLY

        DosContext DS
        LDS     SI,[DPBHead]
    ASSUME      DS:NOTHING
DPB_LOOP:
        CMP     SI,0ffffh           ;use ffff instead of -1 for clarity
        JZ      NO_DPB
        CMP     AL,[SI.dpb_drive]
        jz      fdpbx                   ; Carry clear
        LDS     SI,[SI.dpb_next_dpb]
        JMP     DPB_LOOP

NO_DPB: STC
fdpbx:  RET
EndProc Find_DPB



Break <InitCDS - set up an empty CDS>

;***    InitCDS -- set up an empty CDS
;
;       ENTRY   ThisCDS is ptda_pCDS
;               AL has uppercase drive letter
;
;       EXIT    ES:DI -> CDS
;               [ThisCDS] = ptda_pCDS, which is now empty
;               NC - if DPB associated with drive
;                   [CDSsize] set up with size of CDS pointed to by ThisCDS
;                   ES:DI.cd_flags = CD_INUSE
;               CY - if no DPB associated with drive
;
;       USES    AH, DI, ES
;
Procedure InitCDS,HYBRID
    ASSUME  DS:NOTHING,ES:NOTHING,SS:TASKAREA
        PMONLY

        SaveReg <BX,DS>
        mov     di,TASKAREA:CurrTCB             ; (di) = current TCB     ;### PTR B720732
        les     di,TASKAREA:[di].TCBThisCDS     ; (es:di) = CDS pointer  ;### PTR B720732
;### PTR B720732        LES     DI,[ThisCDS]            ; ES:DI = ThisCDS = ptda_pCDS
        ASSUME  ES:NOTHING
;       Zero out fs dependent part
        SaveReg <DI,CX,AX>
        MOV     CX,(SIZE curdir - SIZE cd_fsi) SHR 2
        .386
        XOR     EAX,EAX
        REP     STOSD
        .errnz  (SIZE curdir - SIZE cd_fsi) AND 2
        CpuMode reset
        STOSB
        .erre   (SIZE curdir - SIZE cd_fsi) AND 1
        RestoreReg  <AX,CX,DI>
        MOV     ES:[DI.cd_OwnerFSC],FSC_BAD ; Owner = BAD

        MOV     AH,':'                  ; AL = drive letter, AH = ':'
        PUSH    AX                      ; Save drive text (d:)
        SUB     AL,"A"                  ; AL = 0-based drive#
        MOV     AH,0                    ; Zero out AH
        CALL    QueryDriveLetter
        LJC     icds3                   ; If the drive is not allocated,
                                        ;  don't initialized the CDS
        OR      BX,BX                   ; Is the drive a local drive?
        LJNZ    icds3

        pop     ax                      ; AL = upper-cased drive letter, AH=":"

;   Physical,local drive, we have info to fill in

        MOV     WORD PTR ES:[DI.cd_fsi].cdi_text,AX
        MOV     WORD PTR ES:[DI.cd_fsi.cdi_text+2],"\"  ; NUL terminate
        MOV     ES:[DI.cd_fsi].cdi_end,CDI_END_ROOT     ; end -> "\"
        OR      ES:[DI.cd_fsi].cdi_flags,CDI_ISROOT     ; mark as root

        SaveReg <AX,DS,SI>                  ; Find_DPB wrecks DS:SI
        SUB     AL,"A"                      ; AL = 0 based drive#
        call    Find_DPB
        JC      icds5                       ; CY = no DPB for drive

; Set the CD_INUSE flag- this is what is used to show that
; the drive is a local drive.  I moved it to after Find_DPB
; so we don't get into a situation where a partially invalid
; CDI is being passed around.  I know that the callers of this
; routine are not always checking the carry flag.  This error
; should never occur, but I would rather catch it here if it
; does.
        OR      ES:[DI.cd_flags],CD_INUSE

        MOV     WORD PTR ES:[DI.cd_devptr],SI
        MOV     WORD PTR ES:[DI.cd_devptr+2],DS
;
; Set up the size of the CDS just "created". This will be the size of the CDS
; structure without the cdi_text field plus 4 (<drive>:\<NUL>) for the text.
; This assumes that the cd_text field is the last field in the CDS structure.
;
        mov     TASKAREA:[CDSsize],(cd_fsi.cdi_text + 4)    ; Size of this CDS
        mov     si,ds:[si].dpb_hVPB
        mov     es:[di.cd_fsi].cdi_hVPB,si  ; Set up hVPB from the DPB

icds5:  RestoreReg  <SI,DS,AX>

icds10: RestoreReg <DS,BX>
        ret

icds3:
    ASSUME ES:NOTHING
        clc
        pop     ax                      ; AL = upper-cased drive letter, AH=":"
        jmp     SHORT icds10            ; don't fill in CDS for non-local drives

EndProc InitCDS


Break   <GetVisDrv - return visible drive>

;***    GetVisDrv - Map Non-Joined In-use Drives
;
;       ENTRY   (AL) = drive identifier (0=default)
;
;       EXIT    'NC' if OK
;                 (AL) = physical drive # (0 = A)
;                 ThisCDS is ptda_pCDS
;               'CY' if invalid drive/macro
;
;       USES    AL, Flags
;
Procedure   GetVisDrv,<near,faronly,far16>
    ASSUME  DS:NOTHING,ES:NOTHING,SS:TASKAREA
        PMONLY

        CALL    GetThisDrv              ; Get inuse drive
        jc      gvdx                    ; Return if error
        push    ds
        push    si
        mov     si,TASKAREA:CurrTCB             ; (SI) = current TCB     ;### PTR B720732
        lds     si,TASKAREA:[si].TCBThisCDS     ; (DS:SI) = CDS pointer  ;### PTR B720732
;### PTR B720732        LDS     SI,ThisCDS              ; DS:SI = ptda_pCDS
        TEST    [SI.cd_flags],CD_JOIN   ; clears carry
        jz      gvd1
        cmp     [SI.cd_handle],NUMDRIVE+1 ; c.d. or macro?
gvd1:   pop     si
        pop     ds
gvdx:   RETP
EndProc GetVisDrv


Break <GetThisDrv - map a drive designator (0=def, 1=A...)>

;***    GetThisDrv - map a driver designator
;***    GetThisDrvSrv - map bad drive designator using server code
;
;       GetThisDrv looks through a set of macros and returns
;       the current drive and macro pointer
;       GetThisDrvSrv simulates the call to GetThisDrv for a server.
;
;       ENTRY   AL has drive identifier (1=A, 0=default)
;               [TCBfSharing] = non-zero for a server call (GetThisDrv)
;
;       EXIT    'C' Set
;                   invalid drive/macro
;               'C' Clear
;                   AL has physical drive (0=A)
;                   [ThisCDS] points to macro
;                   [CDSsize] contains size of CDS pointed to by ThisCDS
;
;       USES    AL, ES, Flags
;
;       ALGORITHM
;       {
;           if (default drive)
;               get current drive
;           make drive number 0-based
;           if (server call | GetThisDrvSrv)    /* physical disk */
;               ThisCDS = ptda_pCDS
;               InitCDS
;               if (!InUse)                         /* not phys disk */
;                   return error
;               return
;           else                                /* logical disk */
;               GetCDSFromDrv
;               if ('C')
;                   return error
;               if (!InUse)
;                   return error
;               return
;       }
;
Procedure   GetThisDrvSrv,,LOCAL
    ASSUME  DS:NOTHING,ES:NOTHING,SS:TASKAREA

; GetThisDrvSrv relies on getting an error from InitCDS below.
; It is always called with AL = 255 which is an invalid drive.

        push    BX                      ; SaveReg <bx,ds,si>
        push    ds
        push    si
        mov     bh,-1                   ; BH = fSharing for server call
        mov     [ExtErr_Locus],ERRLOC_DISK
        jmp     short gtds20            ; go do the InitCDS processing

; This entry point is called with AL both valid and invalid.

Entry   GetThisDrv,HYBRID
        PMONLY

        SaveReg <BX>                    ; Save BX of entry
        push    bp
        mov     bp,CurrTCB
        mov     bh,[bp].TCBfSharing     ; BH = fSharing indicator
        pop     bp
;
; BH = non-zero if server call (physical drive)
;
        OR      AL,AL                   ; are we using default drive?
        JNZ     GetMap                  ; no, go get the CDS pointers

                                        ;72417 added ss:
        MOV     AL,ss:[PCurDrv]         ; get the current drive
        INC     AL                      ; Counteract next instruction
GetMap: DEC     AL                      ; 0 = A
        SaveReg <DS,SI>                 ; save world
        mov     [ExtErr_Locus],ERRLOC_DISK
        or      bh,bh                   ; Logical or Physical?
        JZ      Not_SRVC                ; Logical
        DosContext DS

; 99999
; This is not an exact replacement of the code that is commented out.
; In the original code, a RIPL drive causes a call to InitCDS which
; "should" come back with CD_INUSE as zero which results in carry
; being set and returned.  This version treats a RIPL drive like a
; remote drive (which as far as I can tell is accurate) and will call
; GetCDSFromDrv.  I don't think this should cause a problem.  If it does,
; we need to add the RIPL bit to the DriveLetter array and use it to
; change our action here.
        push    ax
        push    bx
        mov     ah,0
        CALL    QueryDriveLetter
        LJC     Not_SRVC_a              ; If the drive is not allocated,
                                        ;  don't initialized the CDS
        OR      BX,BX                   ; Is the drive a local drive?
        LJNZ    Not_SRVC_a              ; If not, don't InitCDS
        pop     bx
        pop     ax

;; @PAA04757A Begins
;; I hate doing all these pushes/pops of ax, but other solutions I have
;; thought of might have undesireable side effects.  Also, I've set a
;; breakpoints on this code and it VERY seldom executes, either on a
;; local or Ripled workstation.
;
;        push    ax                      ; Save drive number
;        inc     al                      ; Make drive number 1 based
;        mov     bl,NumIO
;        cmp     al,bl                   ; If drive in NumIO range, it is local
;        pop     ax                      ; Retrieve drive w/o affecting flags
;        jle     gtds20                  ;
;
;        push    ax                      ; Save drive number
;        inc     al                      ; Make drive number 1 based
;        call    IsRiplDrive             ;
;        pop     ax                      ; Retrieve drive w/o affecting flags
;        jc      gtds20                  ; It is a Ripl drive
;
;        ;Now we need to check to see if RiplDrive is within the NumIO range.
;        ;If so, the ripl drive is, for example, c: and there may be a local
;        ;disk d:.  The above checks are not enough.  Get the BootDrv. If this
;        ;is a Ripl, the BootDrv is the first Ripl drive.  If so, and the
;        ;Ripl drive is in the NumIO range, add the NumIO and NumIORipl.  Check
;        ;AL against the sum.
;
;        xor     bh, bh                   ; NumIO is in bl
;        cmp     bx,[SISData].SIS_BootDrv ; If NumIO (bx) is less than BootDrive,
;        jl      Not_SRVC                 ; al is not a local or Ripl drive
;
;        push    ax                      ; Save drive number
;        inc     al                      ; Make drive number 1 based
;        add     bl,NumIORipl            ; NumIO and NumIORipl overlap, add them
;        cmp     al,bl                   ; Is this a local or RPL drive?
;        pop     ax                      ; Retrieve drive w/o affecting flags
;        ja      Not_SRVC                ; no, get CDS as if not a server process
;; @PAA04757A Ends

    assume ds:nothing

gtds20: SaveReg <AX,ES,DI,BX>
        clc                             ; Clear carry if set by IsRiplDrive @PAA04757A
        mov     bx,TASKAREA:[CurrTCB]                      ;### PTR B720732
        push    TASKAREA:[bx].TCBpTmpCDS.Offst             ;### PTR B720732
        pop     TASKAREA:[bx].TCBThisCDS.Offst    ; CDS pointer  ;### PTR B720732
        mov     TASKAREA:[bx].TCBThisCDS.Segmt,ss ; CDS pointer  ;### PTR B720732
        ADD     AL,'A'
        CALL    InitCDS                 ;       InitCDS(c);
        TEST    ES:[DI.cd_flags],CD_INUSE       ; Clears carry
        RestoreReg  <BX,DI,ES,AX>
        JZ      GetBerr                 ; Not a physical drive.
        JMP     SHORT GetBye            ; carry clear

; This is not a server call - go find the CDS

Not_SRVC_a:
        pop     bx
        pop     ax
Not_SRVC:
        call    GetCDSFromDrv
        JC      GetBerr                 ; Unassigned CDS -> return error already set
        TEST    [SI.cd_flags],CD_INUSE  ; Clears Carry
        JNZ     GetBye                  ; carry clear
GetBerr:
        mov     [ExtErr_Locus],ERRLOC_UNK
        STC
GetBye: RestoreReg  <SI,DS>             ; restore world
        RestoreReg <BX>                 ; Restore BX of entry
        RETP
EndProc GetThisDrvSrv


Break <GetCDSFromDrv - convert a drive number to a CDS pointer>

;***    GetCDSFromDrv - Convert drive number to CDS pointer
;
;       Given a logical drive number, convert it to a CDS pointer,
;       returning an error if the drive number is greater than the
;       number of CDSs
;
;       ENTRY   AL is physical unit # A=0...
;
;       EXIT    Carry Set if Bad Drive
;               Carry Clear
;                   [ThisCDS] = DS:SI = ptda_pCDS
;                   [CDSsize] = size of CDS pointed to by [ThisCDS]
;
;       USES    DS,SI,Flags
;
Procedure   GetCDSFromDrv,hybrid
    ASSUME  DS:NOTHING,ES:NOTHING,SS:TASKAREA
        PMONLY


        DosContext      ds
        CMP     AL,[uchLastDrive]       ; is this a valid designator
        JBE     gcfd1                   ; yes, go get the macro
        STC                             ; signal error
        RET                             ; bye

gcfd1:  SaveReg <ES,DI,CX,BX,AX>
        push    si                      ;### PTR B720732
        mov     si,TASKAREA:CurrTCB               ; (SI) = current TCB     ;### PTR B720732
        mov     bx,TASKAREA:[si].TCBpTmpCDS.Offst       ; Initialize TCBpTmpCDS ;### PTR B720732
;### PTR B720732        MOV     BX,TASKAREA:[ptda_pCDS]
        mov     TASKAREA:[si].TCBThisCDS.Offst,bx ; CDS pointer  ;### PTR B720732
;### PTR B720732        MOV     [ThisCDS].Offst,BX
        mov     TASKAREA:[si].TCBThisCDS.Segmt,ss ; CDS pointer  ;### PTR B720732
;### PTR B720732        MOV     [ThisCDS].Segmt,SS      ; ThisCDS = ptda_pCDS
        pop     si                      ;### PTR B720732
        mov     BL,AL                   ; get drive number
        xor     BH,BH                   ; (BX) = drive number
        shl     BX,1                    ; turn into word pointer
        mov     AX,CDS_Handle[BX]       ; (AX) = handle of current directory
        or      AX,AX
        jz      gcfd2                   ; no established c.d., look for macro
        call    GetCDS
        jnc     gcfd3                   ; no error, continue
        jmp     short gcfd_ier          ; we MUST find this

gcfd2:  pop     AX                      ; (AX) = drive number
        push    AX
        xor     AH,AH
        inc     AL                      ; make drive 1-based

; GetCDS will only succeed if JOIN or SUBST is in effect for the drive because
; cds handles 1-26 are reserved for these operations.

        call    GetCDS                  ; look up a macro
        jc      gcfd5                   ; no match, make one up
        cmp     ProcType,PT_REALMODE    ; are we real mode box?
        je      gcfd3                   ; yes, continue below
        test    es:[dI.cd_flags],CD_ISNET       ; let net CDS go
        jnz     gcfd3

; don't let protected see JOINS or SUBSTs

        test    es:[dI.cd_flags],CD_JOIN + CD_LOCAL
        jnz     gcfd5
;
; ThisCDS set up to point to where ptda_pCDS points (set up by us earlier)
; CDSsize = size of CDS record (Set by RMP_Find which was called by GetCDS)
;
gcfd3:  SaveReg <ES,DI>
        RestoreReg      <SI,DS>         ; DS:SI -> CDS found
        mov     di,TASKAREA:CurrTCB             ; (di) = current TCB     ;### PTR B720732
        les     di,TASKAREA:[di].TCBThisCDS     ; (es:di) = CDS pointer  ;### PTR B720732
;### PTR B720732        LES     DI,[ThisCDS]            ; ES:DI = ptda_pCDS
        MOV     CX,TASKAREA:[CDSsize]
        SHR     CX,1                    ; convert to words, (plus carry if odd)
        REP MOVSW                       ; copy into TASKAREA
        jnc     gcfd4                   ; even # of bytes, done
        MOVSB                           ; move odd byte
gcfd4:  jmp     short gcfd6

gcfd5:  pop     AX
        push    AX
        add     AL,"A"                  ; map drive number to letter
        call    InitCDS                 ; construct a CDS
gcfd6:
        RestoreReg  <AX,BX,CX,DI,ES>
        mov     si,TASKAREA:CurrTCB             ; (SI) = current TCB     ;### PTR B720732
        lds     si,TASKAREA:[si].TCBThisCDS     ; (DS:SI) = CDS pointer  ;### PTR B720732
;### PTR B720732        LDS     SI,[ThisCDS]            ; DS:SI = ptda_pCDS
        CLC                             ; no error
        RET                             ; bye!

gcfd_ier:
        InternalError   <GetCDSFromDrv: lost CDS>
EndProc GetCDSFromDrv


BREAK   <GetCDS - get pointer to a CDS>

;***    GetCDS - get a pointer to a CDS
;
;       Given a handle to a CDS, return a pointer to the associated
;       CDS structure.
;
;       NOTE:   If the calling routine expects to context switch or
;               do memory allocation before using the pointer returned,
;               it should either lock the RMP segment or copy the
;               returned structure.
;
;       ENTRY   AX = handle to be searched for
;
;       EXIT    'NC' if found
;                   ES:DI = pointer to CDS found
;                   [CDSsize] = size of CDS
;               'CY' if not found
;
;       USES    BX, ES, DI
;
Procedure       GetCDS,hybrid
    ASSUME  DS:NOTHING,ES:NOTHING,SS:NOTHING
        PMONLY
        SaveReg <cx,dx,si,ds>
        TaskContext <ds>,si
        push    ax
        mov     si,sp                   ; (DS:SI) -> match pattern (AX on stack)
        xor     di,di                   ; (DI) = start at begining
        mov     cx,(size cd_handle)     ; (CX) = length of match pattern
        call    GetNextCDS
    ASSUME   ES:NOTHING
;
; NOTE:
;   We copy the value in CX into CDSsize. This is OK even if we had an error
;   since we never use the value in CDSsize unless we have obtained a valid
;   CDS.
;
        mov     TASKAREA:[CDSsize],cx   ; save size of record returned
        pop     ax                      ; (AX) = handle, clean it off the stack
        RestoreReg <ds,si,dx,cx>
        ret
EndProc GetCDS


BREAK   <NextCDS - Iterate through all CDS structures>

;***    NextCDS - Iterate through all CDS structures
;
;       This function allows one to loop through all the CDS
;       structures.  On entry ES:DI point to the last structure
;       matched.
;
;       WARNING Callers of this routine must not ProcBlock
;               between successive calls.  If another process
;               is scheduled, it might remove a record from
;               the RMP segment, possibly invalidating the
;               current record offset.
;
;       ENTRY   DI = 0 if start at beginning of RMP, else
;                  = points to record in RMP to start search from
;
;       EXIT    'NC'
;                    ES:DI   pointer to next structure
;               'CY' no structures remain
;
;       USES    ES:DI
;
Procedure       NextCDS,hybrid
    ASSUME  DS:NOTHING,ES:NOTHING,SS:TASKAREA
        PMONLY
        SaveReg <ax,bx,cx,dx,ds>
        xor     cx,cx                   ; match all records
        call    GetNextCDS
        RestoreReg <ds,dx,cx,bx,ax>
        ret
EndProc NextCDS


BREAK   <GetNextCDS - Iterate through all CDS structures

;***    GetNextCDS - Iterate through all CDS structures
;
;       ENTRY   CX = size of search handle
;               DS:SI = pointer to handle to search
;               DI = 0 if start at beginning of RMP, else
;                  = points to record in RMP to start search from
;
;       EXIT    'NC'
;                    ES:DI   pointer to next structure
;                    CX      size of CDS
;               'CY' no structures remain
;
;       USES    AX, BX, CX, DX, DI, ES, Flags
;
Procedure       GetNextCDS,near,local
    ASSUME  DS:NOTHING,ES:NOTHING,SS:TASKAREA
        PMONLY
gnc10:  mov     bx,cd_handle            ; (BX) = offset into record
        mov     dx,di
        DosContext es
        lea     di,DOSGROUP:CDSAddr     ; get memory handle
        mov     al,1                    ; skip current record
        push    cx
        CALLFAR Rmp_FindN               ; get next record
        mov     dx,cx                   ; (DX) = found record size
        pop     cx                      ; (CX) = search record size
    ASSUME   DS:NOTHING,ES:NOTHING
        jc      short gncx

;       Skip join/subst CDS for processes other than current.  For handles
;       1-26 if cd_pid == 0, the CDS is global (for network attaches).

        cmp     es:[di.cd_handle],NUMDRIVE
        ja      short gnc20             ; jump if not a join/subst CDS
        mov     ax,es:[di.cd_pid]       ; (AX) = CDS's PID
        or      ax,ax
        jz      short gnc20             ; jump if global 1-26 handle CDS
        cmp     ax,[Pid]
        jne     short gnc10             ; jump if not this process's CDS
gnc20:  mov     cx,dx                   ; (CX) = found record size
        clc
    ASSUME      DS:NOTHING,ES:NOTHING
gncx:   ret
EndProc GetNextCDS


Break   <DupCDS - duplicate a CDS reference>

;***    DupCDS - duplicate a reference to a CDS
;
;       Used in process creation
;       There is a race condition during process creation between
;       when the  CDS table is copied and DupCDS is called to inc
;       the reference count on the CDS, so it is not an internal
;       error to be called with an invalid CDS handle.
;
;       ENTRY   AX = CDS handle
;
;       EXIT    'C' clear if valid CDS
;                       CDS reference count is incremented
;               'C' set if invalid CDS
;
;       USES    BX, DI, ES
;
Procedure       DupCDS,<FARONLY,FAR16>

    ASSUME DS:NOTHING, ES:NOTHING, SS:TASKAREA

        PMONLY

        SaveReg <CX>
        call    GetCDS                  ; (es:di) -> CDS
        jc      dpcds9                  ; invalid CDS handle, ignore

        inc     ES:[DI.cd_refcnt]
        jz      dpcds_ier

dpcds9: RestoreReg      <CX>
        ret

dpcds_ier:
        InternalError   <CDS refcnt overflow>
EndProc DupCDS


Break   <AddGlobalCDS, AddCDS - Add a CDS to CDS RMP segment>

;***    AddGlobalCDS - Add a global CDS to CDS RMP segment
;***    AddCDS - Add a per-process CDS to CDS RMP segment
;
;       ENTRY   (AX) = CDS handle
;               (DS:SI) = CDS to add
;               (CX) = size of CDS
;
;       EXIT    if carry clear,
;                       CDS added successfully
;                       CDS reference count is set to one
;               if carry set,
;                       RMP_Add failed
;
;       USES    AX, BX, CX, DX, DI, ES

Procedure       AddGlobalCDS,hybrid

    ASSUME DS:NOTHING, ES:NOTHING, SS:TASKAREA

        xor     dx,dx                   ; (DX) = no pid
        jmp     short acd10

EndProc         AddGlobalCDS

Procedure       AddCDS,hybrid

    ASSUME DS:NOTHING, ES:NOTHING, SS:TASKAREA

        mov     dx,[Pid]                ; (AX) = pid of cds (for handles 1-26)
acd10:  mov     [si.cd_pid],dx
        mov     [si.cd_handle],ax
        mov     [si.cd_refcnt],1        ; set reference count
        DosContext es
        mov     di,OFFSET DOSGROUP:CDSAddr
        call    RlseBuffr               ; avoid swapper/buffer cache deadlock
        SaveReg <ds,si>
        CALLFAR Rmp_Add                 ; save the new CDS
        RestoreReg <si,ds>
        ret

EndProc         AddCDS


Break   <DelCDS - remove a CDS reference>

;***    DelCDS - remove a reference to a CDS
;
;       This routine decrements the reference count for the specified CDS
;       and frees the CDS when the reference count == 0.  FSD_ChDirFree is
;       called to allow the FSD to handle CDS freeing.
;
;       Used in process destruction, Chdir
;
;       ENTRY   AX = CDS handle
;
;       EXIT    CDS reference count is decremented
;               (if decremented count == 0, the cds is deallocated (free'd)
;
;       USES    AX, BX, DX, DI, ES
;
;    PSEUDOCODE {
;        GetCDS
;        Decrement reference count
;        if (reference count == 0) {
;            FSD_ChDirFree                      /* notify FSD */
;            Rmp_Rem                            /* remove CDS */
;        }
;    }

Procedure       DelCDS,hybrid

    ASSUME DS:NOTHING, ES:NOTHING, SS:TASKAREA

        PMONLY

        SaveReg <CX,SI,DS,BP>
        call    GetCDS                      ; (ES:DI) -> CDS
        jc      short delc10
        dec     ES:[DI.cd_refcnt]           ; dec. reference count
        jnz     short delc5                 ; not zero, just return

;   Tell the FSD that this CDS is going away

        SaveReg <ax>
delc3:  TaskContext DS                      ; (DS) = TASKAREA
        call    FSD_ChDirFree               ; go tell FSD
        jc      short delc9                 ; if error, internal error

        RestoreReg  <ax>                    ; AX = CDS handle
        call    GetCDS                      ; ES:DI -> CDS_RMP

        DosContext DS
        lea     bx,DOSGROUP:CDSAddr         ; DS:BX -> CDS (RMP) segment
        CALLFAR Rmp_Rem
    ASSUME      DS:NOTHING                  ; null out assume in DosContext

delc5:  RestoreReg      <BP,DS,SI,CX>
        ret

delc9:  InternalError <DelCDS: FSD return error while freeing CDS>
delc10: InternalError <DelCDS: lost CDS>

EndProc DelCDS


BREAK <TransFCB - convert an FCB into a path, doing substitution>

;***    TransFCB - create a pathname from an FCB
;
;       Copy an FCB from DS:DX into a reserved area doing all of the
;       gritty substitution.  The destination is a drive, colon, followed
;       by a packed name.
;
;       ENTRY   DS:DX       -> FCB
;               TASKAREA:DI -> destination pathname buffer
;       EXIT    'C'  - invalid path in final map
;                   or invalid chars
;               'NC' - FCB has been mapped into ES:DI
;                   Sattrib is set from possibly extended FCB
;                   ExtFCB set if extended FCB found
;
;       USES    ax,bx,cx,dx,di,si,ds,es,flags
;
        ASSUME  DS:NOTHING,ES:NOTHING,SS:TASKAREA
Procedure   TransFCB,<near,faronly,far16>
        PMONLY

        TaskContext ES                  ; get TASKAREA addressability

        SaveReg <ES,DI>                 ; save away final destination
                                        ;72417 moved from PTDA to TCB
        mov     di,CurrTCB
        mov     di,TASKAREA:[di].TCB_pSrchBuf ; point to FCB temp area
        xor     ax,ax
        mov     byte ptr es:[di],al     ; Empty buffer
        SetBP_CurrTCB
        MOV     [bp].TCBExtFCB,al       ; no extended FCB found
        MOV     [bp].TCBSattrib,ax      ; default search attributes
        UnSetBP_CurrTCB
                                        ;72417 end

        CALLFAR GetExtended             ; get FCB, extended or not
        JZ      tf01                    ; not an extended FCB, go get drive
        xor     ax,ax
        MOV     AL,[SI-1]               ; get attributes
                                        ;72417 moved from PTDA to TCB
        SetBP_CurrTCB
        MOV     [bp].TCBSattrib,AX      ; store search attributes
        MOV     [bp].TCBExtFCB,-1       ; signal extended FCB
        UnSetBP_CurrTCB
                                        ;72417 end
tf01:   LODSB                           ; get drive byte
        or      al,al                   ; default drive?
        jz      tf1                     ; yes, go get and use default drive

        call    GetThisDrv              ; set up all CDS info
        jnc     tf2                     ; no error occurred, go drop in drive letter
        jmp     BadPackPop4             ; invalid drive, go return the error

                                        ;72417 added ss: designator
tf1:    mov     al,ss:[LCurDrv]
tf2:    CALL    TextFromDrive           ; convert 0-based drive to text
                                        ; (ES:DI) -> past d: in dest buffer

;   Check filename chars for validity

        SaveReg <ES,SI>

        mov     cx, 8                   ; 8 chars in a filename
        call    ChkVal
        jz      tf3                     ; invalid char, go report it

;   Filename is valid.  Check extension for validity.

        mov     cx, 3
        call    ChkVal

tf3:    RestoreReg  <SI,ES>
        jz      BadPackPop4

; the following section of code is to enable differentiation between '*s' and
; '*s.'  if the name has an explicit dot and no extension, the dot was stored
; in the FCB name following the name by MakeFCB.  there was adequate room
; to store it there because there was no extension.  the last two bytes of
; the extension are the signature word, to reduce the chances that the user
; is passing in an illegal FCB that has a '.' in the extension, rather than
; one created by MakeFCB ($Parse_File_Name). if it is a legal FCB, we copy the
; name and the dot, null terminate it, and skip the call to PackName.

        mov     bx,di                   ; TASKAREA:BX -> past drive name in destination
        cmp     byte ptr [si+8],'.'     ; explicit '.'
        jnz     tf4
        cmp     word ptr [si+9],FCBDOTSIG ; signature word?
        jnz     BadPackPop4             ; no, go return error

        mov     cx,8
tf35:   lodsb                           ; copy the name
        cmp     al,' '                  ; end of name?
        jz      tf37                    ; yes, go store '.' and null terminate
        stosb
        loop    tf35

tf37:   mov     al,'.'                  ; store '.'
        stosb
        mov     byte ptr es:[di],0      ; null terminate
        jmp short tf5                   ; go call transpath

    ASSUME      ES:TASKAREA
tf4:    call    PackName                ; crunch the path
tf5:    RestoreReg  <DI,ES>             ; get original destination
        TaskContext DS                  ; get DS addressability
                                        ;72417 moved from PTDA to TCB
        mov     si,CurrTCB
        mov     si,TASKAREA:[si].TCB_pSrchBuf ; point at new pathname
                                        ;72417 end
        CMP     BYTE PTR [BX],0
        JZ      BadPack
        CALL    TransPathNoJoinIfSame   ; convert the path
        JC      BadPack
        RETP                            ; bye with transPath error code

BadPackPop4:
        add     sp,4
BadPack:
        MOV     AX,ERROR_PATH_NOT_FOUND
        STC
        RETP
EndProc TransFCB


;***    ChkVal - check string of chars for filename validity.
;
;       ChkVal scans a series of characters, advancing over DBCS chars and
;       making sure that the single byte characters are legal in FCB filenames.
;
;       NOTE: there's a duplicate copy of this procedure in chardd.asm.
;
;       ENTRY   (ds:si) point to chars to be scanned
;               (cx)    number of characters to scan
;
;       EXIT    zero set => name contains illegal chars
;               zero reset => name is valid
;                  dl = 0 -> only 8.3 characters
;                  dl = 1 -> contains newly-valid characters
;
;       USES    AL, BX, CX, DX, SI, ES
;
;
;       PSEUDO
;
;       dh = fFCB | fNewChar;
;       if (dbcs is inactive in current process)
;         for (thischar = characters in path) {
;           dh &= CharType[thischar]
;         }
;       else
;         for (thisbyte = bytes in path) {
;           if (thisbyte = DBCS lead byte)
;             ignore DBCS lead & trail bytes
;           else
;             dh &= CharType[thischar]
;         }
;       if (dh & fFCB == 0)     // invalid name
;         return ZR
;       if (dh & fNewChar == 0) // contains newly-valid char
;         dl = 1
;       else
;         dl = 0
;       return NZ

Procedure   ChkVal,HYBRID
    ASSUME  DS:NOTHING, ES:NOTHING
        LEA     BX,DOSGROUP:CharType
        DosContext ES,,noassume         ; es:bx -> CharType table

.errnz FATNAME
.errnz NONFATNAME - 1

; We're interested in two bits in the CharType table: fFCB and fNewChar
; A valid name must have fFCB set for all character.
; To make this run fast, we initialize register dh to fFCB+fNewChar and AND it
; with CharType bits of each path character. If the fFCB becomes zero at the
; end, then the name is invalid. If the fNewChar bit remains set at the end
; then there are no newly valid characters in the path.

        mov     dl,fFCB or fNewChar
        TestDBCSProcess                 ; need to handle DBCS?
        jz      chv30                   ; yes, do DCBS specific version

; non-DBCS version

chv10:  lodsb                           ; al = char to examine
        xlat    byte ptr es:[bx]        ; al = char type bits
        and     dl,al                   ; detect non fFCB, fNewChar
        loop    chv10                   ; repeat for all

chv15:  test    dl,fFCB                 ; valid name?
        jz      chv20                   ; no, exit with zero set

; If newly valid chars are in the path, then fNewChar in dl will become 0.
; To match exit condition, we invert the bit and shift it to bit 0.

        not     dl                      ; fNewChar bit set if new char found
        and     dl,fNewChar             ; mask off other bits
        shr     dl,5                    ; shift fNewChar to bit 0
.errnz  fNewChar - 20h                  ; make sure shift count is right
        inc     cx                      ; set NZ

chv20:  ret

; DBCS version

chv30:  lodsb                           ; al = char or DBCS lead byte
        TestDBCSLeadByte                ; DBCS lead byte?
        jnz     chv40                   ; no

        dec     cx                      ; adjust count for extra DBCS byte
        jz      chv20                   ; half-DBCS in last byte; report error
        inc     si                      ; skip DBCS trail byte
        jmp     short chv50

chv40:  xlat    byte ptr es:[bx]        ; al = CharType bits
        and     dl,al                   ; detect non fFCB, fNewChar

chv50:  loop    chv30                   ; repeat for all
        jmp     short chv15

EndProc ChkVal


CP_VALIDCDS     equ     1

;***    ConvertPathChars
;
;       ENTRY   ds:si -> ASCIZ string
;
;       EXIT    all / changed to \ in string
;
;       USES    ax,flags

Procedure ConvertPathChars

        SaveReg <si,di,es>

        mov     di,ds
        mov     es,di
        mov     di,si                   ; es:di -> string to convert

; BUGBUG philipl - problems with DBCS.INC and this file fix soon 10-Sep-1989
        TestDBCSProcess                 ; DBCS active?
        jz      cpc30                   ; yes, do DBCS copy


; do quick non-DBCS copy; convert / to \

cpc10:  lodsb                           ; al = character
        cmp     al,'/'                  ; forward slash?
        je      cpc20                   ; yes, go convert it
        stosb                           ; store character
        or      al,al                   ; end of string?
        jnz     cpc10                   ; loop if not end and not limit
        jmp     short cpcx              ; done, exit

cpc20:  mov     al,'\'                  ; convert into \
        stosb                           ; store \
        jmp     short cpc10             ; string too long error

; do DBCS copy

cpc30:  lodsb                           ; get next char. from ds:si into al
; BUGBUG philipl - problems with DBCS.INC and this file fix soon 10-Sep-1989
        TestDBCSLeadByte
        jnz     cpc40                   ; not DBCS lead byte; go copy
        jmp     short cpc40

; copy DBCS lead and trail byte. No need to check for path separator or string
; terminator

        stosb                           ; store DBCS lead byte
        lodsb                           ; get DBCS trail byte
        jmp     short cpc50

; not DBCS character. Convert / into \

cpc40:  cmp     al,'/'                  ; forward slash?
        jne     cpc50                   ; no, copy it verbatim
        mov     al,'\'                  ; convert forward slash to back slash

cpc50:  stosb                           ; put char. (al) into next pos at es:si
        or      al,al                   ; end of string?
        jnz     cpc30                   ; jmp if char was not NUL

cpcx:   RestoreReg <es,di,si>
        ret

EndProc ConvertPathChars

;***    TransPath
;***    TransPathNoJoinIfSame
;***    TransPathNoJoin
;***    TransPathNoPCName
;***    TransPathNoErrNMP
;
;   FUNCTION    General (exceptions noted above):
;
;       TransPath copies a path from DS:SI to TASKAREA:DI (from an ASCIIZ
;       string to a Well-Formed-Path (WFP) buffer.  It inserts the current
;       directory, replaces . and .. entries, leaves ThisCDS set up.r
;       WhatVolume is done (for local drives). ValidateCDS is done.
;
;       Returns a PathNameType value (which quickly tells which case is
;       involved).
;
;       Returns the appropriate FSC for the path. This can either be saved
;       for later use (open: for later handle-based routines) or used
;       directly (other name-based routines).
;
;       fSharing is ignored in TransPath.
;
;       No Joined or Subst'ed handling is done in PROTECT MODE.
;
;       TransPath         - REAL MODE ONLY.  Always returns Joined paths.
;
;       TransPathNoJoinIfSame - will reference the actual path if it exactly
;                           matches a Joined path.  REAL MODE ONLY.
;                           Useful when one wants the actual path, regardless
;                           if the exact path is joined to another drive.
;                           (See w_3xFindFirst, w_FindFirst, w_ChMod, w_Unlink,
;                           & w_Rename.)
;
;       TransPathNoJoin   - will never return the Joined path.
;                           REAL MODE ONLY.  Useful when the logical path is
;                           wanted.  (See w_ChDir & w_RmDir.)
;
;       TransPathNoPCName - will not call FSD_ProcessName. This is used by
;                           FSH_Canonicalize.
;
;       TransPathNoErrNMP - Usually a named pipe specified with relative path
;                           is an error. One exception is in w_rename where
;                           we allow an existing "\pipe" directory to be
;                           renamed to something else. Does nothing to join
;                           drives.
;
;       Other behavior:
;           Drive letters on device names and named pipes are ignored..
;           Device names:
;               Real mode:  ignores path names (truncate to 0 length).
;
;               Protect mode: no paths other than \DEV\ allowed.
;
;           UNC names have an empty CDS and are associated with the
;           UNC-related FSD (may be the redirector).
;
;           8.3 names are enforced at this level by FSD_ProcessName.
;
;           A CDS (dummy) is provided for UNC names, pseudo-character
;           devices, and named pipes.
;
;
;   ENTRY   (DS:SI)       -> Path (ASCIIZ string)
;           (TASKAREA:DI) -> Buffer to use
;
;   EXIT    'C'  - (AX) = error code
;           'NC' - [WFP_Start]    -> beginning of buffer
;                  [WFP_Path_End] -> end of path part of target
;                  [Curr_Dir_End] -> end of curdir in path
;                  [ThisCDS]       = ptda_pCDS (if CDS is needed)
;                  [TCBThishVPB]   = current hVPB
;                  [TCBThisFSC]   -> FSC
;
;                  DS = TASKAREA
;
;;;BUGBUG 18-Jan-1988 RajenS - Should be passed in a register
;;; The information passed in PathNameType should really be passed in a
;;; register. We can then get rid of the PTDA variable.
;;;
;               PathNameType =  (PNType_UNC,       /* UNC name    */
;                                PNType_LOCDEV,    /* local dev   */
;                                PNType_PSDEV,     /* psuedo-char dev */
;                                PNType_LOCFILE,   /* local file  */
;                                PNType_REMFILE    /* remote file */
;                                PNType_NEWFILE    /* non-8.3 name */
;                               )
;
;
;               if TransPathNoPCName
;                  if last component of path is non-8.3
;                     al = 0x40
;                  else
;                     al = 0
;
; PNType_NEWFILE applies only to the last component in the path.
;
;;;
;;; And the same goes for TCBThisFSC. (11-Mar-1988 MarkH)
;;;
;;;
;;; BUGBUG END
;
;   USES    AX, BX, CX, DX, DI, SI, DS, ES, flags

Procedure TransPath,<HYBRID, far16>
        mov     ax,FPATH_PCNAME or FPATH_ERRNMP or FPATH_JOIN
        jmp     short trpa10
EndProc TransPath


Procedure TransPathCnvPathChars,<HYBRID, far16>
    assume ds:nothing,es:nothing,ss:TASKAREA

        call    ConvertPathChars
        mov     ax,FPATH_PCNAME or FPATH_ERRNMP or FPATH_JOIN
trpa10: TaskContext <es>
                                      ;72417 moved from PTDA to TCB
        SetBP_CurrTCB
        mov     [bp].TCBWFP_Start,di  ; WFP_Start = offset to result buffer
        UnSetBP_CurrTCB
                                      ;72417 end
        call    TransPath2
        TaskContext <ds>
        RETP

Entry TransPathNoJoinIfSame,hybrid
        mov     ax,FPATH_PCNAME or FPATH_ERRNMP or FPATH_JOIN or FPATH_JOIN_N_EXACT
        jmp     short trpa10

Entry TransPathNoJoin
        mov     ax,FPATH_PCNAME or FPATH_ERRNMP
        jmp     short trpa10

Entry TransPathNoPCName,hybrid
        mov     ax,FPATH_ERRNMP
        jmp     short trpa10

Entry TransPathNoErrNMP
        mov     ax,FPATH_PCNAME
        jmp     short trpa10

EndProc TransPathCnvPathChars

;***    TransPath2 - parse a path and determine what it is
;
;       Transpath2 parses a path, determines the path type, and format the
;       path appropriate for that type:
;
;       UNC name:   canonicalize
;       named pipe: canonicalize
;       device:     insert \DEV\ and canonicalize
;       file:       insert current directory and canonicalize
;
;       e.g.:
;       input           type            output          comment
;       -----           ----            ------          -------
;       a:\\b\c\d       file            A:\B\C\D
;       \\server\name   UNC             \\SERVER\NAME
;       \\\\server\\one UNC             \\\SERVER\ONE
;       \pipe\a         named pipe      \PIPE\A
;       a:\pipe\a       error                           if NoNMPErr is reset
;       a:\pipe\a       file            A:\PIPE\A       if NoNMPErr is set
;       con             device          \DEV\CON
;       os2krnl         file            C:\OSKRNL       assumes C:\ is cur dir
;       (null)          error
;       b:              error
;
;       TransPath2 is a clean up of the old TransPath but not completely
;       done. Certain code fragments still depends on es being TASKAREA
;       (mark with bugbug).
;
;       ENTRY   ax     = flags
;                        FPATH_PCNAME - call FSD_ProcessName
;                        FPATH_ERRNMP - complain about relative named pipe
;               ds:si -> dbcsz path
;               es:di -> buffer to store formatted path
;                        (currently es must be TASKAREA)
;
;       EXIT    if 'NC' - success
;                  [WFP_Start]    -> beginning of buffer
;                  [WFP_Path_End] -> end of path part of target
;                  [Curr_Dir_End] -> end of curdir in path
;                  [ThisCDS]       = ptda_pCDS (if CDS is needed)
;                  [TCBThishVPB]   = current hVPB
;                  [TCBThisFSC]   -> FSC
;                  [cMeta]         = 0ffh if no meta
;
;                  following is used by FSH_CANONICALIZE
;
;                  if !FPATH_PCNAME
;                     if last component of path is non-8.3
;                        al = 0x40
;                     else
;                        al = 0
;
;               if 'CY' - error
;                 ax = error code
;
;       USES    ax,bx,cx,dx,si,di,ds,es,flags
;
;       PSEUDO CODE
;
;       initialize side effects (PTDA variables)
;       if (input path is UNC path)
;         process UNC path (transUNC)
;       else if (input path is null or is only "D:")
;         return ERROR_FILE_NOT_FOUND
;       else if (input path is a named pipe path)
;         process named pipe path (transNMP)
;       else
;         wfp = canonicalize (current directory + input path) [yes validateCDS]
;         if (wfp is a device path)
;           process device path (transdev)
;         else if (FPATH_ERRNMP and wfp is a named pipe path)
;           return error  /* relative named pipe path not allowed */
;         else
;           process file path (transfile)
;       if (path processing success)
;         if (FPATH_PCNAME)
;           call FSD_ProcessName
;         else
;           call CheckLastComponent

Procedure TransPath2,hybrid
    assume ds:nothing,es:nothing,ss:TASKAREA

LocalVar    tp_Flag,WORD
LocalVar    tp_lpPath,DWORD         ; dbcsz path to parse
LocalVar    tp_lpBuf,DWORD          ; -> result buffer
LocalVar    tp_MaxNumDots,WORD      ; Max. number of dots found in a component
LocalVar    tp_NumDotsLastComp,WORD ; Number of dots found in last component of path
LocalVar    tp_p3xPath,DWORD        ; -> 3xbox path with / \ conversion done
LocalVar    tp_pTempBuf,DWORD       ; Pointer to temp buffer
LocalVar    tp_8dot3,BYTE           ; non-8.3 path
LocalVar    tp_TCBNewFlags,BYTE     ; Used to hold TCBNewFlags value when UNC Named pipes are processed ;PTR B721523

        PMONLY
        SegAssert SS,es,"TransPath2"
        TimerOn TIM_FS_TRANSPATH
        EnterProc <ax, ds,si, es,di,0,0, 0,0>  ; Flag=ax, lpPath=ds:si, lpBuf=es:di
                                        ; MaxNumDots=0, NumDotsLastComp = 0
                                        ; tp_p3xPath = 0:0

        mov     bx, CurrTCB             ; Get current TCB                      ;PTR B721523
        mov     al, TASKAREA:[bx].TCBNewFlags                                  ;PTR B721523
        mov     tp_TCBNewFlags, al      ; Save original value of TCBNewFlags   ;PTR B721523
        mov     tp_8dot3,0              ; EnterProc can init words only

; For protected mode, all / are converted into \ by getusrbufz in sci.
; For 3xbox, they have to be converted in this procedure. First we see if we
; need to allocate a buffer to store the converted path.

        cmp     ProcType,PT_REALMODE    ; 3xbox path?
        jne     tp04                    ; no, don't allocate buffer

; 3xbox path. See if it contains any /. If not, we don't need to allocate
; buffer. We can do search for / without worrying about DBCS because / is not
; a valid DBCS trail byte
; ds:si -> path

        mov     ah,'/'                  ; ah = char to search for
        mov     cx,PMPATHLEN            ; max number of char to examine

tp00:   lodsb                           ; al = char to examine
        or      al,al                   ; end of string?
        jz      tp04                    ; yes, no / found; skip /\ conversion
        cmp     al,ah                   ; forward slash?
        loopne  tp00                    ; no, examine next one
        jne     tp04                    ; reached path limit; no / found

; found / in 3xbox path. We need to allocate a buffer to store the translated
; result.

        CALLFAR GetTempBuffer           ; es:di -> buffer
        mov     tp_p3xPath.Segmt,es     ; save pointer
        mov     tp_p3xPath.Offst,di

; copy 3xbox path to buffer and convert / into \. We don't need to worry about
; DBCS because / can't be a DBCS trail byte.

        lds     si,tp_lpPath            ; ds:si -> 3xbox path
        mov     tp_lpPath.Segmt,es      ; set source path to point to converted path
        mov     tp_lpPath.Offst,di
        mov     bx,"/\"                 ; bh = /,  bl = \
        mov     cx,PMPATHLEN            ; cx = max # of char to copy

tp01:   lodsb                           ; al = char to copy
        cmp     al,bh                   ; forward slash?
        jne     tp02                    ; no, go copy it

        mov     al,bl                   ; translate to back slash

tp02:   stosb                           ; save in temp buffer
        or      al,al                   ; end of string?
        loopnz  tp01                    ; repeat if not

tp04:   CALLFAR GetTempBuffer           ; ES:DI -> temp buffer
        mov     tp_pTempBuf.Segmt,ES
        mov     tp_pTempBuf.Offst,DI    ; Save the pointer

        mov     ax,-1
                                        ;72417 moved from PTDA to TCB
        SetBP_CurrTCB
        mov     [bp].TCBCurr_Dir_End,ax ; assume no current dir in path
        mov     si,TASKAREA:CurrTCB             ; (SI) = current TCB     ;### PTR B720732
        mov     TASKAREA:[si].TCBThisCDS.Offst,ax       ; CDS pointer            ;### PTR B720732
;### PTR B720732        mov     [ThisCDS].Offst,ax      ; no CDS yet
        mov     [bp].TCBcMeta,al        ; initialize meta count
        UnSetBP_CurrTCB
                                        ;72417 end

; perform tests on raw path

        lds     si,tp_lpPath            ; ds:si -> input path
        call    UNCTest                 ; UNC path?
        LJC     tp60                    ; error, go return
    assume ds:nothing
        jnz     tp10a                   ; yes, go process UNC
        jmp     tp10
tp10a:

; ds:si -> after D:

        cmp     byte ptr [si],0         ; null file?
        mov     ax,ERROR_FILE_NOT_FOUND ; assume yes
        stc
        LJE     tp60                    ; yes, go error

; CheckPipeDir is case-insensitive

        lds     si,tp_lpPath            ; ds:si -> raw path
        CALLFAR CheckPipeDir            ; absolute named pipe path?
        LJE     tp20                    ; yes, go process named pipe
;
; Check for device name in raw path.
; This will detect most of the devices and will avoid hitting the disk and
; causing harderror deadlocks within BVS which tries to open MOUSE$ when
; a screen group changes.
;
; For the DOS box, we strip out any <drive>: from the path before checking
; since we have to ignore it for compatibility reasons and CheckThisDevice
; does not deal with it.
;
        lds     si,tp_lpPath            ; ds:si -> raw path
        cmp     ProcType,PT_REALMODE    ; Is this DOS box?
        jne     tp05                    ; No. Don't eat <drive>:
        call    DriveFromText           ; Yes, eat <drive>:
tp05:   les     di,tp_pTempBuf          ; es:di -> scratch buffer
        CALLFAR CheckThisDevice         ; bh=device type, ds:si -> dev name
        LJNC    tp30                    ; device, go process Device     ;### PTR Changed from jnc
;
; Make path into absolute, canned (canonicalized) path then perform more tests
; canonicalize path.
;
        mov     ax,CP_VALIDCDS
        lds     si,tp_lpPath            ; ds:si -> source path
        les     di,tp_lpBuf             ; es:di -> result buffer
        mov     bx,tp_Flag              ; bx = JOIN flag bits
        call    canpath
        LJC     tp60                    ; error
        or      tp_8dot3,dl             ; remember if non-8.3 chars seen
        mov     tp_MaxNumDots,cx        ; Save max. number of dots per component
        mov     tp_NumDotsLastComp,ax   ; Save number of dots in last component
;
; Check for device in canonicalized path.
;
        lds     si,tp_lpBuf             ; ds:si -> path to examine
        call    DriveFromText           ; eat d:
        mov     ax,ds:[si+1]            ;
        and     ax,0dfdfh               ; make them uppercase
        cmp     ax,'ED'                 ;
        jne     tp08                    ; there will always be a \ at si
        mov     ax,ds:[si+3]            ; so we can check for possible device
        and     al,0dfh                 ; by looking for 'DEV\'
        cmp     ax,'\V'                 ;
        jne     tp08
        les     di,tp_pTempBuf          ; es:di -> scratch buffer
        CALLFAR CheckThisDevice         ; bh=device type, ds:si -> dev name
        jnc     tp30                    ; device, go process Device

; similar to the device path, we need to check for relative named pipe path
; again.

tp08:   test    tp_Flag,FPATH_ERRNMP    ; catch relative named pipe?
        jz      tp40                    ; no, ignore (treat as file)

; bugbug 12-jan-89 peterwu - CheckNotPipeDir should take pointer as parameter
; currently it assumes path is in taskarea:[wfp_start], which happens to be
; set up by TransPath.
; CheckNotPipeDir is case-insensitive

        call    CheckNotPipeDir
        LJC     tp60                    ; error ;### PTR changed from jc
        jmp     short tp40              ; must be file path; go process file

; process UNC path

tp10:   DosContext <ds>                 ; get access to UNC FSC
        mov     si,offset ds:UNCfscList
        cmp     ds:[si].UNCfsc_Active,FSC_BAD  ; Is this UNC holder being used?
        mov     ax,ERROR_PATH_NOT_FOUND ; assume No UNC FSDs are installed
        stc
        je      tpunc2a                  ; assumption correct; go error exit

;*** CP20 PTR B721523 ************************************************
; If NEWFILES is not set in TCB                                      *
; {                                                                  *
;        Check to see if UNC name has the form \\server\pipe\...     *
;        If UNC name references a named pipe set newfiles bit in TCB *
; }                                                                  *
;*********************************************************************
        lds     si,tp_lpPath            ; ds:si -> input path
        mov     bx, CurrTCB             ; Check to see if newfiles in TCB      ;PTR B721523
        test    TASKAREA:[bx].TCBNewFlags,TCBNEWFILES ; newfiles?              ;PTR B721523
        jnz     tpunc4                  ; If newfiles is set, no further checks;PTR B721523
tpunc1: lodsb                           ; load al with ds:si                   ;PTR B721523
        JNPathChar <tpunc2>             ; While al is a '\' advance            ;PTR B721523
        cmp     al,0                    ; (check for end of string 'nul')      ;gwd 76318
        jnz     tpunc1                  ; the pointer until a non '\' is seen  ;PTR B721523
        jmp     tpunc2a                 ; end of string go error exit          ;gwd 76318

tpunc2: lodsb                                                                  ;PTR B721523
        JPathChar <tpunc3>              ; Scan until a '\' is seen             ;PTR B721523
        cmp     al,0                    ; check for end of string 'nul'        ;gwd 76318
        jnz     tpunc2                  ; if null found then error return      ;PTR B721523

tpunc2a:
        mov     ax,ERROR_PATH_NOT_FOUND ; illegal UNC form, send error msg     ;gwd 76318
        stc                             ;                                      ;gwd 76318
        je      tp60                    ; go error exit                        ;gwd 76318

tpunc3: dec     si                      ; Undo effects of lodsb so ds:si       ;PTR B721523
        CALLFAR CheckPipeDir            ; is set up to search for \PIPE\       ;PTR B721523
        jnz     tpunc4                  ; If \PIPE\ not found                  ;PTR B721523
        or      TASKAREA:[bx].TCBNewFlags,TCBNEWFILES ; Set newfiles in TCB    ;PTR B721523
tpunc4:                                                                        ;PTR B721523
                                        ; otherwise, continue
        lds     si,tp_lpPath            ; ds:si -> input path
        les     di,tp_lpBuf             ; es:di -> result buffer
        call    transUNC                ; CX = max. number of dots per component
                                        ; BX = number of dots found in last component
        LJC     tp60
        or      tp_8dot3,dl             ; remember if non-8.3 chars seen
        jmp     short tp50              ; go call process name

; process named pipe

tp20:   lds     si,tp_lpPath            ; ds:si -> input path
        les     di,tp_lpBuf             ; es:di -> result buffer
        call    transNMP                ; CX = max. number of dots per component
                                        ; BX = number of dots found in last component
        jmp     short tp50              ; go call process name
;
; process device
; ds:si -> device name
; bh = device type
;
tp30:   les     di,tp_lpBuf             ; es:di -> result buffer
        call    transdev
        jmp     short tp45

tp40:   lds     si,tp_lpBuf             ; ds:si -> WFP
        mov     ax,tp_Flag
        call    transfile               ; process file path
tp45:   mov     cx,tp_MaxNumDots        ; CX = max. number of dots per component
        mov     bx,tp_NumDotsLastComp   ; BX = number of dots found in last component
;
; CX = maxium number of dots found in a single component of the path. This is
;      used by the FAT FS to error out if more than one dot was specified.
; BX = number of dots found in last component of path
;
tp50:   jc      tp60                    ; error, go exit

        test    tp_Flag,FPATH_PCNAME    ; call processname?
        jz      tp70                    ; no, go set up al = NewFiles
;
; bugbug 14-jan-89 peterwu - FSD_ProcessName should take long pointer to WFP
; it currently looks in TASKAREA:[WFP_Start] which happens to work since
; all (old) callers of TransPath passes in a buffer in the TASKAREA.
;
                                        ;72417 moved from PTDA to TCB
        SetBP_CurrTCB
        mov     al,[bp].TCBPathNameType ; al = path name type
        UnSetBP_CurrTCB
                                        ;72417 end
        mov     dl,tp_8dot3             ; whether non-8.3 chars seen
        call    FSD_ProcessName

tp60:   pushf
        test    tp_p3xPath.Segmt,-1     ; need to free 3xPath buffer?
        jz      tp65                    ; no

        les     di,tp_p3xPath           ; es:di -> 3xPath buffer
        CALLFAR FreeTempBuffer          ; free it

tp65:   les     di,tp_pTempBuf
        CALLFAR FreeTempBuffer          ; Free the temp buffer
        popf                            ; Restore any error indications
        LeaveProc
        TimerOff TIM_FS_TRANSPATH
        ret

tp70:   call    CheckLastComponent      ; set PNType_NEWFILE
        jc      tp80                    ; error -> non-8.3 name
                                        ;72417 moved from PTDA to TCB
        SetBP_CurrTCB
        mov     al,[bp].TCBPathNameType
        UnSetBP_CurrTCB
                                        ;72417 end
        and     al,PNType_NEWFILE       ; al = 0 or PNType_NewFile
        jmp short tp60                  ; go return (no carry)
tp80:
IFDEF FSSTRICT
        mov     bx,CurrTCB
        test    TASKAREA:[bx].TCBNewFlags,TCBNEWFILES ; newfiles?
        jnz     tpie                    ;   internal error
ENDIF
        mov     al,PNType_NEWFILE       ; al = PNType_NewFile
        jmp short tp60                  ; go return (no carry)

IFDEF FSSTRICT
tpie:   InternalError <TransPath2 - newfiles and bad filename>
ENDIF


EndProc TransPath2


;**     transfile - set file-related side effects
;
;       ENTRY   [ThisCDS] set up
;               ds:si -> WFP path
;               ax = flags (FPATH_JOIN, FPATH_JOIN_N_EXACT meaningful)
;
;       EXIT    if 'NC' - success
;                 [PathNameType] has path type
;                 [TCBThisFSC] has fsc
;                 [Curr_Dir_End] set up if cur dir is a prefix of path
;               if 'CY' - error
;                 ax = ERROR_PATH_NOT_FOUND
;
;       USES    ax,bx,cx,dx,si,di,ds,es,flags

Procedure transfile,near,local
    assume ds:nothing,es:nothing,ss:TASKAREA

LocalVar    tf_Flag, WORD

        TimerOn TIM_FS_TRANSFILE
        EnterProc <ax>                  ; set up tf_Flag

; set up Curr_Dir_End

        mov     di,TASKAREA:CurrTCB             ; (di) = current TCB     ;### PTR B720732
        les     di,TASKAREA:[di].TCBThisCDS     ; (es:di) = CDS pointer  ;### PTR B720732
;### PTR B720732        les     di,[ThisCDS]            ; es:di -> cds
        lea     di,[di].cd_fsi.cdi_text ; es:di -> cur dir text
        call    PathPref                ; is cur dir a prefix of WFP?
        jnz     trf5                    ; no, no curr_dir_end
        mov     al,es:[di-1]            ; al = last char
        JPathChar <trf5>                ; jump if path char
        cmp     byte ptr [si],0         ; end at null?
        jz      trf5                    ; yes, no curr_dir_end
        inc     si                      ; point to after current path char
                                        ;72417 moved from PTDA to TCB
        SetBP_CurrTCB
        mov     [bp].TCBCurr_Dir_End,si ; set Curr_Dir_End
        UnSetBP_CurrTCB
                                        ;72417 end

trf5:   cmp     ProcType,PT_REALMODE
        clc                             ; In case we just need to return.
        jne     trf10                   ; All done. ValidateCDS set everything for us

                                        ;72417 moved from PTDA to TCB
        mov     si,CurrTCB
        mov     si,TASKAREA:[si].TCBWFP_Start ; ds:si -> WFP
                                        ;72417 end
        mov     bx,tf_Flag              ; BX indicates what to do with JOIN
        call    Join                    ; do join
        mov     ax,ERROR_PATH_NOT_FOUND ; assume error
        jc      trf10

        mov     di,TASKAREA:CurrTCB             ; (di) = current TCB     ;### PTR B720732
        les     di,TASKAREA:[di].TCBThisCDS     ; (es:di) = CDS pointer  ;### PTR B720732
;### PTR B720732        les     di,[ThisCDS]            ; es:di -> cds
        SaveReg <bp>
        call    FSCFromCDS              ; es:di -> fsc; bl = PathNameType
        RestoreReg <bp>
        mov     ax,ERROR_PATH_NOT_FOUND
        jc      trf10                   ; error

                                        ;72417 moved from PTDA to TCB
        SetBP_CurrTCB
        mov     [bp].TCBPathNameType,bl
        UnSetBP_CurrTCB
                                        ;72417 end
        mov     bx,CurrTCB
        mov     TASKAREA:[bx].TCBThisFSC.Offst,di
        mov     TASKAREA:[bx].TCBThisFSC.Segmt,es

trf10:  LeaveProc
        TimerOff TIM_FS_TRANSFILE
        ret
EndProc transfile


;**     transdev - format path for device
;
; if a pseudochar device was passed in, WFP_Start contains "d:\DEV\psdevname".
; we need to transform this (in place) to be "\DEV\psdevname".  this is easy
; because we're rippling backwards.
;
; if an installed device was passed in, DS:SI points to the device header.
; we need to put "\DEV\" in WFP_Start then copy in the name.  DO NOT BLOCK
; before copying the name because device headers are in an RMP segment.
;
; the device name has already been canonicalized.
;
;       ENTRY   bh = device type (see DEVTYPE_ equ)
;               ds:si -> device name
;               es:di -> result buffer  (this buffer must not overlap ds:si)
;
;       EXIT    if 'NC' - success
;                 [PathNameType] has device type (local or pseudo)
;                 [TCBThisFSC] has fsc
;
;       USES    ax,bx,cx,dx,si,di,ds,es,flags
;
Procedure transdev,near,local
    assume ds:nothing,es:nothing,ss:TASKAREA

        TimerOn TIM_FS_TRANSDEV
        SaveReg <bp>
        mov     al,PNType_LOCDEV        ; assume local device
        test    bh,DEVTYPE_PSEUDO       ; is it pseudo-char device?
        jz      td5                     ; no, local
        mov     al,PNType_PSDEV         ; pseudo char dev

                                        ;72417 moved from PTDA to TCB
td5:    SetBP_CurrTCB
        mov     TASKAREA:[bp].TCBPathNameType,al
        UnSetBP_CurrTCB
                                        ;72417 end

; Now that we have noted that we have a device, we put it into a
; canonicalized form that will be used to set up an entry in the
; share set i.e. \DEV\<devname> (NOTE that there is no drive specifier
; in the canonicalized form for devices).
;
; Since we got here, CheckThisDevice found a device with the name
; starting at DS:SI.  Further, since it's a legal device name, it
; does not contain any path characters.  The device name can be quite
; long (up to 252 bytes long) - NOTE that this is different from earlier
; versions of the system. We need this because the length of a device
; name has been extended.
;
;;;BUGBUG 29-Mar-1988 MarkH - The following code could use DevPrefix in HighData
;;;

;;        test    bh,DEVTYPE_PSEUDO       ; is it pseudo-char device?
;;        jnz     td20                    ; yes, go copy name

        mov     AL,'\'
        stosb                           ; First '\'
        mov     AX,'E' SHL 8 + 'D'
        stosw                           ; 'DE'
        mov     AX,'\' SHL 8 + 'V'
        stosw                           ; 'V\'
;
; ES:DI -> target buffer space to copy devname into (after "\DEV\".)
; DS:SI -> devname
; This is DBCS correct because NULL is not a valid trailing byte.
;

td20:   lodsb
        stosb
        or      al,al                   ; end of string?
        jz      td30                    ; yes, go continue processing
        jmp short td20

                                        ;72417 moved from PTDA to TCB
td30:   SetBP_CurrTCB
        test    TASKAREA:[bp].TCBPathNameType,PNType_LOCDEV   ; check for local device
        UnSetBP_CurrTCB
                                        ;72417 end
        jz      td60                    ; not local device, must be pseudo dev

        mov     bp,CurrTCB
        mov     DI,GSEL GDT_FSC
        mov     [bp].TCBThisFSC.Segmt,di
        mov     [bp].TCBThisFSC.Offst,FSC_LOCAL ; TCBThisFSC -> FSC (local)
        jmp     short td70

;;;BUGBUG 16-Mar-1988 MarkH - TransPath - Where do we get the FSC (pseudo char)
;;; Right now, it gets the FSC from [DevPt], but CheckThisDevice's header does
;;; not indicate that it is returned (see IsThisADevice).  Maybe it could be
;;; returned in registers.

;   We need a CDS, but we don't care about a specific drive or CurDir.
;   GetThisDrvSrv does this for us.

td60:   mov     al,255                  ; invalid drive number
        call    GetThisDrvSrv           ; set ThisCDS and init ptda_pCDS
        SaveReg <es,di>
        mov     di,TASKAREA:CurrTCB             ; (di) = current TCB     ;### PTR B720732
        les     di,TASKAREA:[di].TCBThisCDS     ; (es:di) = CDS pointer  ;### PTR B720732
;### PTR B720732        les     di,ThisCDS              ; es:di -> cds
        or      es:[di].cd_flags,CD_ISPSEUDOCHAR    ; set up as PC device
        RestoreReg  <di,es>             ; ES:DI -> WFP

                                        ;72417 moved from PTDA to TCB
        mov     bp,CurrTCB
        mov     di,TASKAREA:[bp].TCBDevPt.Segmt
        mov     [bp].TCBThisFSC.Segmt,di
        mov     di,TASKAREA:[bp].TCBDevPt.Offst
                                        ;72417 end
        mov     [bp].TCBThisFSC.Offst,di        ; TCBThisFSC -> FSC

;
; BP -> CurrTCB
;
td70:   mov     TASKAREA:[bp].TCBThishVPB,NULLVPB   ; Zero out TCBThishVPB
        RestoreReg <bp>
        TimerOff TIM_FS_TRANSDEV
        ret
EndProc transdev



;**     canpath - insert current dir and canonicalize a path
;
;       Procedures an absolute, canned path. The flag CP_VALIDCDS will validate
;       the CDS (in case disk has changed) but will also cause disk activity.
;       This may not be desirable in some case (e.g. when testing for a device
;       name, it is bad manner to cause the floppy to spin).
;
;       ENTRY   ax = flag
;                    CP_VALIDCDS  - validate cds; may cause disk activity
;               bx = JOIN flags (this is a bit field) from TransPath2
;                        FPATH_JOIN  - do join (subject to FPATH_JOIN_N_EXACT)
;                        FPATH_JOIN_N_EXACT - join if not exact match
;               ds:si -> path
;               es:di -> result buffer (at least MAXPATHLEN long)
;
;       EXIT    if 'NC' - success
;                 result buffer has an absolute, canned path
;                 [cMeta] set up
;                 DL = 1 -> non-8.3 characters
;                    = 0 -> 8.3 characters only
;                 CX = Maximum number of DOTs found in a single component in
;                      the path. This is used to error out for FAT FS.
;                 AX = Number of dots found in last component of path This
;                      is for use with NO NEWFILES and IFS in FSD_ProcessName.
;               if 'CY'
;                 ax = error code
;
;       USES    ax,bx,cx,dx,si,di,ds,es,flags

Procedure canpath,hybrid
    assume ds:nothing,es:nothing,ss:TASKAREA

LocalVar cp_Flag,WORD
LocalVar cp_JoinFlag,WORD       ; Join flag from TransPath2
LocalVar cp_lpBuf,DWORD         ; -> result buffer
LocalVar cp_lpPath,DWORD        ; -> beginning of path (drive: skipped)

        TimerOn TIM_FS_CANPATH
        EnterProc <ax, bx, es, di>      ; Flag = ax, JoinFlag = bx, lpBuf = es:di
        call    DriveFromText           ; ax = 1 based drive #; si advanced
        mov     cp_lpPath.Offst,si      ; save pointer to path
        mov     cp_lpPath.Segmt,ds
        call    GetVisDrv               ; get CDS
        mov     ax,ERROR_INVALID_DRIVE  ; assume error
        jc      cp60                    ; bad drive, error exit

; ThisCDS -> CDS. Validate CDS if requested

        cmp     cp_Flag,CP_VALIDCDS     ; need to validate CDS?
        jne     cp10                    ; no, skip validate CDS

; bugbug 13-jan-89 peterwu - assume es is TASKAREA
; ValidateCDS needs a temp buffer in the TASKAREA. We could make a LocalVar
; buffer of size MAXPATHLEN, but that risk overflowing the stack. For now,
; we assume the result buf is in the taskarea, so we just pass the result
; buf to ValidateCDS as the temp buffer.

        SegAssert SS,es,<canpath>
        call    ValidateCDS             ; probe disk
    assume ds:nothing,es:nothing
        jc      cp60                    ; error exit

; copy current directory string from CDS into result buffer

cp10:   mov     si,TASKAREA:CurrTCB             ; (SI) = current TCB     ;### PTR B720732
        lds     si,TASKAREA:[si].TCBThisCDS     ; (DS:SI) = CDS pointer  ;### PTR B720732
;### PTR B720732        cp10:   lds     si,[ThisCDS]            ; ds:si -> cds
        mov     bx,[si].cd_fsi.cdi_end
        lea     si,[si].cd_fsi.cdi_text ; ds:si -> cur dir string
        les     di,cp_lpBuf             ; es:di -> result buffer
        add     bx,di           ; es:bx -> backup limit e.g. at first \ in c:\
        call    FStrCpy                 ; copy cur dir to result buf
        dec     di                      ; es:di -> nul

; make sure end of cur dir is a '\'
; es:bx -> backup limit

        mov     al,'\'
        jc      cp20                    ; last char is a DBCS, append '\'
        cmp     es:[di-1],al            ; last char is '\' ?
        je      cp30                    ; yes, don't append '\'

cp20:   stosb                           ; append '\'

; append path to or overwrite cur dir
; es:bx -> backup limit
; es:di -> after last \ in result buf

cp30:   lds     si,cp_lpPath            ; ds:si -> path (no drive)

        mov     al,[si]
        JNPathChar <cp50>               ; end of string or no path sep

; path begins with \, therefore we don't need cur dir. So can the path and
; put the result in result buf overwriting cur dir.
; ds:si -> source path (no drive)
; es:bx -> backup limit (first \ in D:\ )

        mov     di,bx                   ; es:di -> \ in D:\

; canonicalize the path into result buffer
; es:di -> somewhere in result buf (where we should copy the canned path)
; es:bx -> backup limit
; ds:si -> first non \ in source path

cp50:   mov     ax,cp_lpBuf.Offst       ; es:ax -> start of result buf
        add     ax,MAXPATHLEN           ; es:ax -> end of result buf
        SaveReg <bp>
        mov     bp,ax                   ; es:bp -> end of result buf
        call    Canonicalize
        RestoreReg <bp>

cp60:   LeaveProc
        TimerOff TIM_FS_CANPATH
        ret

EndProc canpath


;**     transNMP - format named pipe path
;
;       this routine uppercases the name.
;
;       ENTRY   ds:si -> path to format
;               es:di -> result buffer (at least MAXPATHLEN long)
;
;       EXIT    if 'NC' - succes
;                 result buffer has well formed UNC path
;                 side effects, etc.
;                 DL = 1 -> non-8.3 characters
;                    = 0 -> 8.3 characters only
;                 CX = Maximum number of DOTs found in a single component in
;                      the path. This is used to error out for FAT FS.
;                 BX = Number of dots found in last component of path This
;                      is for use with NO NEWFILES and IFS in FSD_ProcessName.
;               if 'CY' - error (bad path; e.g. too many .)
;                 ax = error code
;
;       USES    ax,bx,cx,dx,si,di,ds,es,flags

Procedure transNMP,near,local
    assume ds:nothing,es:nothing,ss:TASKAREA

        SaveReg <bp>
        lea     bp,[di+MAXPATHLEN]      ; bp -> after last byte of result buf
        lea     bx,[di+PIPEDIRLEN-1]    ; bx = backup limit
        call    Canonicalize            ; copy path to result buffer (CX = max number of dots)
        jc      tn10
        mov     bx,ax                   ; BX = number of dots in last component

        TaskContext <ds>
                                        ;72417 moved from PTDA to TCB
        mov     si,CurrTCB
        mov     si,TASKAREA:[si].TCBWFP_Start
        mov     di,si                   ; es:di = ds:si -> pipe name
        call    StrCpy                  ; uppercase name

        mov     bp,[CurrTCB]
        mov     [bp].TCBPathNameType,PNType_NMPIPE
                                        ;72417 end
        mov     [bp].TCBThisFSC.Segmt,0
        mov     [bp].TCBThisFSC.Offst,FSC_LOCAL ; local fsc
        mov     TASKAREA:[bp].TCBThishVPB,NULLVPB   ; Zero out TCBThishVPB

tn10:   RestoreReg <bp>
        ret

EndProc transNMP


;**     UNCtest - test for UNC path
;
;       ENTRY   ds:si -> raw path to check
;
;       EXIT    ds:si -> after "D:"
;               if 'NC' - path is ok so far
;                  'ZR' - is UNC path
;                  'NZ' - not UNC path
;               if 'CY' - path is invalid
;                  AX = error code
;
;       USES    ax,bx,cx,dx,si,di,ds,es,flags
;
;       If a drive is specified, we don't take the name as an UNC name
;       even if there are two or more \'s. We return an error.  this is
;       to support the IBM C Compiler, which expects d:\\foo to return an
;       error.
;
Procedure UNCTest,near,local
    assume ds:nothing,es:nothing,ss:nothing

; ds:si -> path

        call    DriveFromText           ; advance ds:si pass "D:" if exist
        cmp     word ptr [si],'\\'      ; '\\'?
        jnz     unt00
        or      al,al                   ; AL = 0 if no drive present
        jnz     unt20                   ; drive present, go return error
unt00:  clc                             ; ZR
unt10:  ret

unt20:  stc
        mov     ax,ERROR_PATH_NOT_FOUND
        jmp short unt10

EndProc UNCTest


;**     transUNC - format an UNC path
;
;       ENTRY   ds:si -> UNC path to format
;               es:di -> result buffer (must be at least MAXPATHLEN long)
;
;       EXIT    if 'NC' - succes
;                 result buffer has well formed UNC path
;                 side effects, etc.
;                 DL = 1 -> non-8.3 characters
;                    = 0 -> 8.3 characters only
;                 CX = Maximum number of DOTs found in a single component in
;                      the path. This is used to error out for FAT FS.
;                 BX = Number of dots found in last component of path This
;                      is for use with NO NEWFILES and IFS in FSD_ProcessName.
;               if 'CY' - error (bad path; e.g. too many .)
;                 ax = error code
;
;       USES    ax,bx,cx,dx,si,di,ds,es,flags
;
; NOTES of UNC naming conventions:
;
;   Two or three leading slashes are OK, and unmodified.
;
;   Four or more leading slashes are compressed to three
;    leading slashes.
;
;   Any number of slashes as a separator, in a local or UNC
;    path, are compressed to a single slash.
;
; The \\ is a short hand for the 'current name space' whereas
; three slashes are the root of the world-wide name space.


Procedure transUNC,near,local
    assume ds:nothing,es:nothing,ss:TASKAREA

        SaveReg <bp>                    ; preserve caller's bp
        lea     bp,[di+MAXPATHLEN]      ; bp = past last byte of result buffer

        mov     dx,1                    ; this is a UNC name

        SaveReg <es,di>
        call    UNC_Canonicalize        ; copy unc path; sets cMeta
        RestoreReg <di,es>              ; no need to setup backup limit; will
                                        ; be taken care of by canonicalize
        jc      tu20

        SaveReg <ax,cx,dx>              ; Save counts returned from Canonicalize
                                        ;72417 moved from PTDA to TCB
        SetBP_CurrTCB
        mov     TASKAREA:[bp].TCBPathNameType,PNType_UNC
        UnSetBP_CurrTCB
                                        ;72417 end

;   We need a CDS, but don't care about any physical drive or CurDir.
;   GetThisDrvSrv does this for us.

        mov     al,255                  ; invalid drive number

        SaveReg <es,di>
        call    GetThisDrvSrv           ; set up dummy CDS
        RestoreReg <di,es>              ; ES:DI = result_buffer(wfp)

        mov     si,TASKAREA:CurrTCB             ; (SI) = current TCB     ;### PTR B720732
        lds     si,TASKAREA:[si].TCBThisCDS     ; (DS:SI) = CDS pointer  ;### PTR B720732
;### PTR B720732        lds     si,ThisCDS              ; DS:SI = ptda_pCDS
    assume ds:nothing
        or      ds:[si].cd_flags,CD_ISUNC   ; set UNC flag

        DosContext <ds>
        mov     si,offset ds:UNCfscList
        cmp     ds:[si].UNCfsc_2.UNCfsc_Active,FSC_BAD ; more than 1 FSD??
        je      tu10

; DS:SI = pointer to the beginning of the UNCfsclist table in DOSData segment
; ES:DI = pointer to the well-formed UNC path to be passed to FSDs

        call    GetUncFsdFromPath
        jc      tu15                    ; No FSD accepted the path, so don't
                                        ; update the TCB
                                        ; Otherwise, DS:SI = entry in
                                        ; UNCfsclist that accepted path

tu10:   mov     bp,CurrTCB
        mov     dx,ds:[si].UNCfsc_FSCptr.Offst
        mov     TASKAREA:[bp].TCBThisFSC.Offst,dx
        mov     dx,ds:[si].UNCfsc_FSCptr.Segmt
        mov     TASKAREA:[bp].TCBThisFSC.Segmt,dx

        mov     dx,ds:[si].UNCfsc_hVPB          ; Get hVPB handle
        mov     TASKAREA:[bp].TCBThishVPB,dx    ; Set hVPB for this TCB

        clc                             ; clear carry flag to indicate no error

tu15:   RestoreReg <dx,cx,bx>           ; CX = max number of dots in a component
                                        ; BX = number of dots in last component
                                        ; DL = whether name contains non-8.3 chars
tu20:   RestoreReg <bp>                 ; restore bp
        ret

EndProc transUNC


;**     GetUncFsdFromPath -  Call UNC FSDs to see who will "own" our UNC
;                                                      well-formed path
;       ENTRY   ds:si -> pointer to UNCfsclist
;               es:di -> result buffer (must be at least MAXPATHLEN long)
;
;       EXIT    if 'NC' - succes
;                 ds:si -> is a pointer to the UNCfsclist entry that accepted
;                    the UNC well-formed path
;               if 'CY' - error (bad path; e.g. too many .)
;                 ax = error code
;
;       USES    cx,ds,si,es,di,flags
;
;*** NOTE *** This code assumes that this is a 2-pass operation with the
; FSD's UNCVERIFYNAME entry point.  Defines in FSD.H have PASS_1 = 0 and
; PASS_2 = 1; If this changes then this code must change with it.
;*** NOTE ***

Procedure GetUncFsdFromPath,near,local
    assume ds:nothing,es:nothing,ss:TASKAREA

LocalVar gffp_UNClist,DWORD         ; -> pointer to UNCfsclist
LocalVar gffp_UNCpath,DWORD         ; -> UNC well-formed path

        EnterProc <ds,si,es,di>

        xor     cx,cx                         ; set loop for pass 1
        jmp short gffp20                      ; jump over increment pass flag
                                              ; function
gffp10: inc     cx                            ; increment pass flag for pass 2
        cmp     cx,2                          ; Have we done the pass 2 calls?
        jb      gffp15                        ; no they do them
        mov     ax,ERROR_PATH_NOT_FOUND       ; Otherwise all the pass 2 calls
                                              ; failed, so load ax with error
        stc                                   ; code, set carry bit, and leave
        jmp short gffp40                      ; the routine

gffp15: lds     si,gffp_UNClist               ; Start Pass 2 calls from first
                                              ; UNC FSC on the uncfsclist

gffp20: cmp     ds:[si].UNCfsc_Active,FSC_BAD ; check current entry for a
                                              ; loaded UNC FSC
        jne     gffp30                        ; If we have a winner, jump to
                                              ; call its verifyuncname entry
        add     si,size uncfscentrstruc       ; else get offset to next entry
        cmp     si,offset ds:UNCfscLast       ; Past last entry??
        jbe     gffp20                        ; No, jump to top of the entry
                                              ; transversal loop
        jmp short gffp10                      ; Yes, jump to the top of the
                                              ; pass loop

gffp30: SaveReg <cx,ds,si>                    ; save Passflag and entry pointer
        push    cx                            ; push Passflag word
        mov     ax,gffp_UNCpath.Segmt         ; push
        push    ax                            ;     pointer  to
        mov     ax,gffp_UNCpath.Offst         ;      well-formed
        push    ax                            ;             UNC path
        les     di,ds:[si].UNCfsc_FSCptr      ; load current FSC ptr into es:di
        call    es:[di].FS_VERIFYUNCNAME      ; call verify name entry for path
        RestoreReg <si,ds,cx>
        or      ax,ax                         ; Does the FSD claim this path?
        jz      gffp40                        ; Yes, lets leave the routine
                                              ; with ds:si = entry who accepted
                                              ; the path
        add     si,size uncfscentrstruc       ; otherwise get offset of next
                                              ; entry
        cmp     si,offset ds:UNCfscLast       ; are we past the last entry??
        jbe     gffp20                        ; No, jump to top of the entry
                                              ; entry transversal loop
        jmp short gffp10                      ; Yes, jump to the top of the
                                              ; Pass loop
gffp40: LeaveProc                             ; return to caller
        ret

EndProc GetUncFsdFromPath


BREAK <Canonicalize - copy a path and remove . and .. entries>

;***    Canonicalize - copy path removing . and .. entries.
;
;       ENTRY   DS:SI - point to ASCIZ string path
;               ES:DI - point to buffer
;               BX - backup limit (offset from ES) points to slash
;                       (can be dummy in case of unc ; we find this ourselves
;                               as we go on, in case of UNC )
;               BP - past end of (ES:DI) buffer (past last byte of buffer)
;               TCBfSharing set to indicate call via ServerDosCall
;
;       EXIT    'C' - invalid path specification: too many .., bad
;                     syntax, etc.
;                   AX = error code
;               'NC'
;                   DS:SI - advanced to end of string
;                   ES:DI - advanced to end of canonicalized form after nul
;                   ES:WFP_Path_End -> byte after last path separator.
;                                      (first byte of filename part.)
;                   CX    - Maximum number of DOTs found in a single component in
;                           the path. This is used to error out for FAT FS.
;                   AX    - Number of dots found in last component of path This
;                           is for use with NO NEWFILES and IFS in FSD_ProcessName.
;                   DL = 0 -> entire path is 8.3 (legal for FAT fs)
;                   DL = 1 -> non-8.3 chars seen in path
;
;       USES    AX, CX, DX, SI, DI, Flags
;
Procedure Canonicalize,HYBRID
    ASSUME  DS:NOTHING,ES:NOTHING,SS:TASKAREA
        PMONLY
LocalVar    cz_BP,BP                ; BP of entry
LocalVar    cz_MaxNumDots,WORD      ; Count of maximum dots found in a component
LocalVar    cz_NumDotsLastComp,WORD ; Number of dots found in last component
LocalVar    cz_8dot3,BYTE           ; any non-8.3 chars seen

        xor     dx,dx                   ; not a UNC name
 ;
 ; DX = 0  if not an UNC name
 ; DX = 1  if it is an UNC name         ;

Entry   UNC_Canonicalize

        EnterProc <0,0,FATNAME>         ; initialize to no non-8.3 chars seen
                                        ;72417 moved from PTDA to TCB
        SetBP_CurrTCB
        mov     [bp].TCBWFP_Path_End,DI
        MOV     [bp].TCBcMeta,-1
        UnSetBP_CurrTCB
                                        ;72417 end
;
; Copy leading path separators (0, 1, 2 or 3 allowed ; 2 and 3 is a UNC name.
; we skip the remaining instead of giving an error.
; As soon as we encounter a non-path-separator, go to CanonDec.
;
        LODSB                           ; get first possible path separator
        JNPathChar <CanonDec>           ; test and translate (/ => \) path char
uncc1:
        CMP DI,cz_BP                    ; are we at the end of the string?
        LJAE    CanonBad                ; yup, bad news
        STOSB                           ; nope, copy char to destination

        LODSB                           ; 2nd possible path separator (UNC name)
        JNPathChar <CanonDec>
uncc2:
        or      dx,dx                   ; are we looking for UNC names ?
        LJNZ    CanonUNC                ; Yes, go process UNC name

; we hit more than one leading path separator and we are not looking for
; a UNC name; we should gobble these extra separators and continue
; normally
        xor     cx,cx                   ; (cx) = 0, do not copy more '\'s
        DEC     SI                      ; undo last LODSB
        JMP     CanonPath               ; eat up extra separators and
                                        ; continue normally

CanonDec:
        DEC     SI                      ; undo effects of LODSB
;
; Main canonicalization loop.  We come here with DS:SI pointing to a textual
; component (no leading path separators) and ES:DI being the destination
; buffer.
;
CanonLoop:
;
; If we are at the end of the source string, then we need to check to see that
; a potential drive specifier is correctly terminated with a path sep char.
; Otherwise, do nothing
;
        XOR     AX,AX
        CMP     [SI],AL                 ;       if (*s == 0) {
        JNZ     DoComponent
        CMP     BX,DI                   ; don't test for double byte character
        JE      Canon_2                 ;   if at beginning of string
        MOV     DX, DI
        DEC     DX
        call    ExmDBCS                 ; double byte character?
        JC      DoTerminate             ; yes, don't check for ':'
Canon_2:
;
; BUGBUG tedm 11/26/89:
;       At this point we are examining a part of the buffer that is not
;       necessarily valid!  What if the canonicalization destination
;       buffer starts at offset 0?  The assumption that there are chars
;       in the buffer before the start of the pathspec is bogus.
;
        or      di,di                   ; avoid testing d[-1] if at offset 0
        jz      DoTerminate
        CMP     BYTE PTR ES:[DI-1],':'  ;           if (d[-1] == ':')
        JZ      caz_asl
        CMP     BYTE PTR ES:[DI-1],'\'  ;
        jne     DoTerminate             ;
        cmp     bx,dx                   ; check for a str. like "c:\"
        je      DoTerminate             ;
        jmp     short CanonPNF          ; path not found error

caz_asl: MOV    AL,'\'                  ;               *d++ = '\';
        STOSB
                                        ;72417 moved from PTDA to TCB
        SetBP_CurrTCB
        INC     [bp].TCBWFP_Path_End
        UnSetBP_CurrTCB
                                        ;72417 end
        MOV     AL,AH
DoTerminate:
        STOSB                           ;           *d++ = 0;
        CLC                             ;           return (0);
        mov     dl,cz_8dot3             ; return whether non-8.3 chars seen
        MOV     cx,cz_MaxNumDots        ; Return count of max dots per component.
        MOV     ax,cz_NumDotsLastComp   ; and number of dots found in last component
canret: LeaveProc                       ; clean off stack
        RET

;       Bad input.  All we need to do is determine which error to return

CanonBad:
        CALL    ScanPathChar            ; check for path chars in rest of string
        MOV     AX,ERROR_FILE_NOT_FOUND ; Set up for bad file error
        JNZ     PathEnc                 ; path character encountered in string
CanonPNF: MOV   AX,ERROR_PATH_NOT_FOUND ; Set up for bad path error
PathEnc:
        STC
        jmp     short canret
;
; We have a textual component that we must copy.  We uppercase it in the
; process.
;
DoComponent:                            ;           }
        call    RemoveLeadDots          ; remove . and .. lead path component
        jnc     LeadDotsSuccess         ; no anomilies removing lead .'s

        or      ax,ax                   ; was there an error with the path?
        jnz     PathEnc                 ;   yes, error with path

        jmp     CanonLoop               ; end of the source string found
;
; cx = 0 if no . or .. found in the path
; cx = nonzero -> we encountered a . or ..; we must also have copied
; a path sep
;

LeadDotsSuccess:jcxz    NoDots          ;
        dec     si                      ; point back to the path separator
        dec     di                      ;
        jmp     short CanonPath

NoDots:                                 ; no leading . or .. in source
                                        ;72417 moved from PTDA to TCB
        SetBP_CurrTCB
        mov     [bp].TCBWFP_Path_End,DI
        UnSetBP_CurrTCB
                                        ;72417 end
        SaveReg <bp>
        mov     bp,cz_BP                ; BP -> End of destination buffer
        CALL    CopyComponent           ;       if (!CopyComponent (s, d))
        RestoreReg <bp>
        jc      PathEnc                 ;           return (-1);

        or      cz_8dot3,dl             ; remember if non-8.3 chars seen
        mov     cz_NumDotsLastComp,ax   ; Keep track of dots in last component
        cmp     cz_MaxNumDots,ax        ; Did we find more dots per component?
        jae     CanonNormal             ; No. Don't update our max.
        mov     cz_MaxNumDots,ax        ; Yes. Update our current max.
;
; We have a normal path.  Advance destination pointer over it.
;
CanonNormal:                            ;       else
        ADD     DI,CX                   ;           d += ct;
;
; We have successfully copied a component.  We are now pointing at a path
; sep char or are pointing at a nul or are pointing at something else.
; If we point at something else, then we have an error.
; cx = 0 if we have to skip all pathseparators
; and nonzero if we can copy one pathsep.
CanonPath:
        mov     al,[si]                 ;
        or      al,al                   ; at end of string?
        jz      uncc3                   ; yes
        JNPathChar <CanonBad>           ; error if not pointing to path sep.
;
; Copy the first path char we see.
;
uncc3:  LODSB                           ; get the char
        JPathChar <uncc4>
        jmp     CanonDec
uncc4:
        jcxz    CanonPathLoop           ; don't store this if we don't want it
        CMP     DI,cz_BP                ; beyond buffer end?
        JAE     CanonBad                ; yep, error.
        STOSB                           ; copy the one byte
;
; Skip all remaining path chars
;
CanonPathLoop:
        LODSB                           ; get next byte
        JPathChar <CanonPathLoop>       ; skip path char
        DEC SI                          ; no - back up
        JMP     CanonLoop               ; go copy component
;
; We hit the second leading separator (a UNC name) and we have been looking
; for a UNC name .
;
CanonUNC: CMP   DI,cz_BP                ; Are we at end of destination buffer?
        JAE     CanonBad                ; Yes. Return error.
        STOSB

        LODSB                           ; If there is a 3rd separator copy it too
        JNPathChar <can2>
        STOSB                           ; store the 3rd separator
 can1:  LODSB                           ; skip the remaining separators also
        JPathChar <can1>
;
; We come here if there's a servername (starts with \\ or \\\)
;
can2:   DEC     SI                      ; undo effects of LODSB
        mov     cx,si                   ; remember the starting of string
;
; copy servername from src to dest, capitalizing on the way
;
can3:   CMP     BYTE PTR [SI],0         ; are we at end of source string?
        LJZ CanonBad                    ; if so, error
        LODSB                           ; if not, continue getting servername
        JPathChar <can4>
IFDEF DISABLENEWFILES
        CALL    UCase                   ; capitalize char
ENDIF
        CMP     DI,cz_BP                ; are we at end of dest buffer?
        LJA     CanonBad                ; if so, error
        STOSB                           ; otherwise, store char...
;
; if this is a DBCS char, copy next char without validating
;
        TestDBCS <can3>         ; is it DBCS lead char?
        JNZ     can3                    ; no; loop again
        LODSB                           ; yes; load trailing char
        CMP     DI,cz_BP                ; are we at end of dest buffer?
        LJA     CanonBad                ; if so, error
        STOSB                           ; store it...
        JMP short can3                    ; ...and continue
;
; when we hit a path separator, undo effects of LODSB and continue normally
; cx = start of string
can4:   DEC     SI
        SaveReg <SI, ES, BX, CX>        ;
        sub     si,cx                   ; si = count of chars - 1
        mov     cx,si                   ; cx = count of chars in string
        RestoreReg      <si>            ; si = start of string
        CALL    ChkVal                  ; check for invalid characters
        RestoreReg <BX, ES, SI>
        MOV     AX,ERROR_INVALID_NAME   ; assume we found bad char
        LJZ     PathEnc
        or      cz_8dot3,dl
        mov     bx,di                   ;set the backup limit for UNCs
                                        ; properly
        mov     cx,1                    ; we have to copy one pathsep atleast
        JMP     CanonPath               ; shortname is an 8.3 component
EndProc Canonicalize


BREAK <RmLeadPathDots - remove lead . and .. source path components>

;***    RmLeadPathDots - remove lead . and .. source path components
;
;       This procedure takes a source path and a destination path
;       and removes all leading . and .. components from the source
;       path, adjusting ES:DI to point to the place where the next
;       character should be added to the destination string.
;       An error occurs if there are more .. components stripped
;       by this routine than elements in the destination path
;       to be backed up over.
;
;       Normal Input (0 or more lead . or .. components)
;
;           source: ..\.\.\..\pathxxx\newpath0
;                   ^                        ^
;              DS:SI points here            null terminator
;
;           dest:   X:\pathcomp1\pathcomp2\pathcomp3\
;                     ^                              ^
;                ES:BX is the backup limit      ES:DI points here
;
;       Normal Exit  (no error and end of source string not reached
;
;           source: ..\.\.\..\pathxxx\newpath0
;                             ^
;                       DS:SI points to the first non . or .. component
;
;           dest:   X:\pathcomp1\pathcomp2\pathcomp3\
;                                ^
;                   ES:DI points where the next component should be added
;
;       End of Source Exit  (no error and end of source string reached)
;
;           source: ..\.\.\..0
;                            ^
;                       DS:SI points to the null terminator
;
;           dest:   X:\pathcomp1\pathcomp2\pathcomp3\
;                               ^
;                   ES:DI points to where the null terminator should be placed
;
;           carry set and AX = 0
;
;       End on Error (attempted to back up past the backup limit)
;
;           source: ..\..\..\..0
;                            ^
;            DS:SI (undefined) points to the beginning of the offending ..
;
;           dest:   X:\pathcomp1\pathcomp2\pathcomp3\
;                     ^
;            ES:DI (undefined) points to the backup limit
;
;           carry set and AX = ERROR_PATH_NOT_FOUND
;
;
;       ENTRY   ES:DI - point to end of path string being built
;               DS:SI - beginning of source string
;               BX has current directory back up limit (point to a / \)
;
;       EXIT    Carry Clear - normal exit
;                   ES:DI backed up to where the next component should be added
;                   DS:SI advanced past all leading . or .. path components
;                   AL has char from output ES:DI (path sep if carry clear)
;               Carry Set - abnormal exit (error or end of source string)
;                   if end of source string
;                       Carry Set
;                       AX = 0
;                       ES:DI backed up to end of path
;                       DS:SI points to null terminator
;                   if error
;                       Carry Set
;                       AX = ERROR_PATH_NOT_FOUND
;
;       USES    AX,SI,DI,Flags
;
;       NOTE    This routine properly handles DBCS characters
;
Procedure   RemoveLeadDots,NEAR,LOCAL
    ASSUME  DS:NOTHING,ES:NOTHING,SS:TASKAREA
        PMONLY
;
; We special case the . and .. cases.  These will be backed up.
;
        xor     cx,cx
rlpd05: mov     al,byte ptr ds:[si]     ; get the first char in the component
        cmp     al,'.'                  ; does the component start with "."?
        jne     rlpd40                  ; no, not a . or .. lead component
                                        ; yes, so...
        mov     al,byte ptr ds:[si+1]   ; get the second char in the component
        JPathChar <rlpd09>              ; is it a path separator char or null?
        or      al,al                   ; yes, done checking for . or ..
        jz      rlpd09                  ;

        cmp     al,'.'                  ; does the component start with ".."?
        jne     rlpd40                  ; no, not a . or .. lead component
;
; We found a '..' Now check if that is the whole component.
; NOTE: File names starting with '..' are allowed.
;
        mov     al,byte ptr ds:[si+2]
        or      al,al                   ; check for path separator after ..
        jz      rlpd07                  ; to make sure that .. exactly
        JNPathChar <rlpd40>             ; constitutes the component
rlpd07: dec     di
        CALL    SkipBack                ; this is the ".." path component
        MOV     AX,ERROR_PATH_NOT_FOUND ; Set up for possible bad path error
        jc      rlpdx                   ;   error, return failure

        inc     di                      ; go forward over path separator
rlpd09: inc     cx                      ; we encountered some lead dots
rlpd10: lodsb                           ; get next char from source string
        JPathChar <rlpd05>              ; it's a path char--do next component
        or      al,al                   ; End of ASCIIZ string?
        jnz     rlpd10                  ; No, check next char

        dec     di
        dec     si                      ; ds:si -> end of input string
rlpd20: xor     ax,ax                   ; error code = 0
rlpd30: stc                             ; carry indicates abnormal exit
        jmp     short rlpdx             ; exit, lead dots in path found

rlpd40: clc                             ; exit no error
        mov     ax,cx                   ;
rlpdx:  ret

EndProc RemoveLeadDots


BREAK <SkipBack - move backwards to a path separator>

;***    SkipBack - look at ES:DI and backup until it points to a / \
;
;       ENTRY   ES:DI - point to a char
;               BX has current directory back up limit (point to a / \)
;
;       EXIT    ES:DI backed up to point to a path char
;               AL has char from output ES:DI (path sep if carry clear)
;               Carry set if illegal backup
;               Carry Clear if ok
;
;       USES    DI,AL,Flags
;
;       NOTE    This routine properly handles DBCS characters
;
Procedure   SkipBack,HYBRID
    ASSUME  DS:NOTHING,ES:NOTHING,SS:TASKAREA
        PMONLY

        CMP     DI,BX                   ;   while (TRUE) {
        JB      SkipBad                 ;       if (d < dlim)
        DEC     DI                      ;           goto err;
        MOV     AL,ES:[DI]              ;       if (pathchr (*--d) && !DBCS)
        JNPathChar <SkipBack>           ;               break;
        SaveReg <DX>                    ;   }
        MOV     DX, DI
        call    ExmDBCS                 ; double byte character?
        RestoreReg <DX>
        JC      SkipBack                ; yes, keep looking
        cmp al,'/'                      ; translate / to \
        jne sb2
        mov al,'\'
sb2:
        CLC                             ;       return (0);
        RET                             ;
SkipBad:                                ;err:
        MOV     AX,ERROR_PATH_NOT_FOUND ; bad path error
        STC                             ;   return (-1);
        RET                             ;
EndProc SkipBack


Break <CopyComponent - copy out a file path component>

;***    CopyComponent - Copy a component from the path string
;
;       CopyComponent copies a file component from a path string (DS:SI)
;       into ES:DI. It converts any letters to upper case, and copies
;       across any DBCS characters. (We let the file system that will get
;       the string handle any DBCS characters. It is up to the particular
;       file system to return an error if it does not recognize the DBCS
;       characters).
;       A check is made to see that we do not exceed the limit on the
;       destination buffer (as indicated by BP on entry).
;       Validation is done for FCB character set.
;
;       ENTRY   DS:SI - source path (ASCIIZ STRING)
;               ES:DI - destination   (BufStart)
;               ES:BP - end of buffer (BufEnd)
;
;       EXIT    'C' - (AX) = error code
;                   if (AX) == ERROR_FILENAME_EXCED_LIMIT,
;                       [cMeta] has number of wildcard characters
;                       (AX) = ERROR_INVALID_NAME
;                          if there are invalid characters in the component
;               'NC' - DS:SI moved past component
;                      AX    - Maximum number of DOTs found in a single component in
;                              the path. This is used to error out for FAT FS.
;                      dl = 0 -> 8.3 only characters
;                         = 1 -> FSD legal characters
;                                     the path. This is used to error out for FAT FS.
;               CX has length of destination
;
;               [cMeta] has number of wildcard characters
;
;       USES    AX,CX,DX
;
;       ALGORITHM
;           Error=FALSE
;           Done = FALSE
;           BufLen = BufEnd - BufStart
;           While ((BufLen--) && (NOT Done)) {
;               Get a character
;               UCASE
;               if (TestDBCS()) {
;                   Store character
;                   Copy trailing byte
;                   LastNonSpPtr = TempNamePtr  /* Remember last non-space */
;               else  {                     ; Not DBCS
;                   if char is pathchar or null
;                       Done = TRUE         ; End of this component in path
;                   else
;                       if char is '*' or '?'
;                           MetaFlg = 1
;                       validate character
;                       if (char != " ")
;                           LastNonSpPtr = TempNamePtr  /* Remember last non-space */
;                   }
;               }
;           if (Done) {                     ; Valid path component
;               if (!TCBfSharing) {
;                   [cMeta] += (MetaFlg & 1)
;                   if [cMeta] > 0
;                       ERROR = CopyBad
;                   else if ([cMeta] == 0) && (MetaFlg == 0)
;                       ERROR = CopyBadPath
;                   }
;               Store the character
;               Store NULL
;               }
;           else                                ; Some error occurred
;               GET CORRECT ERROR CODE
;           Return (String length, pointers)
;
Procedure CopyComponent,NEAR,local
    ASSUME  DS:NOTHING,ES:NOTHING,SS:TASKAREA
LocalVar  cc_CopyBP,BP                  ; old BP value
LocalVar  cc_CopyD,DWORD
LocalVar  cc_CopyS,DWORD
LocalVar  cc_LastNonSpPtr,WORD          ; offset of last non-space character in dest
LocalVar  cc_8dot3,BYTE                 ; any non-8.3 chars seen
LocalVar  cc_NumDots,WORD               ; Number of dots found in component

        PMONLY
;
; Ensure that this routine is always in the High2Code so that we can use the
; XLAT on the table CharTypeHigh2.
;
        SegAssert CS,<HIGH2CODE>,"CopyComponent"
;
        EnterProc <ES,DI,DS,SI,DI,FATNAME,0> ; Setup stack frame. Initialize count to zero.
        SaveReg <bx>                        ; Preserve bx for caller
        LEA BX,CharTypeHIGH2                ; HIGH2CODE:BX -> Table of character types
        XOR     DL,DL                       ; Assume no meta-characters in component
;
; Copy the component over, keeping track of any meta-characters.
;
        MOV     CX,cc_CopyBP
        SUB     CX,cc_CopyD.Offst   ; CX = size of buffer
        LJBE    CopyBad             ; if there is no space in the dest
                                    ; buffer, quit NOW
;
; We can enforce the 255 maximum component length at the same time by
; setting buffer size to the minimum of buffer space left and (255 + 1) for
; the trailing component separator (0 or \).
;
        cmp     cx,MAXCOMPLEN + 1   ; buffer size > max component length?
        jbe     cc30                ; no, go continue processing
        mov     cx,MAXCOMPLEN + 1   ; yes, set to max component length

cc30:   LODSB
        TestDBCS <cc40>
        jnz     cc40                    ; It is not a DBCS character
;
; The character is a double byte character. We copy the first byte to the
; destination and copy the second byte as well. This works for DBCS since a
; NULL is not a valid trailing byte
;
        STOSB
        dec     cx
        or      cx,cx
        jnz     cc30_1
        jmp     CopyBadName
cc30_1:
        LODSB
        or      al,al           ; trail byte is nul?
        jnz     cc60            ;                        ;### PTR B718696
cc30a:  jmp     CopyBadName     ; yes, go return error   ;### PTR B718696

; The character is a single-byte character. Examine it to see if it
; is a path separator or a NULL. If it is, we have reached the end of
; the component.

cc40:
IFDEF DISABLENEWFILES
        call    UCase
ENDIF
        JPathChar <cc70>                ; branch if at end of component
        or      al,al                   ; or at end of string
        jz      cc70
        mov     ah,al                   ; save character in AH
        xlat    byte ptr CS:[BX]        ; AL = type of char
        xchg    ah,al                   ; AL = Char ; AH = type
        test    ah,fFCB                 ; illegal file name character ?
        jz      cc30a                   ; Yes, return error ;### PTR B718696
        test    ah,fNewChar             ; is this a non-8.3 character?
        jnz     cc45                    ; no, go continue
        mov     cc_8dot3,NONFATNAME     ; yes, remember it

cc45:   CMP     AL,'*'
        JZ      cc50
        CMP     AL,'?'
        JNZ     cc60                    ; Not a meta-character
;
; We found a meta character in this component. We increment [cMeta] and if
; it becomes greater than 0, we know that there was another meta-char in
; the path name. This is an error.
;
cc50:   MOV     DL,1                    ; Remember that we found a meta character
;
; The character in AL is valid. Store it in the destination and process
; the next one, unless we have exceeded the limit of the destination buffer.
;
cc60:   STOSB
        cmp     al,' '                  ; this test is OK because ' ' isn't a valid trailing byte
        je      cc65                    ; Found a space. Skip pointer update.
        cmp     al,'.'                  ; trailing '.'s also should be removed
        jne     cc63
        inc     cc_NumDots              ; Found a dot - keep a count of it.
        jmp     short cc65

cc63:   mov     cc_LastNonSpPtr,DI      ; Remember where last non-space found.
cc65:   LOOP    cc30                    ; Get next character
;### PTR B718696 ####
;*** If we get here then path plus filename exceed legal size, thus let us         ;### PTR B718696
        CALL    ScanPathChar            ; check for path chars in rest of string   ;### PTR B718696
        MOV     AX,ERROR_FILENAME_EXCED_RANGE                                      ;### PTR B718696
        JNZ     CopyErr                 ; Bad file if no more path chars           ;### PTR B718696
        JMP     SHORT CopyBadPath       ; If more path chars then bad path error   ;### PTR B718696

; If we are redirected, we do not care how many meta-characters we have
; found in the path - we just copy them over to the destination.
; If we are local, then we can only allow meta-characters in the last
; component of the path.

                                        ;72417 moved from PTDA to TCB
cc70:   SetBP_CurrTCB
        ADD     TASKAREA:[bp].TCBcMeta,DL
        UnSetBP_CurrTCB
                                        ;72417 end
        pushf
        PUSH    BP
        MOV     BP,TASKAREA:[CurrTCB]
        TEST    [BP].TCBfSharing,-1
        POP     BP
        jz      cc75                    ; not redirected
        popf
        or      dl,dl           ;meta found in last component
        jnz     cc77            ; yes, don't truncate trailing dot
        jmp short cc80
;
; Check to see if any meta-characters had been encountered earlier. If so,
; i.e. cMeta is zero, then return a bad path error because we only allow
; meta characters in the last component of a path.
; DL = 1 if we encountered a meta character in this component, 0 if not.
;
cc75:   popf
        JG      CopyBad                 ; More than one meta character found
        JNZ     cc80                    ; No meta char found
        OR      DL,DL
        JZ      CopyBadPath             ; Meta was found in earlier component
; metachar is present in the current component. We cannot blindly remove the
;trailing dots from the  end of the component in this case. We have to
; leave atleast one '.' at the end
;
cc77:   cmp     di,cc_LastNonSpPtr      ;
        je      cc80                    ; we didn't truncate any, no processing
        mov     di,cc_LastNonSpPtr      ;
        cmp     byte ptr es:[di],'.'    ; is this a '.' ?
        jne     cc80                    ; no, no change at all
        inc     cc_LastNonSpPtr         ; Just allow one trailing dot
;
; We have reached the end of the component and have not found anything wrong
; with the path or the compponent. Terminate the string copied with a NULL.
;
cc80:   DEC     SI                      ; Back up over path separator or NULL
        MOV     cc_CopyS.Offst,SI       ; Adjust pointer to end of component
        XOR     AL,AL                   ; Clears Carry
        mov     di,cc_LastNonSpPtr      ; (ES:DI) -> one byte past last non-space
        STOSB
        JMP     SHORT ccEnd             ; We found a valid '.' or '..'
;
; illegal char in path/file name
CopyBadName:
        MOV     AX,ERROR_INVALID_NAME
        JMP SHORT CopyErr
; We encountered an error. Determine whether we should return a "bad file"
; error or a "bad path" error.
;
CopyBad:
        CALL    ScanPathChar            ; check for path chars in rest of string
        MOV     AX,ERROR_FILE_NOT_FOUND
        JNZ     CopyErr                 ; Bad file if no more path chars
;
; The path was bad.
;
CopyBadPath:
        MOV     AX,ERROR_PATH_NOT_FOUND ; set bad path error
CopyErr:
        STC
;
; Return any error indication, plus the length of the component copied.
; Also move DS:SI past the component that was copied.
;
ccEnd:  PUSHF
        mov     cx,di                   ;
        LES     DI,cc_CopyD             ; (ES:DI) = original destination addr
        LDS     SI,cc_CopyS             ; (DS:SI) = updated source addr
        sub     cx,di                   ; length of dest. string including null
        DEC     CX                      ; DON'T ADD NULL
        POPF
        RestoreReg <bx>
        jc      ccRet
        mov     ax,cc_NumDots           ; (AX) = number of dots found in component
        mov     dl,cc_8dot3             ; return whether non-8.3 char seen
ccRet:  LeaveProc                       ; clean off stack
        RET
EndProc   CopyComponent


Break   <ValidateCDS - given a CDS, determine the volume>

;***    ValidateCDS - Assure the knowledge of the volume and current directory
;
;   FUNCTION
;
;       Joins (if necessary).  Calls WhatVolume to verify volume.  If
;       volume was uncertain, do FSD_Chdir to validate path.  If invalid,
;       reset original CDS to root.
;
;   ENTRY   [ThisCDS]  = ptda_pCDS (copy of CDS of interest)
;           (SS:DI)   -> temp buffer for the current dir.
;
;   EXIT    'NC' - no error
;               The current directory string is validated on the appropriate drive
;               (ES:DI) (= ThisCDS) -> original CDS
;               [TCBThishVPB]        = current hVPB
;
;           'C'  - Error (currently user FAILed to I 24)
;
;   NOTE    ValidateCDS always forces a JOIN (i.e. it always calls Join to
;           perform any JOIN if necessary).
;
;   USES    AX, BX, CX, DL, DI, SI, DS, ES, flags
;
;   PSEUDOCODE {
;
;       strcpy(Temp,ThisCDS.cd_text)            /* copy CD into buffer */
;       call Join                               /* do any joining needed */
;       if (Error in Join)                      /* join failed */
;           goto DONE
;       if (!remote) {
;           call WhatVolume                     /* volume info is current */
;           if (WhatVolume error)               /* whatvolume failed */
;               goto DONE
;           update ThisVPB
;       }
;       if (remote || CD_MEDIASWAPPED) {        /* vol has changed || remote */
;           if (Joined)                         /* join did useful work */
;               strcpy (ThisCDS.cd_text,bufTemp)/* put newer CD in CDS */
;           FSD_ChDir (CHDIR_VERIFY)            /* validate directory */
;           if (handle != 0)                    /* RMP CDS exists */
;               GetCDS                          /* get real CDS */
;           else
;               ES:DI = ThisCDS = ptda_pCDS
;           if (FSD_ChDir failed) {
;               Change CDS text to root         /* FSD cleaned up dependent data */
;               Set CDI_ISROOT
;               Zero out cdfsd
;           }
;           else
;               copy cdfsd to RMP               /* FSD may have changed it */
;           Set CDI_ISVALID
;           Reset CD_MEDIASWAPPED
;           if (handle != 0)                    /* we updated the RMP_CDS */
;               copy RMP_CDS = ptda_pCDS
;       }
;       if (old handle != new handle) {         /* different CDS */
;           GetCDS
;           copy RMP_CDS -> ThisCDS             /* update the local copy */
;       }
;       Clear carry
;       es:di = ThisCDS
;       return
;   } /* ValidateCDS */

Procedure   ValidateCDS,NEAR
    ASSUME  DS:NOTHING,ES:NOTHING,SS:TASKAREA
        PMONLY

        LocalVar    Temp,WORD                   ; offset to temp buffer
        LocalVar    SaveCDSHandl,WORD
        LocalVar    Savecdi_hVPB,WORD               ; #94772

        TimerOn TIM_FS_VALIDATECDS
        EnterProc   <di>

        mov     si,TASKAREA:CurrTCB             ; (SI) = current TCB     ;### PTR B720732
        lds     si,TASKAREA:[si].TCBThisCDS     ; (DS:SI) = CDS pointer  ;### PTR B720732
;### PTR B720732        lds     SI,ThisCDS              ; (DS:SI) -> CDS

        mov     AX,[SI.cd_handle]       ; (AX) = CDS handle
        mov     SaveCDSHandl,AX

;   Move cur dir text into temp buffer (for join); do join (always)

        TaskContext ES                  ; (ES:DI) -> temp buffer
        lea     SI,[SI].cd_fsi.cdi_text ; (DS:SI) -> cur dir text
        call    FStrCpy                 ; copy current dir. into temp. buffer

;   Handle JOINed (joined) drives

        TaskContext DS
        mov     bx,FPATH_JOIN           ; Force the JOIN not worrying about exact matches
        call    Join                    ; attempt to join, get physical drive
    ASSUME      DS:NOTHING              ; (CX) = Join was done
        LJC     vcds_ret                ; failed Join

;   Assure we know what volume is in drive (local drives only)
;
;   FSCFromCDS returns the FSC & PathNameType (== REMFILE) for remote drives.
;   For local drives, it does a WhatVolume, updates CDS.cdi_hVPB & TCBThishVPB,
;   returns the correct FSC, and the PathNameType (== LocFile)

        mov     di,TASKAREA:CurrTCB             ; (di) = current TCB     ;### PTR B720732
        les     di,TASKAREA:[di].TCBThisCDS     ; (es:di) = CDS pointer  ;### PTR B720732
;### PTR B720732        les     di,ThisCDS              ; ES:DI -> CDS
        SaveReg <bp>
        call    FSCFromCDS              ; ES:DI -> FSC; BL = PathNameType
        RestoreReg  <bp>
        LJC     vcds_ret                ; error on return

        mov     si,CurrTCB
        mov     TASKAREA:[si].TCBThisFSC.Offst,di
        mov     TASKAREA:[si].TCBThisFSC.Segmt,es   ; TCBThisFSC -> FSC
                                        ;72417 moved from PTDA to TCB
        mov     TASKAREA:[si].TCBPathNameType,bl    ; Store path type
                                        ;72417 end

        cmp     bl,PNType_REMFILE       ; is this a remote drive?
        LJE     vcds7                   ; yes, we don't know about media changes

;   check currency
;
;   if media changed, validate the directory

        TaskContext DS
                                        ;72417 moved from PTDA to TCB
        mov     si,TASKAREA:CurrTCB             ; (SI) = current TCB     ;### PTR B720732
        lds     si,TASKAREA:[si].TCBThisCDS     ; (DS:SI) = CDS pointer  ;### PTR B720732
;### PTR B720732        lds     SI,ThisCDS              ; (DS:SI) -> CDS
        test    [SI.cd_fsi].cdi_flags,CDI_MEDIASWAPPED  ; did the media change
        LJZ     vcds7                   ; media did not change, skip validate

;   We think the media changed. Validate the current directory.
;   We have to set up WFP_Start (for DOS_ChDir)

vcds1:  mov     si,CurrTCB
        SaveReg <TASKAREA:[si].TCBWFP_Start,bp> ; preserve it
        mov     SI,Temp                 ; (DS:SI) -> ASCIIZ path
        SetBP_CurrTCB
        mov     [bp].TCBWFP_Start,si    ; [WFP_Start] = start of path
        UnSetBP_CurrTCB
                                        ;72417 end

;   CX indicates if we joined (changed the WFP)

        jcxz    vcds2                   ; we did not do a join, skip move

;   Since we joined, WFP should have changed.  We must move it in the CDS,
;   which is where FSD_ChDir expects it (for CHDIR_VERIFY).

        mov     di,TASKAREA:CurrTCB             ; (di) = current TCB     ;### PTR B720732
        les     di,TASKAREA:[di].TCBThisCDS     ; (es:di) = CDS pointer  ;### PTR B720732
;### PTR B720732        les     di,ThisCDS
        lea     di,[di].cd_fsi.cdi_text ; (ES:DI) -> cur dir text
        call    FStrCpy                 ; copy WFP into CDS text

;   Now we go validate the Current Directory (text in WFP & CDS)

vcds2:  mov     ax,CHDIR_VERIFY         ; want to verify Cur Dir
        call    FSD_ChDir               ; Verify the chdir, CX = cluster
                                        ;72417 moved from PTDA to TCB
        mov     di,CurrTCB
        RestoreReg  <BP,TASKAREA:[di].TCBWFP_Start>  ; restore BP & WFP_Start
                                        ;72417 end
    ASSUME      DS:NOTHING

;   If the original CDS handle != 0 (0 is set up in InitCDS; means that the
;   cur dir is really the root), then we must get the CDS pointer back
;   (from RMP).

        pushf                           ; save FSD_ChDir error state
        mov     AX,SaveCDSHandl
        mov     di,TASKAREA:CurrTCB             ; (di) = current TCB     ;### PTR B720732
        les     di,TASKAREA:[di].TCBThisCDS     ; (es:di) = CDS pointer  ;### PTR B720732
        push    si                                      ; #94772
        mov     si,es:[di.cd_fsi].cdi_hVPB              ; #94772
        mov     Savecdi_hVPB,si                         ; #94772
        pop     si                                      ; #94772
;### PTR B720732        les     DI,ThisCDS              ; (ES:DI) -> CDS
    ASSUME      ES:NOTHING
        or      AX,AX                   ; is Cur Dir = root
        jz      vcds4                   ; it is root, go on
        call    GetCDS                  ; get back pointer to the CDS (RMP)
        LJC     vcds_ier

;   ES:DI -> RMP_CDS (if it exists; otherwise = ptda_pCDS)

        push    si                                      ; #94772
        mov     si,Savecdi_hVPB                         ; #94772
        mov     es:[di.cd_fsi].cdi_hVPB,si              ; #94772
        pop     si                                      ; #94772

;   If FSD_ChDir failed, we set cur dir to the root


vcds4:  popf
        jnc     vcds6                   ; FSD_ChDir succeeded, go on
        mov     BX,es:[di.cd_fsi].cdi_end   ; find end of root
        cmp     bx,CDI_END_ROOT         ; root dir (no join)?
        jne     vcds5                   ; joined, skip increment
        inc     bx                      ; increment past '\'
vcds5:  mov     byte ptr ES:[DI].cd_fsi.cdi_text[BX],0  ; set dir. to root
        or      es:[di.cd_fsi].cdi_flags,CDI_ISROOT

;   Since we changed to a root, we must zero out the cdfsd, because it has no
;   meaning anymore.

        SaveReg <AX,DI>
        LEA     DI,[DI.cd_fsd]
        .386
        XOR     EAX,EAX
        .errnz  SIZE cd_fsd - 8
        STOSD
        STOSD
        CpuMode reset

        RestoreReg  <DI,AX>             ; DS:DI -> CDS, AX = CDS handle
        jmp     short vcds6b

;   FSD_ChDir succeeded, if there is a RMP_CDS, update the cdfsd
;   AX = CDS handle)

vcds6:  or      ax,ax                   ; do we have a valid handle
        jz      vcds6b                  ; no RMP CDS, go on
        SaveReg <di>
        mov     si,TASKAREA:CurrTCB             ; (SI) = current TCB     ;### PTR B720732
        lds     si,TASKAREA:[si].TCBThisCDS     ; (DS:SI) = CDS pointer  ;### PTR B720732
;### PTR B720732        lds     si,ThisCDS              ; DS:SI = ptda_pCDS (ES:DI -> RMP_CDS)
        lea     si,[si].cd_fsd          ; DS:SI -> local cdfsd
        lea     di,[di].cd_fsd          ; DS:DI -> RMP_CDS.cdfsd
        .errnz  SIZE cd_fsd - 8
        .386
        movsd
        movsd
        CpuMode reset

vcds6a: RestoreReg  <di>                ; ES:DI -> RMP_CDS

;   Mark it valid, and reset the MediaSwapped flags

vcds6b: or      es:[di.cd_fsi].cdi_flags,CDI_ISVALID    ; mark it valid
        and     es:[di].cd_fsi.cdi_flags,NOT CDI_MEDIASWAPPED   ; media is known

;   We may have updated the RMP_CDS.  If so, then copy it back to our CDS

        or      ax,ax                   ; is it a valid handle
        jz      vcds7                   ; no, our CDS is current, go on

        mov     cx,TASKAREA:[CDSsize]   ; size of RMP_CDS
        mov     si,TASKAREA:CurrTCB             ; (SI) = current TCB     ;### PTR B720732
        lds     si,TASKAREA:[si].TCBThisCDS     ; (DS:SI) = CDS pointer  ;### PTR B720732
;### PTR B720732        lds     si,ThisCDS
        SaveReg <es,ds>
        RestoreReg  <es,ds>
        xchg    di,si                   ; ES:DI = ptda_pCDS; DS:SI -> RMP_CDS
        shr     cx,1                    ; convert to words (plus carry if odd)
    rep movsw
        jnc     vcds7                   ; even # of bytes; we're done
        movsb                           ; move odd byte

;   Now if we have changed CDSs, then go find the original one, and copy it
;   into buffer pointed to by ptda_pCDS

vcds7:  mov     AX,SaveCDSHandl         ; (AX) = CDS handle
        mov     di,TASKAREA:CurrTCB             ; (di) = current TCB     ;### PTR B720732
        les     di,TASKAREA:[di].TCBThisCDS     ; (es:di) = CDS pointer  ;### PTR B720732
;### PTR B720732        les     DI,ThisCDS              ; (ES:DI) -> CDS
        cmp     AX,es:[di.cd_handle]    ; is it the same CDS?
        je      vcds9                   ; yes, still have CDS, go return
        or      AX,AX                   ; is Cur Dir = root
        jz      vcds9                   ; it is root, go on
        call    GetCDS                  ; (ES:DI) -> CDS (RMP)
        jc      vcds_ier
vcds8:  SaveReg     <ES>
        RestoreReg  <DS>
        xchg    di,si                   ; DS:SI -> RMP_CDS

    ASSUME      DS:NOTHING
        mov     di,TASKAREA:CurrTCB             ; (di) = current TCB     ;### PTR B720732
        les     di,TASKAREA:[di].TCBThisCDS     ; (es:di) = CDS pointer  ;### PTR B720732
;### PTR B720732        les     DI,ThisCDS              ; ES:DI = ptda_pCDS
        mov     CX,TASKAREA:[CDSsize]   ; CX = size of CDS pointed to by ThisCDS
        shr     cx,1                    ; convert to words (plus carry if odd)
    rep movsw                           ; move RMP-CDS to ptda_pCDS
        jnc     vcds9                   ; even # of bytes; we're done
        movsb                           ; move odd byte

vcds9:  clc
vcds_ret:
        mov     di,TASKAREA:CurrTCB             ; (di) = current TCB     ;### PTR B720732
        les     di,TASKAREA:[di].TCBThisCDS     ; (es:di) = CDS pointer  ;### PTR B720732
;### PTR B720732        les     DI,ThisCDS              ; (ES:DI) -> CDS
        LeaveProc
        TimerOff TIM_FS_VALIDATECDS
        ret

vcds_ier:
        InternalError   <ValidateCDS: lost CDS>
EndProc ValidateCDS


Break <Join - pseudo mount by string substitution>

;***    Join - take a string and substitute a prefix if one exists.
;
;       Join only apply to Real Mode.  Also the number of joins
;       done is kept track of to allow a quick check.
;
;       Searches through macro CDS entries looking for a matching JOIN prefix.
;
;       Calls WhatVolume for all cases (except remote drives).
;
;       Sets TCBThishVPB for all cases.
;
;       Change ptda_pCDS to point to physical drive CDS.
;
;       ENTRY   DS:SI point to string
;               (BX) = JOIN flags (this is a bit field) from TransPath2
;                    FPATH_JOIN  - do join (subject to FPATH_JOIN_N_EXACT)
;                    FPATH_JOIN_N_EXACT - join if not exact match
;
;       EXIT    'NC' - No error
;                   Path (DS:SI) may be shortened (prefix replaced by drive letter)
;                   If (JOIN prefix found)
;                       CX != 0
;                       [ThisCDS] = ptda_pCDS (path modified)
;                       Path (DS:SI) may be shortened (prefix replaced by drive letter)
;                   Else (no JOIN prefix found)
;                       CX = 0
;                       [ThisCDS] = ptda_pCDS (path unchanged)
;               'C'  - operation was failed
;
;       USES    AX,BX,CX,DI,SI,DS,ES,flags
;
;       PSEUDOCODE {
;           If (not ProtectMode && FPATH_JOIN)
;               if (JOIN(s) has been done)
;                   for (i=1; i <= 26; i++) {
;                       GetCDS (i)
;                       if (CDS returned)
;                           if (CDS is Joined)
;                               if (PathPref (s,d))     /* prefix matches */
;                                   /*don't do this if we got an exact match, and FPATH_JOIN_N_EXACT */
;                                   if (!exact match | !FPATH_JOIN_N_EXACT) {
;                                       Put out new drive letter (from i)
;                                       Adjust cdi_end if necessary
;                                       Assure path separator
;                                       strcpy (path, cd_text)
;                                       Set cd_Join
;                                       if (ThisCDS.devptr != CDS.devptr)
;                                           MemMove (ptda_pCDS <- RMP_CDS)
;                                       CL != 0
;                                       Jump to FoundPrefix
;                                   }
;                       }
;                   }
;                   if (ThisCDS.cd_Join) {
;                       Find_DPB (for phys. drive)
;                       ThisCDS.devptr = pDPB
;                       ThisCDS.hVPB = DPB.hVPB
;                       ThisCDS.cd_Join = 0
;                   }
;                   CX = 0
;       FoundPrefix:
;                   if (not ThisCDS.cd_isnet)
;                       WhatVolume
;                   TCBThishVPB = DPB.hVPB
;       }
;
;
Procedure   Join,NEAR,LOCAL

    ASSUME  DS:NOTHING,ES:NOTHING,SS:TASKAREA

    LocalVar    Join_Flag,WORD          ; Join flag

        PMONLY

        EnterProc <bx>                  ; Save flag
        push    dx

        cmp     ProcType,PT_REALMODE
        LJNE    splc9                   ; Not the 3xbox. Do nothing.

        test    Join_Flag,FPATH_JOIN
        LJZ     splc9                   ; JOIN not to be done. Return.

                                        ;72417 moved from PTDA to TCB
        SetBP_CurrTCB
        TEST    [bp].TCBJoins,-1        ; has JOIN been run?
        UnSetBP_CurrTCB
                                        ;72417 end
        LJZ     splc9                   ; no, we're done
        XOR     AX,AX                   ;   for (i=1; i <= 26; i++)
JoinScan:
        inc     AL
        cmp     AL,NUMDRIVE
        LJA     splc8
        push    AX
        call    GetCDS
        pop     AX
        JC      JoinScan
        TEST    ES:[DI.cd_flags],CD_JOIN
        JZ      JoinScan                ;       if ( Joined (i) ) {
        SaveReg <DI,SI>
        LEA     DI,[DI.cd_fsi].cdi_text
        CALL    PathPref                ;           if (!PathPref (s, d))
        JZ      JoinFound
JoinSkip:
        RestoreReg  <SI,DI>
        JMP     JoinScan                ;               continue;

JoinFound:
        CMP     BYTE PTR DS:[SI],0      ;           if (*s || FPATH_JOIN_N_EXACT) {
        JNZ     JoinDo                  ; not an exact match, go on
        TEST    Join_Flag,FPATH_JOIN_N_EXACT   ; what do we do with exact match
        JNZ     JoinSkip                ; skip exact match

;*      We found a join.
;
;       ES:DI   point to end of joined directory in CDS
;       DS:SI   point to end of joined directory in path string
;       (TOS)   beginning of path string
;       ES:(TOS+2) point to join CDS structure
;       [CDSsize] = size of CDS at ES:(TOS+2) (Placed by GetCDS)
;
JoinDo:
        DBPRT   80h,8,<Join 80:8 - Splicing drive $b >,<AX>
        RestoreReg      <DI>
        SaveReg <ES>
        SaveReg <DS>
        RestoreReg  <ES>

;       ES:DI   point to path string
;       DS:SI   point to end of joined directory in path string
;       (TOS):(TOS+2) point to join CDS structure
        CALL    TextFromDrive1          ; put out new drive letter
                                        ;72417 moved from PTDA to TCB
        SetBP_CurrTCB
        MOV     AX,[bp].TCBCurr_Dir_End ; adjust Curr_Dir_End by prefix length
        UnSetBP_CurrTCB
                                        ;72417 end
        cmp     ax,-1                   ; is Curr_Dir_End set?
        je      NoPoke                  ; no, leave it un-set
        ADD     AX,DI
        SUB     AX,SI
                                        ;72417 moved from PTDA to TCB
        SetBP_CurrTCB
        MOV     [bp].TCBCurr_Dir_End,AX
        UnSetBP_CurrTCB
                                        ;72417 end

NoPoke: CMP     BYTE PTR [SI],0         ;               if (*p)
        JNZ     JoinCopy                ;                   *src++ = '\\';
        MOV     AL,"\"
        STOSB
JoinCopy:                               ;               strcpy (src, p);
        call    FStrCpy
        RestoreReg      <DS,SI>

        mov     di,TASKAREA:CurrTCB             ; (di) = current TCB     ;### PTR B720732
        les     di,TASKAREA:[di].TCBThisCDS     ; (es:di) = CDS pointer  ;### PTR B720732
;### PTR B720732        LES     DI,ThisCDS              ; ES:DI = ptda_pCDS

; Check if we need to copy join CDS

;*      Fixup our local copy of the CDS.  We need to be sure that the
;       flags reflect that splicing was done, and the DPB pointer and
;       directory cluster are correct.
;
;       (ES:DI) point to our copy of CDS
;       (DS:SI) point to joined drive's CDS
;       [CDSsize] = size of joined drive's CDS
;
        or      ES:[DI.cd_flags],CD_JOIN
        mov     AX,WORD PTR ES:[DI.cd_devptr]
        cmp     WORD PTR DS:[SI.cd_devptr],AX
        jnz     splc6
        mov     AX,WORD PTR ES:[DI.cd_devptr+2]
        cmp     WORD PTR DS:[SI.cd_devptr+2],AX
        jz      splc7                   ; DPB pointer is to joined drive
splc6:  mov     CX,TASKAREA:[CDSsize]   ; Get size of CDS
        shr     cx,1                    ; convert to words (plus carry if odd)
    rep movsw
        jnc     splc7                   ; even # of bytes; we're done
        movsb                           ; move odd byte

splc7:  OR      CL,1                    ; signal join done.
        JMP     SHORT splc91            ;               return;

;*      Splicing in effect, but this path wasn't joined.  Fiddle the
;       local copy of the CDS to correct the DPB pointer and flags.

splc8:
    ASSUME      DS:NOTHING
        mov     di,TASKAREA:CurrTCB             ; (di) = current TCB     ;### PTR B720732
        les     di,TASKAREA:[di].TCBThisCDS     ; (es:di) = CDS pointer  ;### PTR B720732
;### PTR B720732        les     DI,ThisCDS                      ; ES:DI = ptda_pCDS
        test    ES:[DI.cd_flags],CD_JOIN        ; current dir. on joined drive?
        jz      splc9                   ; no, CDS is okay
        mov     AL,ES:[DI.cd_fsi].cdi_text      ; joined c.d., but no join found
        sub     AL,"A"
        call    Find_DPB                ; get DPB to reflect phys. drive
        jc      splc9
        MOV     WORD PTR ES:[DI.cd_devptr],SI
        MOV     WORD PTR ES:[DI.cd_devptr+2],DS
        mov     si,ds:[si].dpb_hVPB
        mov     es:[di.cd_fsi].cdi_hVPB,si  ; Set up hVPB from the DPB
        AND     ES:[DI.cd_flags],NOT CD_JOIN

splc9:  XOR     CX,CX                   ; indicate no join found

splc91: pop     dx
        LeaveProc
        ret

EndProc Join,NoCheck


Break   <DriveFromText - return drive number from a text string>

;***    DriveFromText - examine DS:SI and remove a drive letter, advancing
;       the pointer.
;
;       ENTRY   DS:SI point to a text string
;
;       EXIT  GOOD - AL has drive number (1-based)
;                    DS:SI advanced
;             ERROR - AL = 0 || -1 (bad drive #)
;
;       USES    AX, SI, Flags
;
;       ALGORITHM
;
;             drive = 0
;             if ((*s != 0) && (s[1] == ':')) {
;                  drive = (*s | 020) - ('a' - 1);
;                  s += 2
;              }
;
Procedure   DriveFromText,<near,faronly,far16>
    ASSUME  DS:NOTHING,ES:NOTHING,SS:NOTHING
        PMONLY

        xor     al,al                   ; drive# = 0
        CMP     BYTE PTR [SI],AL        ; is there a drive there?
        jz      dft10                   ; no, return 0
        CMP     BYTE PTR [SI+1],':'     ; does it have a ":" suffix?
        jnz     dft10                   ; no, return 0
        LODSW                           ; AL = "d" OR "D"
        OR      AL,020h                 ; AL = "d"
        SUB     AL,'a'-1                ; AL = 1 based drive#
        jbe     dft20                   ; invalid ?
        cmp     al,'z'-'a' +1           ; drive nos above 'z' also invalid
        ja      dft20                   ;
dft10:  RET                             ; AL = 1 based drive no
; Here, AL contained an invalid drive#(0), so return with -1 for safety
dft20:  MOV     AL,-1                   ; AL = -1 (invalid drive#)
        RET

EndProc DriveFromText


Break   <TextFromDrive - convert a drive number to a text string>

;***    TextFromDrive - turn AL into a drive letter: and put it at es:di with
;       trailing :. TextFromDrive1 takes a 1-based number.
;
;       ENTRY   AL has 0-based drive number
;
;       EXIT    ES:DI advanced
;
;       USES    AX
;
Procedure TextFromDrive,HYBRID
    ASSUME  DS:NOTHING,ES:NOTHING,SS:NOTHING
        PMONLY

        INC     AL
Entry   TextFromDrive1
        ADD     AL,'A'-1                ;   *d++ = drive-1+'A';
        MOV     AH,":"                  ;   strcat (d, ":");
        STOSW
        RET
EndProc TextFromDrive


Break   <PathPref - see if one path is a prefix of another>

;***    PathPref - compare DS:SI with ES:DI to see if one is the prefix of the
;       other.  Remember that only at a pathchar break are we allowed to have a
;       prefix: A:\ and A:\FOO
;
;       this routine is case-insensitive
;
;       ENTRY   ES:DI potential prefix MUST HAVE "\"
;               DS:SI string
;
;       EXIT    ZR => prefix found
;                   DI/SI advanced past matching part
;               NZ => no prefix, DI/SI garbage
;
;       USES    CX
;
Procedure   PathPref,HYBRID
    ASSUME  DS:NOTHING, ES:NOTHING, SS:TASKAREA

        PMONLY
        push    ax

pp1:    cmp     byte ptr es:[di],0      ; did we scan entire string?
        jz      pp2                     ; yes
        xor     cx,cx                   ; clear dbcs flag
        lodsb                           ; get byte from source
        call    UCase                   ; convert upper case
        mov     ah,al                   ; ah = *src
        mov     al,byte ptr es:[di]     ; al = *dest
        inc     di
        call    UCase                   ; convert upper case
        cmp     al,ah                   ; are source and dest ==?
        jnz     pp3                     ; no, go return difference
        TestDBCS <pp1>                  ; double byte character
        jnz     pp1                     ; no, go check next character

;       handle second byte of DBCS character just as first byte

        cmp     byte ptr es:[di],0      ; did we scan entire string?
        jz      pp2                     ; yes
        inc     cx                      ; set DBCS flag
        lodsb                           ; get trailing byte of dbcs
        scasb                           ; source and dest ==?
        jnz     pp3                     ; no, go return difference
        jmp     pp1                     ; yes, go check next character

pp2:    OR      CX,CX                   ; test DBCS flag
        JNZ     pp2_1
        MOV     AL,DS:[SI-1]            ; get last byte to match
        JPathChar <pp3>                 ; is it a path char
pp2_1:  MOV     AL,DS:[SI]              ; get next char to match
        JPathChar <pp3>                 ; was it path char?
        or      al,al                   ; or not, was it a null?
pp3:    pop     ax                      ; get back original
        RET
EndProc PathPref


Break   <ScanPathChar - see if there is a path character in a string>

;***    ScanPathChar - search through the string (pointed to by DS:SI) for
;       a path separator. Note: This routine properly handles DBCS characters.
;
;       ENTRY   DS:SI target string (null terminated)
;
;       EXIT    Zero set => path separator encountered in string
;                 ds:si -> byte after path sep
;               Zero clear => null encountered
;
;       USES    SI, AL
;
Procedure     ScanPathChar,hybrid
    ASSUME  DS:NOTHING, ES:NOTHING, SS:TASKAREA

        PMONLY

        TestDBCSProcess
        jz      spc50                   ; DBCS active ?

; DBCS not active

spc10:  LODSB                           ; fetch a character
        JPathChar <spcExit>
        or      al,al                   ; pathchar or null => ZR set => done
        jnz     spc10
spcExit:
        TestPathChar                    ; path separator?
        RET

;DCBS is active check for each character for lead byte

spc50:  LODSB                           ; fetch next character
        JPathChar <spcExit>
        or      al,al
        jz      spcExit                 ;end of string ?
        TestDBCSLeadByte                ;
        jnz     spc50                   ; not a DBCS char
        lodsb                           ; get trailing byte
        or      al, al                  ; don't miss the null
        jnz     spc50                   ; go for next char
        jmp     short   spcExit         ;path char or null ; done

EndProc       ScanPathChar


Break   <w_Canon - worker for canonicalize string API>

;***    w_Canon converts a name into a canonicalized form
;
;       There are times when an application needs to validate a pathname.
;       We provide an interface to both validate and canonicalize (remove
;       . and .., case map, etc).  w_Canon is merely an interface to
;       Canonicalize above.
;
;       PSEUDO CODE
;
;           Take_Kernel_Sem                  /* get ptda semaphore 72291 */
;           TempBufPtr = GetTempBuffer()
;           if (invalid parameters for user buffer)
;               return(error)
;           if (FuBuff(TempBufPtr,UsrDestBufPtr) == ERROR)  /* safe copy */
;               return(error)
;           if (BackupLimit is out of range)
;               return(error)
;           rc = Canonicalize()                         /* do the actual work */
;           if (rc == ERROR)
;               return(error)
;           If (metacharacters are not allowed)
;               if (rc == FOUND_METACHARACTERS)
;                   return(error)
;           if (SuBuff(UsrDestBufPtr,TempBufPtr) == ERROR)  /* save copy */
;               return(error)
;           Clear_Kernel_Sem               /* release ptda semaphore 72291 */
;           return
;
;       ENTRY   (ds:si)     source path buffer      (supplied by kernel)
;               (es:di)     destination path buffer
;               (bx)        is backup limit
;               (ax)        is end of buffer
;               (dx)        is flags defined as:
;
;                       5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0
;                       <------ reserved ---------> | |
;                                                   | |
;                                                   | meta flag
;                                               uppercase
;       0 no meta characters allowed in name (return ERROR_INVALID_NAME)
;       1 meta characters allowed.  Do not return error.
;       NOTE:  Interupts enabled, 286 POPF bug.
;
;       EXIT    carry clear =>
;                   No error and the path name in the destination is reasonable
;               carry set =>
;                   ax has error code
;
;       USES    AX, CX, DX, DI, SI, BP, DS, ES
;
;  Change activity:
;  ----------------
;  72291 08/26/93 LRN - serialize filesystem fields in ptda (WFP fields,
;              cMeta, and any others). What can happen is that one thread
;              in a process using file system api's such as DosOpen which
;              uses these fields, while another thread in the same process
;              can be doing something like DosCreateMutexSem which calls
;              this routine to canonicalize the named semaphore. The
;              file system api's are serialized by the ptda semaphore
;              which is grabbed/released by the 'enterfscrit/exitfscrit'
;              macros. This routine will also have to get the ptda semaphore,
;              otherwise it may interfere with the file system api's.
;
     Procedure w_Canon,<FARONLY,FAR16>
         ASSUME  DS:NOTHING,ES:NOTHING,SS:TASKAREA
LocalVar    UsrPathNamePtr,DWORD        ; pointer to path to canonicalize
LocalVar    UsrDestBufPtr,DWORD         ; pointer to canonicalized output
LocalVar    BackupLimit,WORD            ; backup limit in UsrDestBuf
LocalVar    UsrDestEnd,WORD             ; offset to last byte in UsrDestBuf
LocalVar    UsrFlags,WORD               ; used for metacharacter flag
LocalVar    TempBufPtr,DWORD            ; pointer to kernel buffer
LocalVar    dummycds,<SIZE curdir>

        EnterProc   <ds,si, es,di, bx, ax, dx>

        CALLFAR Take_Kernel_Sem         ; 72291 serialize ptda
        LJC     wcn17                   ; 72291 abnormal wakeup

        lea     di,dummycds
        push    bx                              ;### PTR B720732
        mov     bx,TASKAREA:[CurrTCB]                      ;### PTR B720732
        mov     TASKAREA:[bx].TCBpTmpCDS.Offst,di       ; Initialize TCBpTmpCDS ;### PTR B720732
;### PTR B720732        mov     TASKAREA:[ptda_pCDS],di ; Initialize ptda_pCDS
        pop     bx                              ;### PTR B720732

        xor     di,di
        mov     es,di                   ; zero segment register, GTB can block
        CALLFAR GetTempBuffer           ; es:di is a temp buffer
        mov     TempBufPtr.Offst,di     ; save the address of the temp buffer
        mov     TempBufPtr.Segmt,es

        mov     cx,UsrDestEnd           ; (cx) = last byte in user buffer
        cmp     cx,UsrDestBufPtr.Offst  ; is the length < 0?
        jb      wcn15                   ;   YES! exit with error

        sub     cx,UsrDestBufPtr.Offst  ; (cx) = length of UsrDestBuf - 1
        inc     cx                      ; (cx) = length of UsrDestBuf
        cmp     cx,MAXPATHLEN           ; is len(TempBuf) >= len(UsrDestBuf)?
        jbe     wcn05                   ;   YES! do not truncate buffer

        mov     cx,MAXPATHLEN           ; (cx) = len(TempBuf)
wcn05:  mov     bx,UsrDestBufPtr.Segmt
        mov     si,UsrDestBufPtr.Offst
        SaveReg <cx>                    ; preserve the buffer length
        call    FuBuff                  ; safe copy
        RestoreReg  <cx>
        jc      wcn15                   ; FuBuff failed, exit with error

        mov     ax,TempBufPtr.Offst
        add     ax,cx                   ; (ax) = end of the kernel buffer
        mov     bx,BackupLimit
        sub     bx,UsrDestBufPtr.Offst  ; Is (bx) after beginning of buffer?
        jb      wcn15                   ;   NO! exit with error

        cmp     bx,cx                   ; Is (bx) past the end of the buffer?
        ja      wcn15                   ;   YES! exit with error
                                        ; bx has the same offset in the kernel
        add     bx,TempBufPtr.Offst     ; buffer as it did in the user buffer
        lds     si,UsrPathNamePtr
        les     di,TempBufPtr
                                        ;72417 moved from PTDA to TCB
        SetBP_CurrTCB
        mov     [bp].TCBcMeta,-1
        UnSetBP_CurrTCB
                                        ;72417 end
        SaveReg <cx,bp>                 ; preserve the buffer length
        mov     bp,ax
        call    Canonicalize            ; go convert the buffer
        RestoreReg  <bp,cx>
        jc      wcn25                   ; Canonicalize failed, exit with error

        test    UsrFlags,1              ; are meta characters a problem?
        jnz     wcn20                   ;   NO, just return success

                                        ;72417 moved from PTDA to TCB
        SetBP_CurrTCB
        inc     [bp].TCBcMeta
        UnSetBP_CurrTCB
                                        ;72417 end
        clc                             ; were there metacharacter in the name?
        jz      wcn20                   ; NO, return success

wcn10:  mov     ax,ERROR_INVALID_NAME
        stc
        jmp     short wcn25

wcn15:  mov     ax,ERROR_INVALID_PARAMETER
        stc
        jmp     short wcn25

wcn17:  mov     ax,ERROR_INTERRUPT      ; 72291 get ptda semaphore failed
        stc                             ; 72291
        jmp     short wcn30             ; 72291

wcn20:  test    UsrFlags,2              ; uppercase name?
        jz      wcn23                   ; no, go copy to user buf

        lds     si,TempBufPtr
        les     di,TempBufPtr
        call    StrCpy                  ; uppercase string in place

wcn23:  mov     bx,UsrDestBufPtr.Segmt  ; copy the canonicalized name out
        mov     di,UsrDestBufPtr.Offst  ; to the user buffer
        lds     si,TempBufPtr
        call    SuBuff                  ; was there an error while copying?
        jc      wcn15                   ;   YES, exit with error

wcn25:  les     di,TempBufPtr
        pushf
        CALLFAR FreeTempBuffer          ; release the kernel buffer
        CALLFAR Clear_Kernel_Sem        ; 72291 release serialization
                                        ; 72291 keep inside pushf/popf
                                        ; 72291  to preserve carry flag!
        popf                            ; STI during this instruction!
;### PTR B720732        mov     SS:[ptda_pCDS],0        ; Clear ptda_pCDS
wcn30:                                  ; 72291 gets here on error path
        LeaveProc
        ret

EndProc   w_Canon


Break   <ScanCDS - see if given path is a prefix of any CDS>

;***    ScanCDS - see if given path is a prefix of any CDS path
;
;       This is used by DOS_Rename and anyone else to make sure no
;       one is using the directory.
;
;       ENTRY   ds:si -> canocalized path (potential prefix)
;               di = non-zero if start from some CDS in RMP
;                    zero if start at beginning of RMP segment
;
;       EXIT    NC - given path is a prefix or equal to a CDS
;                         es:di -> exact CDS
;               CY - given path is not a prefix of any CDS
;;; Nagara 20th July 88.
;;;     AL - the last character in the target path (input ds:si)
;;;          that was checked
;;; END
;
;
;       USES    ax,cx,si,di,es,ds,flags

        assume es:nothing,ds:nothing
Procedure ScanCDS,HYBRID
        LocalVar        sc_lpszPrefix,DWORD     ; -> potential prefix

        EnterProc       <ds,si> ; lpszPrefix = (ds:si)

        ;mov    es,CP_CaseMapTbl.Segmt  ; prevent procedure UCase from blocking
        mov     bx,CP_CaseMapTbl.Segmt  ; AWJ67294 specify the segment and
        mov     si,CP_CaseMapTbl.Offst  ; AWJ67294 offset of the case table
        mov     cx,1                    ; AWJ67294 we will read 1 byte
        mov     dh,ACCTYPE_READ         ; AWJ67294 the operation is a read
        CALLFAR ProbeBuff               ; AWJ67294 this call will force a page
                                        ; AWJ67294 fault if its not in memory

        BeginNoBlock

; first pass, di -> 1st structure, or structure to start at.

sc1:    call    NextCDS

; es:di->next CDS structure

        jc      scx             ;   no more CDS, exit

        SaveReg <di>            ; save pointer to CDS
        add     di,cd_fsi+cdi_text ; (es:di)->CDS directory text
        mov     si,di
        mov     ax,es
        mov     ds,ax           ; (ds:si)->CDS path

        les     di,sc_lpszPrefix ; (es:di)->potential prefix

; es:di->input path (potential prefix)
; ds:si->CDS path

; ZR=prefix found=PathPref(es:di->potential prefix, ds:si->path)

        call    PathPref        ; is input path a prefix of a CDS
                                ; (ZR == prefix or exact match)

        RestoreReg <di>         ; restore pointer to CDS
        mov     es,ax           ; (es:di) -> CDS entry

; ES:DI -> CDS entry if exact match

        jnz     sc1             ; Go check next CDS

sc2:    clc
        mov     al,ds:byte ptr[si]  ;   (ax)== final target byte checked

scx:    EndNoBlock
        LeaveProc
        ret

EndProc ScanCDS


;***    FSCFromCDS - returns FSC from/through the CDS.
;
;       This is used by TransPath, w_QFsInfo & ValidateCDS.
;
;       For remote FSDs (and Redir), the CDS contains the FSC, but
;       for local FSDs (and FAT), we must assure currency, and then go
;       through the DPB and the VPB to get the FSC.
;
;       We update TCBThishVPB and cdi_hVPB for local drives also.
;
;       ENTRY   (ES:DI) -> CDS
;
;       EXIT    if 'NC' - success
;                 ES:DI -> current FSC
;                 BL     = PathNameType
;                 If (local drive)
;                   TCBThishVPB is current
;                   cdi_hVPB is current
;               if 'C' - failed
;                 AX = return code from WhatVolume
;
;       USES    AX, BX, DI, BP, ES, flags

Procedure FSCFromCDS,HYBRID

        ASSUME  DS:NOTHING,ES:NOTHING,SS:TASKAREA

        TimerOn TIM_FS_FSCFROMCDS
        test    es:[di.cd_flags],CD_ISNET       ; is it a remote FSD
        jz      FFC1                            ; no, go use DPB & VPB

;   FSD is remote.  CDS has FSD (in cd_devptr).

        mov     bx,es:[di].cd_fsi.cdi_hVPB      ; (BX) = hVPB
        mov     si,CurrTCB
        mov     TASKAREA:[si].TCBThishVPB,bx    ; TCBThishVPB = hVPB

        les     di,es:[di].cd_devptr            ; ES:DI -> FSC

        mov     bl,PNType_REMFILE               ; BL = 'remote file'
        jmp     short ffc3                      ; All done. Return.

;   FSD is local.  Call WhatVolume; update hVPBs; then go through CDS & DPB
;   to VPB to get the FSC.

FFC1:   SaveReg <ds,si>
        SaveReg <es,di>

        ;* The following is an interim fix to avoid hangtrap when
        ;* cd_devptr comes in as a null pointer. This may be just
        ;* circumventing the problem. Though this fixes the reported
        ;* problem, this needs to be looked into further. The cd_devptr
        ;* is not supposed to be null at any time - 103910

        lds     si,es:[di].cd_devptr            ; DS:SI -> DPB
        mov     ax,ds                           ; 103910 - RV
        or      ax,si                           ; 103910 - RV
        jnz     @f                              ; 103910 - RV
        stc                                     ; 103910 - RV
        jmp     short FFC2                      ; 103910 - RV
                                                ; 103910 - RV
@@:
        call    WhatVolume
        RestoreReg  <di,es>
        jc      FFC2                            ; error, go return error

        mov     bx,ds:[si].dpb_hVPB             ; BX = hVPB

        mov     bp,CurrTCB
        mov     TASKAREA:[bp].TCBThishVPB,bx    ; TCBThishVPB = hVPB
        mov     es:[di].cd_fsi.cdi_hVPB,bx      ; CDS.hVPB = hVPB

        VPBContext  ds,bx                       ; DS:BX -> VPB

        les     di,ds:[bx].vpb_FSC              ; ES:DI -> FSC

        mov     bl,PNType_LOCFILE               ; BL = 'local file'

FFC2:   RestoreReg  <si,ds>
ffc3:   TimerOff TIM_FS_FSCFROMCDS
        ret
EndProc FSCFromCDS


;**     FSD_ProcessName - post path processing
;***    CheckLastComponent - check last component of path for 8.3 compliance
;
;       Called before TransPath non-error exits so file system can apply
;       unique transformation to the WFP. If current process is 3xbox, the
;       path components are truncated to 8.3 format for all file systems.
;
;       ENTRY   TASKAREA:WFP_Start -> wfp
;               [ThisCDS] Points to CDS being used
;               [TCBThishVPB] = hVPB
;               TCBThisFSC -> FSC
;               [PathNameType] = type of path
;               cMeta      = -1 if path contains no meta
;                         != -1 if path contains meta (only the last
;                               component will contain meta because TransPath
;                               will error exit if a middle path component
;                               contains meta)
;               CX = Maximum number of DOTs found in a single component in
;                    the path. This is used to error out for FAT FS.
;               BX = Number of dots found in the last component of the path.
;                    For IFS and an app with no NEWFILES, we reject a path
;                    where the last component has more than one dot.
;               DL = 1 -> non-8.3 characters
;                  = 0 -> 8.3 characters only
;
;       EXIT    if 'NC' - success
;                 WFP processed in place (length may change)
;                 WFP_Path_End updated
;                 if FSD & NEWFILES
;                    if final component of path is not 8.3
;                       [PathNameType] |= PNType_NEWFILE
;               if 'C' - error
;                 ax = error code
;
;       USES    ax,bx,cx,dx,si,di,ds,es,flags
;
;       PSEUDOCODE
;          if (named pipe or local device)
;             return
;          if (pseudo-char device)
;             FS_PROCESSNAME
;             update WFP_Path_End
;             return
;          if (3xbox)
;             check for non-8.3 characters in any component
;             truncate path components
;          else if (FAT fs)
;             check for non-8.3 characters in any component
;             CheckPathSyntax  (enforce 8.3)
;          if (FSD) {
;             FS_PROCESSNAME
;          update WFP_Path_End
;          if (FSD)
;             check for non-8.3 characters in last component
;             check the last component for 8.3
;             if (name not 8.3)
;               if (NONEWFILE)
;                 return error
;               else
;                 set PNType_NEWFILE
;
;

Procedure FSD_ProcessName,near,local
    assume ds:nothing,es:nothing,ss:TASKAREA

    LocalVar    fpn_MaxNumDots,WORD         ; Max. number of dots found in any component
    LocalVar    fpn_NumDotsLastComp,WORD    ; Number of dots in last component of path
    LocalVar    fpn_SaveReg,WORD            ; register save variable   80608

; if local device or pipe, no more processing needed.

        TimerOn TIM_FS_PROCESSNAME

                                        ;72417 moved from PTDA to TCB
        SetBP_CurrTCB
        test    [bp].TCBPathNameType,PNType_LOCDEV or PNType_NMPIPE
        UnSetBP_CurrTCB
                                        ; device or pipe?
        LJNZ    fpn42                   ; yes, go return (NC)

        EnterProc <cx,bx>               ; Store counts for later use

        TaskContext <ds>
        mov     si,CurrTCB
        mov     si,TASKAREA:[si].TCBWFP_Start ; ds:si -> WFP
        mov     bx,CurrTCB              ;
        test    TASKAREA:[bx].TCBPathNameType,PNType_PSDEV ; for pseudo char devices
                                        ;72417 end

;        jnz     fpn20                   ; go call FS_PROCESSNAME
        jz      short fpn1              ;                              80608
        jmp     fpn20                   ; changed to long jump         80608
fpn1:                                   ;                              80608

; if we get here, we have a file/dir name.
; (DS) = TASKAREA
; (BX) = CurrTCB
; (DL) = 1 -> non-8.3 characters
;      = 0 -> 8.3 characters only
; truncate components to 8.3 for 3xbox
;
; 74302 changed the following code to allow long file names, with
; the intent to allow long UNC names for Novell. We will still allow
; long UNC names and pathnames, but we must still truncate the filename
; and extension to be compatible with DOS.                             80608

        cmp     ProcType,PT_REALMODE    ; 3xbox?                      ;74302
        jne     fpn5                    ; no, don't truncate          ;74302

; The following loop counts the number of leading path separator characters
; to see if this is a UNC name. There must be at least two for UNCs.   80608
        xor     dh,dh                   ; init count of path chars     80608
fpnnext:                                ;                              80608
        cmp     byte ptr [si],0         ; end of string?               80608
        jz      short fnpath            ; yes. won't hurt to truncate. 80608
        inc     dh                      ; add to count                 80608
        lodsb                           ; get next char from string    80608
        JPathChar <fpnnext>             ; check for path char          80608
fnpath:                                 ; found a non-path char        80608
        cmp     dh,3                    ; for UNC names, must be >= 3  80608
        jb      short fpntrunc          ; either not a unc name or it  80608
                                        ; is '\\null'. go truncate.    80608

; We have parsed the leading path characters in the UNC name and       80608
; are now pointing at a non-path character (servername) or we          80608
; could be pointing at the end of the string.                          80608
        xor     dh,dh                   ; first time indicator         80608
fpnnext2:                               ;                              80608
        cmp     byte ptr [si],0         ; end of string?               80608
        jz      short fpn2              ; yes, no need to truncate.    80608

        lodsb                           ; get next char of string      80608
        JNPathChar <fpnnext2>           ; loop back if not path char   80608
;
; We get here either when we hit the leading path separator for        80608
; netname (1st time through), or when we hit the leading path          80608
; separator (if any) on the 2nd time through.                          80608

        cmp     dh,1                    ; first time through?          80608
        jz      short fpnNotFirst       ; no.                          80608

        mov     dh,1                    ; Remember that we've been here80608
        jmp     short fpnnext2          ; loop back again.             80608

; We know we're on the 2nd time through. si is now pointing one        80608
; character past the separator (and this could be null).               80608
; So at this point, we are either pointing at a pathname, a file.ext,  80608
; or null (end of string).                                             80608
; Call FindPathEnd to find the last path separator in the WFP. Once we 80608
; have this, we will truncate the possible file.ext which follows it.  80608
fpnNotFirst:                            ;                              80608
        cmp     byte ptr [si],0         ; end of string? (null)        80608
        jz      short fpn2              ; yes, no need to truncate.    80608

        push    ds                      ;                              80608
        pop     es                      ; ds now copied to es          80608
        mov     di,si                   ; es:di -> string              80608
        mov     fpn_SaveReg,di          ; save in case no more '\'     80608
        call    FindPathEnd             ; es:(di-1) -> last '\' in path80608
        jnc     short settrunc          ; '\' found. set up to trunc.  80608
        mov     di,fpn_SaveReg          ; we were already at file.ext  80608
settrunc:                               ; set up to truncate file.ext  80608
        dec     di                      ; back up to last '\'          80608
        mov     si,di                   ;                              80608
fpntrunc:                               ;                              80608
        or      dl,dl                   ; illegal characters?
.errnz FATNAME
        LJNZ    fpn45                   ; yes, go return
        call    Truncpath               ; ds:si -> WFP
        jnc     fpn2
        jmp     fpn40
fpn2:
                                        ;72417 moved from PTDA to TCB
        mov     bx, CurrTCB             ;
        mov     si,TASKAREA:[bx].TCBWFP_Start ; ds:si -> WFP
                                        ;72417 end

fpn5:   cmp     TASKAREA:[bx].TCBThisFSC.Offst,FSC_LOCAL ; what file system?
ifdef FSSTRICT
        LJA     fpnie2                  ; impossible
endif
        jb      fpn20                   ; fsd, go call fsd worker
;
; FAT FS worker
;
; If we found more than one dot in any component, we return an error as this
; is illegal for the FAT file system
;
; DS:SI -> WFP (canonicalised path)
;

;*****************************************************************
; ptr B715417
;
fpn10:  cmp     ProcType,PT_REALMODE    ; 3xbox?
        jne     fpn15                   ; no

; 3xbox processing already done
        pushf                           ; save flags
        cmp     fpn_MaxNumDots,1        ; More than one dot found in any component?
        ja      fpn45a                  ; Yes. Return error.
        popf                            ; restore flags to original state
        jmp short fpn30                 ; yes, processing already done
fpn45a:
        popf                            ; restore flags to original state
        jmp  fpn45

fpn15:  cmp     byte ptr [si+3],0       ; null path?
;*****************************************************************

        je      fpn30                   ; yes, go return no error
        or      dl,dl                   ; illegal characters?
.errnz FATNAME
        jnz     fpn45                   ; yes, go return
        call    CheckPathSyntax         ; enforce 8.3 rules
        jmp     fpn30

    assume ds:nothing
; DS:SI -> WFP
; bx - CurrTCB

fpn20:  push    ds                      ; push pointer to wfp
        push    si
        les     di,TASKAREA:[bx].TCBThisFSC     ; es:di -> FSC
        call    es:[di].FS_PROCESSNAME
        xor     bx,bx
        cmp     bx,ax                   ; set carry if ax != 0

; after the xxx_ProcessName worker is done, we need to update WFP_Path_End
; because the worker may change the length of WFP (e.g. truncate components
; to 8.3)

fpn30:  jc      fpn40                   ; if error, exit now

ifdef FSSTRICT

; The xxx_ProcessName worker is not supposed to change the length of the
; current directory portion of the name. We do a simple check here to verify
; this.

                                        ;72417 moved from PTDA to TCB
        mov     si,CurrTCB
        mov     si,TASKAREA:[si].TCBCurr_Dir_End ; si = -1 or ss:(si-1) -> '\'
                                        ;72417 end

        inc     si                      ; is Curr_Dir_End set?
        jz      fpn35                   ; no, nothing to check
        cmp     byte ptr ss:[si-2],'\'  ; is ss:Curr_Dir_End-1 -> '\' ?
        jne     fpnie1                  ; no, go panic
fpn35:
endif
        jmp short fpn37

;***    CheckLastComponent - check last component of path for 8.3 compliance

Entry   CheckLastComponent

        EnterProc <cx,bx>,NoCheck       ; Store counts for later use

; re-compute WFP_Path_End

fpn37:  TaskContext <es>,,noassume
                                        ;72417 moved from PTDA to TCB
        mov     di,CurrTCB
        mov     di,TASKAREA:[di].TCBWFP_Start ; es:di -> wfp
        call    FindPathEnd             ; es:(di-1) -> last '\' in path
ifdef FSSTRICT
        jc      fpnie1                  ; can't find end of path, go panic
endif
        mov     bx,CurrTCB
        mov     TASKAREA:[bx].TCBWFP_Path_End,di  ; update WFP_Path_End
                                        ;72417 end

        cmp     TASKAREA:[bx].TCBThisFSC.Offst,FSC_LOCAL ; what file system?
        jb      fpn50
fpn40:  LeaveProc
fpn42:  TimerOff TIM_FS_PROCESSNAME
        ret

fpn45:  stc
        mov     ax,ERROR_INVALID_NAME
        jmp short fpn40

ifdef FSSTRICT
fpnie1: InternalError <FSD_ProcessName: bad path>
fpnie2: InternalError <FSD_ProcessName: bad FSC>
endif

; For FSD processed names we stil have to process the last component.
; if NewFiles is set, then we need not tamper with the name anymore, otherwise
; we have to check the last component for 8.3 FAT conventions

                                        ;72417 moved from PTDA to TCB
fpn50:  SetBP_CurrTCB
        test    [bp].TCBPathNameType,PNType_PSDEV ; pseudo char device?
        UnSetBP_CurrTCB
                                        ;72417 end

        jnz     fpn40                   ; yes, go return

        TaskContext     <ds>            ;
        cmp     byte ptr [di],0
        je      fpn40                   ; Nothing to do if this is there is no last component.

        mov     si,di                   ; ds:si -> last component
        call    NameTrans               ; check the last component alone
        jc      fpn70                   ; non-8.3

                                        ;72417 moved from PTDA to TCB
        SetBP_CurrTCB
        cmp     si,[bp].TCBWFP_Path_End ;
        UnSetBP_CurrTCB
        je      fpn60                   ;
;
; check the last component for non-8.3 chars
;
        mov     di,CurrTCB
        mov     di,TASKAREA:[di].TCBWFP_Path_End  ; es:di -> name
        xor     al,al
        mov     cx,MAXCOMPLEN          ; maximum length of 8.3 name (including metachars)
        repne   scasb                   ; find end of name
        mov     si,CurrTCB
        mov     si,TASKAREA:[si].TCBWFP_Path_End ; ds:si -> name

                                        ;72417 end

        sub     cx,MAXCOMPLEN
        not     cx                      ; cx = length of name (not including NUL)
        push    cx
        call    ChkVal
        pop     cx                      ; cx = length of name
IFDEF FSSTRICT
                                        ;72417 changed to conditional long jump
        LJZ     fpnie3                  ; path contains illegal chars (impossible)
ENDIF
        or      dl,dl                   ; non-8.3 chars?
        jnz     fpn60                   ; yes, go check for newfiles
;
; If NEWFILES is not set, we do not allow more than one dot in the last
; component of the path.
; if (more than one dot) {
;       if (NEWFILES) then set PNType to newfiles
;       else error
; }
; if (blanks before dot) {
;       if (NEWFILES) then flag NewFiles and quit
;       else remove those blanks  (for FAT 8.3 convention)
; }
;
; cx = length of name
;
        cmp     fpn_NumDotsLastComp,1   ; More than one dot in last component?
        ja      fpn60                   ; Yes. Name is invalid if not NEWFILES.

        TaskContext <es>
                                        ;72417 moved from PTDA to TCB
        mov     di,CurrTCB
        mov     di,TASKAREA:[di].TCBWFP_Path_End ; es:di -> last component of name
                                        ;72417 end

        mov     al,'.'                  ; '.' is not a valid DBCS lead or trailing byte
        repne   scasb                   ; look for '.' in name
        clc
                                        ;72417 changed to conditional long jump
        LJNZ    fpn40                   ; not found, go return no error
        dec     di                      ; es:di -> '.' in name
        SaveReg <di>

; following loop is DBCS correct because ' ' is not a valid DBCS lead or
; trailing byte
; DS = ES = TASKAREA
; es:di -> '.' in name

fpn55:  dec     di                      ; back up over ' 's immediately preceding dot
        cmp     byte ptr [di],' '       ; entire name can't be ' 's, so don't worry
        jz      fpn55                   ; about backing up past beginning of name
        inc     di                      ; es:di -> first ' '
        RestoreReg <si>                 ; ds:si -> '.' in name
        cmp     di,si                   ; were there any blanks ?
        LJE     fpn40                   ; no
;
; if there are blanks before dot, if NEWFILES set (and FSD, of course)
; then we have to flag this name as a NEWFile name and we should not
; truncate those blanks; else, we have to remove those blanks.
;
        mov     bx,CurrTCB
        test    TASKAREA:[bx].TCBNewFlags,TCBNEWFILES ; newfiles?
        jnz     fpn80                   ; yes, go set newfile bit in PathNameType

        SaveReg <di>
        mov     cx,-1
        mov     al,0
        repne scasb                     ; look for end of string
        not     cx                      ; cx = length of extension + '.'
        RestoreReg <di>
        rep     movsb                   ; copy extension
        clc
        jmp     fpn40                   ; go return no carry


fpn60:  mov     ax,ERROR_INVALID_NAME   ; set up error code
fpn70:                                  ; error already set
    SegAssert SS,ds,<FSD_ProcessName>
        mov     bx,CurrTCB
        test    TASKAREA:[bx].TCBNewFlags,TCBNEWFILES ; if NEWFILES
        jnz     fpn80                     ; go set newfile bit in PathNameType
        stc
        jmp     fpn40

                                        ;72417 moved from PTDA to TCB
fpn80:  SetBP_CurrTCB
        or      [bp].TCBPathNameType,PNType_NEWFILE ; remember non-8.3 name
        UnSetBP_CurrTCB
                                        ;72471 end
        jmp     fpn40                   ; carry clear

IFDEF FSSTRICT
fpnie3: InternalError <FSD_ProcessName: name contains illegal chars>
ENDIF

EndProc FSD_ProcessName,NoCheck


;**     Truncpath - truncate path components to 8.3
;
;       If last component contains meta character, that component is not
;       processed.
;
;       ENTRY   ds:si -> WFP to process
;               cMeta      = -1 if path contains no meta
;                         != -1 if last component of path contains meta
;
;       EXIT    if 'NC' - success
;                 path modified in place
;               if 'C' - error (component contains two '.')
;                 ax = ERROR_PATH_NOT_FOUND
;
;       USES    ax,bx,cx,si,di,es,flags

Procedure Truncpath,near,local
    assume ds:nothing,es:nothing,ss:TASKAREA

LocalVar trp_pWFP,WORD                  ; offset to WFP

        TaskContext <es>,,noassume
        mov     di,si                   ; es:di -> result buffer
                                        ;72417 moved from PTDA to TCB
        SetBP_CurrTCB
        cmp     [bp].TCBcMeta,-1        ; meta in last component?
        UnSetBP_CurrTCB
                                        ;72417 end
        jne     trp10                   ; yes, don't process last component
        call    Trunc8dot3
        jnc     trp5                    ; no error, go return
trp1:   cmp     ax,ERROR_INVALID_NAME   ;
        stc
        je      trp5                    ;
; we got ERROR_PATH_NOT_FOUND or some other error;
; figure out whether invalid component is last component.  if so, return
; ERROR_FILE_NOT_FOUND.  otherwise return ERROR_PATH_NOT_FOUND.
;
trp2:   mov     al,[si]
        JPathChar <trp3>
        or      al,al
        jz      trp3
        inc     si          ; pathptr++
        TestDBCS <trp2>     ; if (DBCS)
        jnz     trp2        ;   pathptr++
        inc     si
        jmp short trp2

trp3:   or      al,al       ; last component?
        mov     ax,ERROR_PATH_NOT_FOUND ; assume no
        stc
        jnz     trp5        ; no, go return path error
        mov     ax,ERROR_FILE_NOT_FOUND ; yes, return file error
trp5:   ret

; don't process last component: pass all but the last component to
; Trunc8dot3, then append the last component to the processed path

trp10:  EnterProc <si>                  ; pWFP = si
        mov     ax,ds
        mov     es,ax
        mov     di,si                   ; es:di -> WFP
        call    FindPathEnd             ; es:(di-1) -> last '\'
        jc      trpie1                  ; no '\', go panic
        mov     byte ptr es:[di-1],0    ; fake end of WFP at last '\'
        mov     si,trp_pWFP             ; ds:si -> WFP
        mov     di,si                   ; es:di -> result buffer
        call    Trunc8dot3              ; process path without last component
        jc      trp20                   ; error, go error exit

; now append unprocessed last component to process path
; es:(di-1) -> end of processed path
; ds:(si-1) -> beginning of last component

        mov     byte ptr es:[di-1],'\'  ; put back last '\'
        push    di
        call    NameTrans               ; process last component
        pop     di                      ;
        jc      trp20                   ; error, go return (AX) = error
        TaskContext <es,ds>, si         ; (ES:DI) -> beginning of last component in dest
        mov     si,[CurrTCB]
        cmp     TASKAREA:[si].TCBThisFSC.Offst,FSC_LOCAL ; what file system?
                                        ;72417 moved from PTDA to TCB
        lea     si,TASKAREA:[si].TCBName1 ; (DS:SI) -> beginning of last component in source
                                        ;72417 end
        je      trp15                   ; FAT - everything is OK

        cmp     byte ptr [si],05h       ; Should the entry really be 0E5h?
        jne     trp15                   ; no, do nothing special

        mov     byte ptr [si],0E5h      ; correct the character
trp15:  call    FStrCpy                 ; append last component
        clc                             ; no error

trp20:  LeaveProc
        ret

trpie1: InternalError <Truncpath: no \>

EndProc Truncpath


;**     FindPathEnd - locate last path separator in a path
;
;       This is used to update WFP_Path_End. The path separator must be '\'.
;
;       ENTRY   es:di -> DBCSZ WFP
;               CLD
;
;       EXIT    if 'NC' - success
;                 es:(di-1) -> last '\' in given path
;               if 'C' - failed
;                 can't find any '\' at all
;
;       USES    ax, cx, si, di, flags

Procedure FindPathEnd,<near,faronly,far16>
    assume ds:nothing,es:nothing,ss:TASKAREA
        CALLFAR StrLen                  ; cx = length of WFP + null
        dec     cx                      ; cx = length of WFP
        mov     al,'\'                  ; al = path separator
        xor     si,si                   ; si = pointer to last path separator

; al = path separator
; es:di -> where to start search
; es:si -> last '\' found
; cx = bytes remainding in string to search

fpe10:  CALLFAR FindChar                ; es:di -> path separator
        jnz     fpe20                   ; can't find any more, exit
        mov     si,di                   ; save pointer to path separator
        jmp     short fpe10             ; again

fpe20:  mov     di,si                   ; es:di -> last '\'
        cmp     di,1                    ; set carry if di == 0
        ret
EndProc FindPathEnd


;***    Trunc8dot3 - truncate all path components to 8.3 format
;
;       Returns a path that has all components truncated to 8.3 format.
;       DBCS character will be truncated whole (may result in 7.2 instead
;       of 8.3). Trailing '.' is removed (i.e. D:\ABC. becomes D:\ABC).
;
;       ENTRY   ds:si -> ASCIIZ well formed path to process
;               es:di -> result buffer. If ds == es, di must be <= si or >
;                        si+WFP length
;               CLD
;
;       EXIT    if 'NC' - success
;                 result buf has path with components truncated to 8.3
;                 es:(di-1) -> string terminator in destination
;                 ds:(si-1) -> string terminator in source
;               if 'C' - error (a component contains two '.')
;                 ds:(si-1) -> second '.' or first char of name (.)
;                 ax = ERROR_PATH_NOT_FOUND
;
;       USES    ax,bx,si,di,flags
;
;       ALGORITHM
;
;       A state machine is used to transform the path.
;
;       state      char      action     next
;       -----      -----     ------     ----
;       0-6,9-10   DBCS      copy 2     state+2
;       7-8        DBCS      skip 2     8
;       11-12      DBCS      skip 2     12
;       9            0       special    exit
;       0-8,10-12    0       copy       exit
;       0-12         \       copy       0
;       0-8          .       copy       9
;       0,9-12       .       error      exit
;       0-7,9-11   others    copy       state+1
;       8,12       others    skip       same
;
;       copy 2 = copy two bytes (one DBCS char)
;       skip 2 = skip two bytes (one DBCS char)
;       special = backup destination pointer and overwrite '.' with null
;
;       The state number is encoded as 2^state to allow multiple state
;       comparisons with one instruction.
;
;       Example:
;
;       Current-               11111     1111
;        State  012012345678889012220123902220
;       Input   D:\ABCDEFGHIJ.LMNOP\ABC.D.FG\A
;       Output  D:\ABCDEFGH  .LMN  \ABC.D   \A  = D:\ABCDEFGH.LMN\ABC.D\A

Procedure Trunc8dot3,near
    assume ds:nothing,es:nothing,ss:TASKAREA

TRU_S0  =       1                       ; pattern for state 0
TRU_S7  =       1 shl 7                 ; pattern for state 7
TRU_S8  =       1 shl 8                 ; pattern for state 8
TRU_S9  =       1 shl 9                 ; pattern for state 9
TRU_S10 =       1 shl 10                ; pattern for state 10
TRU_S11 =       1 shl 11                ; pattern for state 11
TRU_S12 =       1 shl 12                ; pattern for state 12

        TimerOn TIM_FS_TRUNC8DOT3
tru10:  mov     bx,TRU_S0               ; state = 0
tru20:  lodsb                           ; al = char, si++
        TestDBCS <tru25>        ; DBCS?
        jz      tru70                   ; yes, go process DBCS
tru25:
        or      al,al                   ; end of string ?
        jz      tru90                   ; yes, go process end of string
        cmp     al,'\'                  ; current char = '\' ?
        je      tru40                   ; yes, go process '\'
        cmp     al,'.'                  ; current char = '.' ?
        je      tru50                   ; yes, go process '.'

        test    bx,TRU_S8 or TRU_S12    ; is state = 8 or 12 ?
        jnz     tru20                   ; yes, skip current char

        stosb                           ; copy one char
tru30:  shl     bx,1                    ; next state
        jmp     short tru20

tru40:  stosb                           ; copy '\'
        jmp     short tru10             ; start at state 0 again

tru50:  cmp     bx,TRU_S0               ; is state == 0?
        je      tru55                   ; yes, error
        test    bx,TRU_S9 - 1           ; is state == 1 - 8 ?
        jnz     tru60                   ; yes
; found second '.';
        mov ax,ERROR_INVALID_NAME       ;
        jmp short tru56
; name starts with '.', error exit

tru55:  mov     ax,ERROR_PATH_NOT_FOUND
tru56:  stc
        jmp     short tru120            ; Return the error

; '.' found; remove the preceding blanks, if any, and store the '.'
; DBCS correct since ' ' cannot be the second char

tru60:  cmp     byte ptr es:[di-1],' '  ; was the last char blank ?
        jne     tru65                   ; no blank; go store '.'
        dec     di                      ; remove blank
        jmp     short tru60             ;
tru65:  stosb
        mov     bx,TRU_S9               ; next state = 9
        jmp     short tru20

; Process DBCS char

tru70:  dec     si                      ; ds:si -> DBCS lead byte
        lodsw                           ; ax = DBCS char

        test    bx,(TRU_S7-1) or TRU_S9 or TRU_S10 ; if state = 0-6,9-10
        jnz     tru80                   ; then goto tru80

; if state == 7 or 8, then set next state to 8
; if state == 11 or 12, then set next state to 12

        add     bx,TRU_S7 or TRU_S11    ; set next state to 8 or 12
        and     bx,TRU_S8 or TRU_S12    ; mask out unwanted bits
        jmp     short tru20

tru80:  stosw                           ; copy DBCS char
        shl     bx,1
        jmp     short tru30             ; next state += 2

; Process end of string. If last char copied is '.', delete it

tru90:  test    bx,TRU_S9               ; is state == 9 ? (clears carry)
        jz      tru100                  ; no
        dec     di                      ; remove trailing '.'

; end of string; remove the trailing  blanks, if any.
; DBCS correct        since ' ' cannot be the second char

tru100: cmp   byte ptr es:[di-1],' '  ; was the last char blank ?
        jne     tru110                ; no blank; go store '.'
        dec     di                    ; remove blank
        jmp     short tru100          ;

;
; The previous CMP should have cleared carry.
;
tru110: stosb                           ; copy string terminator
;
; Carry flag indicateserror or success.
;
tru120: TimerOff TIM_FS_TRUNC8DOT3
        ret

EndProc Trunc8dot3

;@PAA04757A Begins
;
; Subroutine Name:      IsRiplDrive
;
; Function:     Find out whether the subject drive letter is a RIPL drive.
;
;           ENTRY: AL is Drive Number, A: => 1
;           EXIT : CY set if RIPL
;           USES : Nothing
;

Procedure IsRiplDrive,near,local

        push      bx
        push      ds
        DosContext   ds
        cmp       ds:NumIORipl,0
        je        IRD_NoRipl

        mov       bx,ds:[SISData].SIS_BootDrv  ; If this is a Ripl, our boot
                                               ; drive is our first Ripl drive

        cmp       al, bl                     ; Is ax below 1st Ripl Drive?
        jl        IRD_NoRipl                 ; Yes, not a Ripl drive
        add       bl, ds:NumIORipl           ; Add Number of Ripl Drives
        cmp       al, bl                     ; Is ax outside the range of Ripl?
        jge       IRD_NoRipl                 ; Yes, not a Ripl drive

        stc                                  ; Ax is in the Ripl range, set carry
        jmp short IRD_out

IRD_NoRipl:
        clc

IRD_out:
        pop       ds
        pop       bx
        ret

EndProc IsRiplDrive
;@PAA04757A Ends



;@99999 Code to support reassigning local drive letters
Break <QueryDriveLetter - Query Drive Letter >

;***    QueryDriveLetter - Query Drive Letter
;
;   ENTRY   AX is drive number  (0=A,1=B,2=C...)
;
;   EXIT    Carry Clear
;           BX = 0 for Local drive
;                1 for Remote drive
;           Carry Set
;               Drive Letter is not allocated
;
;   USES
;

Procedure QueryDriveLetter,<near,faronly,far16>
    ASSUME  DS:NOTHING,ES:NOTHING,SS:TASKAREA
        PMONLY

     SaveReg <ds,si>

     DosContext DS
     cmp       ax,25          ;Q: Is it greater than 25?
     ja        QDL_RetSTC     ; Y: Return with carry

     mov       si,ax
     test      DriveLetter[si],DLF_Allocated
                              ;Q: Is it allocated?
     jz        QDL_RetSTC     ; N: Return with carry

     xor       bx,bx
     test      DriveLetter[si],DLF_Remote
                              ;Q: Is this a Remote drive?
     jz        QDL_RetCLC     ; N: Return Local Drive
     mov       bx,1           ; Y: Return Remote drive

QDL_RetCLC:
     clc

QDL_Ret:
     RestoreReg <si,ds>
     ret

QDL_RetSTC:
     stc
     jmp  short QDL_Ret

EndProc QueryDriveLetter

Break <AllocateDriveLetter - Allocate Drive Letter >

;***    AllocateDriveLetter - Allocate Drive Letter
;
;   ENTRY   AL is drive number  (0=A,1=B,2=C...)
;           BX = 0 for Local drive
;                1 for Remote drive
;
;   EXIT    Carry Set
;               Drive Letter is not available
;           Carry Clear
;               Drive Letter is now allocated
;
;   USES
;
DLF_Allocated  equ  1
DLF_Local      equ  0
DLF_Remote     equ  2

Procedure AllocateDriveLetter,<near,faronly,far16>
    ASSUME  DS:NOTHING,ES:NOTHING,SS:TASKAREA
        PMONLY

     SaveReg <ds,si>

     DosContext DS
     cmp       al,25          ;Q: Is it greater than 25?
     ja        ADL_RetSTC     ; Y: Return with carry
     .386p
     movzx     si,al
     test      DriveLetter[si],DLF_Allocated
                              ;Q: Is it allocated?
     jnz       ADL_RetSTC     ; Y: Return with carry

     or        DriveLetter[si],DLF_Allocated
     cmp       bx,1           ;Q: Is this a Remote drive?
     jne       ADL_RetCLC     ; Y: Return OK
     or        DriveLetter[si],DLF_Remote

ADL_RetCLC:
     clc

ADL_Ret:
     RestoreReg <si,ds>
     ret

ADL_RetSTC:
     stc
     jmp  short ADL_Ret

EndProc AllocateDriveLetter


Break <FreeDriveLetter - Free Drive Letter >

;***    FreeDriveLetter - Free Drive Letter
;
;   ENTRY   AL is drive number  (0=A,1=B,2=C...)
;
;   EXIT    Carry Set
;               Drive Letter is not available
;           Carry Clear
;               Drive Letter is freed
;
;   USES
;
Procedure FreeDriveLetter,<near,faronly,far16>
    ASSUME  DS:NOTHING,ES:NOTHING,SS:TASKAREA
        PMONLY

     SaveReg <ds,si>

     DosContext DS

     cmp       al,25          ;Q: Is it greater than 25?
     ja        FDL_RetSTC     ; Y: Return with carry
     .386p
     movzx     si,al
     mov       DriveLetter[si],0

FDL_RetCLC:
     clc

FDL_Ret:
     RestoreReg <si,ds>
     ret

FDL_RetSTC:
     stc
     jmp  short FDL_Ret

EndProc FreeDriveLetter

;@99999 end new code for reassigning local drive letters





HIGH2CODE ENDS

        FARCODE INIT
INITCODE segment

Break   <StrTransPathChar - translate / => \ in a path string>

;***    StrTransPathChar - translate all occurrences in a string of the evil
;       path separator character (/) to the good path separator character (\).
;***    StrTransPathCharR3 - same, but for ring 3 caller
;
;       ENTRY   (DS:SI) -> ASCIIZ target string
;       EXIT    / translated to \ whenever it occurs in target string
;       USES    AX, SI, Flags
;
;       NOTE: this routine is not DBCS aware, because it is intended to be
;               called early in initialization, when DBCS is not initialized.
;               It is intended to be used on the LIBPATH string.

Procedure StrTransPathChar
    ASSUME DS:NOTHING, ES:NOTHING, SS:TASKAREA
        mov     ah,'/'                  ; will save one clock per compare
        jmp     short pct2

pct1:   mov     byte ptr ds:[si-1],'\'
pct2:   lodsb                           ; fetch a character
        cmp     al,ah                   ; is character / ?
        je      pct1                    ; yes, change to \
        or      al,al                   ; see if at end of string
        jnz     pct2                    ; exit if at end of string
        ret
EndProc StrTransPathChar


INITCODE ends

        FARCODE INITR3
INITR3CODE segment


Procedure StrTransPathCharR3
    ASSUME DS:NOTHING, ES:NOTHING
        mov     ah,'/'                  ; will save one clock per compare
        jmp     short pcr2

pcr1:   mov     byte ptr ds:[si-1],'\'
pcr2:   lodsb                           ; fetch a character
        cmp     al,ah                   ; is character / ?
        je      pcr1                    ; yes, change to \
        or      al,al                   ; see if at end of string
        jnz     pcr2                    ; exit if at end of string
        ret
EndProc StrTransPathCharR3

INITR3CODE ends


CpuMode 386

        FARCODE HIGH32
    HIGH32CODE segment

;***    _PathCharTrans - translate / => \ in a path string
;
;       This routine is DBCS-aware and C-callable.
;
;       ENTRY:  PUSH    @pszTarget              (target string)
;
;       EXIT:   / translated to \ whenever it occurs in target string
;
;       USES:   eax,flags
;
Procedure _PathCharTrans
    ASSUME DS:FLAT, ES:FLAT, FS:NOTHING, GS:NOTHING, SS:NOTHING

ArgVar  _pct_string,DWORD               ; flat pointer to string

        EnterProc
        SaveReg <esi>
        mov     esi,_pct_string         ; (flat:)esi -> dest string

        TestDBCSProcess                 ; dbcs process?
        jz      _pct60                  ; yes, use slower dbcs version
;
; Process is not DBCS-active, use quick non-DBCS-aware translation.
;
        jmp     short _pct20

_pct10: mov     byte ptr [esi-1],'\'
_pct20: lodsb                           ; fetch a character
        cmp     al,'/'                  ; is character / ?
        je      _pct10                  ; yes, change to \
        or      al,al                   ; at end of string?
        jnz     _pct20                  ; no, check next char
        jmp     short _pctx             ; EOS, exit
;
; Process is DBCS active, use DBCS-aware version of translation algorithm.
;
_pct50: mov     byte ptr [esi-1],'\'

_pct60: lodsb                           ; fetch a character
        or      al,al                   ; end of string?
        jz      _pctx                   ; yes, exit
        TestDBCSLeadByte                ; is char a DBCS lead byte?
        jnz     _pct70                  ; not dbcs lead, do regular test on char
        inc     esi                     ; skip DBCS trail byte
        jmp     short _pct60            ; check next char
_pct70: cmp     al,'/'                  ; is char the evil path seperator?
        jnz     _pct60                  ; no, check next char
        jmp     short _pct50            ; yes, translate and continue

_pctx:  RestoreReg <esi>
        LeaveProc
        RETC

EndProc _PathCharTrans

HIGH32CODE ENDS
        END
