;       SCCSID = @(#)joinsbst.asm       6.4 91/07/11
;      SCCSID = @(#)joinsbst.asm        6.4 91/07/11
PAGE ,132
TITLE   JOINSBST - Join and Subst workers and support routines
NAME    JOINSBST

;       Global          Local
;
;       w_JSA
;                       w_Join
;                       w_Subst
;                       ListCDSText
;                       DriveBusy
;                       PathErr
;                       CheckDirPath
;
;   MODIFICATION HISTORY:
;    - 11/27/95 - MLM defect 72417
;                 moved from PTDA to TCB: DTAddr, Joins

.xlist
.xcref
include dosseg.inc

include basemaca.inc
include osmaca.inc
include oldmaca.inc
include dossymc.inc
include argframe.inc
include curdir.inc
include dbcs.inc
include dpb.inc
include dirent.inc
include error.inc
include filesys.inc
include jsa.inc
include mi.inc
include tkexport.inc
                                      ;72417 moved find.inc after tkexport.inc
include find.inc
.cref
.list

.sall

        ModName joinsbst

        ReferGlobal     NumIO, BYTE     ; Number of drives in system

                                        ;72417 removed PTDA entries now
                                        ;      in TCB

                                        ;   current process
        ReferTask       CDS_Handle,WORD         ; handles of current directories
        ReferTask       CurrTCB,WORD            ; Current thread control block ;### PTR B720732
        ReferTask       LCurDrv,BYTE
        EXTRNFAR        DStrLen
        EXTRNFAR        FindChar
        EXTRNFAR        StrLen
        EXTRNFAR        XCHGP
        EXTRNFAR        w_3xFindFirst
        EXTRNFAR        w_3xFindNext
        EXTRNFAR        w_3xFind_Close
        EXTRNFAR        QueryDriveLetter    ;@99999

        EXTRN           AsgCheckDrive:NEAR
        EXTRN           AddCDS:NEAR
        EXTRN           DelCDS:NEAR
        EXTRN           Find_DPB:NEAR
        EXTRN           GetCDS:NEAR
        EXTRN           NextCDS:NEAR
        EXTRN           SuBuffz:NEAR
        EXTRN           TransPath:NEAR
        EXTRN           FStrCpy:NEAR
        EXTRN           PathChrCmp:NEAR
        EXTRN           $TJMP:NEAR
        EXTRN           ExmDBCS:NEAR




BUSY_DRIVE      EQU     ASG_CURDIR OR ASG_CURDRIVE  ; Mask for busy drive
NULLCDSID       EQU     0FFFFh  ; Used as Net_ID in CDS'


        FARCODE HIGH2
HIGH2CODE SEGMENT

;
; ATTR_ANY - bit mask to find any type of file in a directory
; srchstr - string to match any file in a directory
;

ATTR_ANY        EQU     attr_hidden OR attr_system OR attr_directory
srchstr DB              "\*",0

BREAK <w_JSA - join/subst worker routine>

;***    w_JSA - join/subst worker routine
;
;       This entry point is used by the int 21 interface for join/subst.
;       It determines which call is being performed, sets up the appropriate
;       arguments and calls w_Join or w_Subst.
;
;       ENTRY:  bp = JSA_MAGIC
;               si = JSA_JOIN, JSA_SUBST
;               es:di, al, bx, cx = set up to arguments
;
;       EXIT:   ax = error code (0 if no error)
;
;       See workers for other exit conditions.
;
;       USES:   ALL
;
        ASSUME  DS:NOTHING, ES:NOTHING, SS:NOTHING

Procedure w_JSA,<far16>
        cmp     bp,JSA_MAGIC
        jne     short jsa20             ; jump if bp != magic value
;
; Pseudo $Join entry point
;
        cmp     si,JSA_JOIN
        jne     short jsa10
        call    w_Join
        jmp     short jsax
;
; Pseudo $Subst entry point
;
jsa10:  cmp     si,JSA_SUBST
        jne     short jsa20
        call    w_Subst
        jmp     short jsax
;
; If we got here, either none of the pseudo entry points were taken
; or the magic value was not in ds when we entered. In either case
; clear ax and return.
;
jsa20:  xor     ax,ax
jsax:   RETD
EndProc   w_JSA

BREAK <w_Join - Worker for Join command>

;***    w_Join - worker for Join command
;
;       w_Join is the worker routine for the DOSJOIN API.
;       The result of a drive being joined (to a directory)
;       is that any references to the directory will actually refer
;       to the drive. The drive letter itself becomes invalid.
;       'Joins' is the only global which is affected. It is
;       incremented for each join which is added and decremented
;       for each one that is deleted.
;
;       w_Join performs three functions. These are;
;               1) List the drives that are currently joined
;               2) Join a drive
;               3) Delete a join which is already in effect.
;
;       w_Join make use of the special CDS' whose handles are in
;       the range one to 26. These CDS' are especially used by the
;       DOS to support name aliasing.
;
;
;       ENTRY:
;               al      Sub-function code
;                       0 - list joins
;                       1 - add join
;                       2 - delete join
;
;               bx      0-based drive designator
;               es:di   pointer to path string/buffer
;               cx      length of path string/buffer
;
;       EXIT:
;               Carry Clear (no error)
;                       ax = 0
;                       es:di may contain path string if ax = 0 on entry.
;               Carry Set (error)
;                       ax = error code
;
;       USES:
;               All but bp
;

        ASSUME          DS:NOTHING, ES:NOTHING, SS:TASKAREA
Procedure w_Join,near,local
LocalVar        drivelet, 3             ; Drive letter string
LocalVar        path, DWORD             ; Pointer to path
LocalVar        drive, WORD             ; Drive to be (un)joined
LocalVar        cds, <SIZE curdir>      ; Buffer for CDS
LocalVar        wj_renbuf,PMPATHLEN     ; temp buffer
LocalVar        dummy,WORD              ; KLUDGE: see gshare.asm
LocalVar        dummycds,<SIZE curdir>  ; Buffer for CDS

        EnterProc

        lea     dx,dummycds
        push    bx                                      ;### PTR B720732
        mov     bx,TASKAREA:[CurrTCB]                   ;### PTR B720732
        mov     TASKAREA:[bx].TCBpTmpCDS.Offst,dx       ; Initialize TCBpTmpCDS ;### PTR B720732
;       mov     TASKAREA:[bx].TCBpTmpCDS.Segmt,ss       ; Initialize TCBpTmpCDS ;### PTR B720732
;### PTR B720732        mov     TASKAREA:[ptda_pCDS],dx ; Initialize ptda_pCDS
        pop     bx                                      ;### PTR B720732
        mov     drive, bx               ; Save 0-based drive number
        call    $TJMP                   ; Jump to appropriate sub-function
;
; Jump table
;
        DB      (wjnTE - wjnTS) / 2     ; Number of sub-functions
        DW      DOSHIGH2CODE:wjn109     ; Invalid sub-function

wjnTS:  DW      DOSHIGH2CODE:wjnList    ; List joins
        DW      DOSHIGH2CODE:wjnAdd     ; Add join
        DW      DOSHIGH2CODE:wjnDelete  ; Delete join
wjnTE   LABEL   WORD

;
; List joins.
; If a CDS exists for the given 0-based drive number, check
; to see if it is joined. If it is copy the CDS text field
; back to the user's buffer.
;

wjnList:mov     ax, drive               ; 0-based drive number
        mov     bl, CD_JOIN             ; Only list joined drives
        call    ListCDSText
        jmp     wjn99

;
; Add join.
; Add a CDS for the given drive to the CDS RMP segment. This CDS will
; have a handle equal to the one based drive number which it represents.
; Before the CDS is added it is necessary to determine that the drive
; and the path to which it is joined are in a 'useable' state. For the
; drive 'useable' means that it is not already joined or substed and
; that it is not being used. The path is useable so long as it is an
; empty, sub-directory of the root and not on the drive that is being
; joined.
;

wjnAdd: mov     path.Segmt, es          ; Save pointer to path
        mov     path.Offst, di
        inc     drive
        mov     bx, drive               ; 1-based drive number
;
; Check that drive is not busy.
;
        call    DriveBusy
        LJC     wjn102                  ; Error if drive is busy

        mov     ax, drive               ; 1-based drive number
;
; Check that the drive is not already joined or substed.
;
        call    GetCDS                  ; es:di -> CDS
        jc      short wjn20             ; Jump if no CDS for drive
        test    es:[di.cd_flags], CD_JOIN
        LJNZ    wjn105                  ; Error if drive is already joined
        test    es:[di.cd_flags], CD_LOCAL
        LJNZ    wjn103                  ; Error if drive is substed
;
; Check that the drive exists
;

wjn20:  DosContext      <es>
        SaveReg         <ax>            ; @99999
        mov     ax,drive
        mov     ah,0                    ; Zero out AH
        CALLFAR QueryDriveLetter
        RestoreReg      <ax>
        LJC     wjn100                  ; Error if drive is invalid
        cmp     bx,0
        LJA     wjn100                  ; Error if network drive
        mov     bx, drive
;
; Check through the other CDS' to determine if the drive is
; being used anywhere else.
;
        mov     drivelet, bl            ; Build asciiz drivelet (ex. A:)
        add     drivelet, 'A' - 1
        mov     WORD PTR drivelet+1, 0 SHL 8 + ':'

        TaskContext     <ds>
        lea     si, drivelet            ; ds:si -> asciiz drivelet
        xor     di, di                  ; es:di -> no CDS (first time)
        call    PathErr                 ; es:di -> CDS
        ASSUME  es:NOTHING
        jnc     short wjn21
        cmp     es:[di.cd_handle], NUMDRIVE
        LJA     wjn102                                  ; Error if drive is busy

        mov     si, WORD PTR es:[di.cd_flags]
        and     si, CD_INUSE OR CD_LOCAL OR CD_JOIN     ; Mask out uninteresting
                                                        ;  flags
        cmp     si, CD_INUSE OR CD_LOCAL
        LJE     wjn109_1                                ; Error if drive is the
                                                        ;  target of a subst
        cmp     si, CD_INUSE OR CD_JOIN
        LJE     wjn109_2                                ; Error if drive is the
                                                        ;  target of a join
        jmp     wjn110                                  ; Error if flags is in
                                                        ;  inconsistent state
;
; Check that the directory path exists and is valid.
; Start by converting to a WFP ("well formed path").
;

wjn21:  lds     si, path                ; ds:si -> path
        mov     al, [si]                ; al = drive letter of path
        xor     ah, ah                  ; ax = drive letter of path
    ASSUME  ds:NOTHING
        lea     di, wj_renbuf
        SaveReg         <bp, ax>
        call    TransPath               ; Convert path to WFP
        RestoreReg      <ax, bp>
        LJC     wjn104                  ; Error if path not found
        lea     si, wj_renbuf           ; ds:si -> wj_renbuf
        mov     path.Segmt, ds          ; Save pointer to WFP
        mov     path.Offst, si

        sub     al, 'A' - 1             ; ax = 1-based drive letter of directory
;
; Check that the drive that the directory is on is neither joined
; nor substed. Note that the drive before and after TransPath will be different
; if the user supplied a path like "h:\abc" where h: is a subst disk.
;
        call    GetCDS                  ; es:di -> CDS
        jc      short wjn22             ; Jump if no CDS for drive
        test    es:[di.cd_flags], CD_LOCAL
        LJNZ    wjn106                  ; Error if drive is substed
        test    es:[di.cd_flags], CD_JOIN
        LJNZ    wjn108                  ; Error if drive is joined
;
; Check that the directory is not on the drive that is being joined.
;

wjn22:  cmp     ax, drive
        LJE     wjn107                  ; Error if drives are the same

; Check that the directory is valid (see CheckDirPath for a description
; of valid).
;
        lds     si, path                ; ds:si -> wj_renbuf
        call    CheckDirPath
        LJC     wjn200                  ; Error if directory does not check out
;
; All is fine...
; Initialize the CDS to be added.
;
        TaskContext     <ds>
        lea     si, cds                 ; ds:si -> cds
;
; Drive is busy and joined
;
        mov     [si.cd_flags], CD_INUSE OR CD_JOIN
        mov     [si.cd_fsi].cdi_end,CDI_END_ROOT ; Point in text where '. and '..'
                                                 ; stop making sense
        mov     [si.cd_fsd.cddFAT_id],NULLCDSID  ; NULL ID

; Make es:di -> cdi_text
; Make ds:si -> directory string
; Copy directory string to cdi_text

        SaveReg         <ds, si>
        SaveReg         <ds>
        RestoreReg      <es>
        lea     di, [si.cd_fsi].cdi_text    ; es:di -> cdi_text
        lds     si, path                    ; ds:si -> wfp in wj_renbuf
        ASSUME  ds:NOTHING
        CALLFAR DStrLen                     ; cx = length of wfp
        SaveReg         <cx>
        mov     bx,es
        call    SuBuffz
        LJC     wjn200                      ; Error if GP fault
        RestoreReg      <cx>

        mov     ax, drive
        dec     ax                          ; ax = 0-based drive number
        call    Find_DPB                    ; ds:si -> DPB
        RestoreReg      <di, es>            ; es:di -> CDS
        LJC     wjn111                      ; Error if DPB not found

        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
;
; Add the CDS to the CDS RMP segment
;
        TaskContext     <ds>
        lea     si, cds                         ; ds:si -> CDS to add
        add     cx, (cd_fsi+cdi_text)           ; length of CDS record
        mov     ax, drive                       ; ax = handle
        call    AddCDS
        LJC     wjn112                          ; Error if adding CDS failed
                                                ;72417 moved from PTDA to TCB
        SetBP_CurrTCB
        inc     [bp].TCBJoins                   ; Tell the rest of DOS that an
        UnSetBP_CurrTCB
                                                ;72417 end
                                                ;  additional join is in effect
        jmp     wjn99


;*      Delete join.
;
;       Delete the CDS associated with the joined drive. In order to do
;       this the drive has to be joined and no current directory can be
;       located on that drive

wjnDelete:
        inc     drive
        mov     bx, drive               ; 1-based drive number
;
; Check that the drive is not busy.
;
        call    DriveBusy
        jc      short wjn102            ; Error if drive is busy

        mov     ax, drive               ; 1-based drive number
;
; Make sure that the drive is joined and that it is
; not substed.
;
        call    GetCDS                  ; es:di -> CDS
        jc      short wjn101            ; Error if no CDS for drive
        test    es:[di.cd_flags], CD_LOCAL
        LJNZ    wjn103                  ; Error if drive is substed
        test    es:[di.cd_flags], CD_JOIN
        LJZ     wjn101                  ; Error if drive is not joined
;
; Check that the join is not currently being used.
;
        CALLFAR XCHGP                       ; ds:si -> CDS
        lea     si, [si.cd_fsi].cdi_text    ; ds:si -> cdi_text
        DosContext      <es>
        xor     di, di                      ; es:di -> no CDS (first time)

wjn31:  SaveReg         <ds, si>
        call    PathErr                     ; es:di -> CDS
        ASSUME  es:NOTHING
        RestoreReg      <si, ds>
        jnc     short wjn32                 ; No more CDS' to check

        mov     bx, drive
        cmp     es:[di.cd_handle], bx
        je      short wjn31                 ; Continue if CDS handle is the drive
        cmp     es:[di.cd_handle], NUMDRIVE
        jbe     short wjn103                ; Error if CDS handle represents a drive
        jmp     short wjn102                ; Error if drive is busy
;
; All is fine...
; Remove the CDS from the CDS RMP segment
;

wjn32:  mov     ax, drive
        xor     bx, bx
        mov     ds, bx                      ; Invalidate ds
        call    DelCDS
    ASSUME  DS:NOTHING,ES:NOTHING
                                            ;72417 moved from PTDA to TCB
        SetBP_CurrTCB
        dec     [bp].TCBJoins               ; Tell the rest of DOS that one less
        UnSetBP_CurrTCB
                                            ;72417 end
                                            ;  Join is in effect
;
; Exit wth no error.
;

wjn99:  xor     ax, ax
;### PTR B720732 wjnx:  mov     ss:[ptda_pCDS],0            ; Clear ptda_pCDS
wjnx:   LeaveProc
        ret
;
; Following are all the errors that can be generated by w_Join.
;

wjn100: mov     ax, ERROR_INVALID_DRIVE
        jmp     SHORT wjn200

wjn101: mov     ax, ERROR_NOT_JOINED
        jmp     SHORT wjn200

wjn102: mov     ax, ERROR_BUSY_DRIVE
        jmp     SHORT wjn200

wjn103: mov     ax, ERROR_IS_SUBSTED
        jmp     SHORT wjn200

wjn104: mov     ax, ERROR_PATH_NOT_FOUND
        jmp     SHORT wjn200

wjn105: mov     ax, ERROR_IS_JOINED
        jmp     SHORT wjn200

wjn106: mov     ax, ERROR_JOIN_TO_SUBST
        jmp     SHORT wjn200

wjn107: mov     ax, ERROR_SAME_DRIVE
        jmp     SHORT wjn200

wjn108: mov     ax, ERROR_JOIN_TO_JOIN
        jmp     SHORT wjn200

wjn109: mov     ax, ERROR_INVALID_FUNCTION
        jmp     SHORT wjn200

wjn109_1:
        mov     ax, ERROR_IS_SUBST_TARGET
        jmp     SHORT wjn200

wjn109_2:
        mov     ax, ERROR_IS_JOIN_TARGET
        jmp     SHORT wjn200

wjn110: mov     ax, ERROR_INVALID_FLAG_NUMBER
        jmp     SHORT wjn200

wjn111: mov     ax, ERROR_INVALID_BLOCK
        jmp     SHORT wjn200

wjn112: mov     ax, ERROR_NOT_ENOUGH_MEMORY

;
; Exit with ax set to error.
;

wjn200: stc
        jmp     short wjnx

EndProc w_Join

BREAK <w_Subst - Worker for Subst command>

;***    w_Subst - worker for Subst command
;
;       w_Subst is the worker routine for the DOSSUBST API.
;       The result of a drive being substed (for a directory)
;       is that any references to the drive really refer
;       to the directory.
;
;       w_Subst performs three functions. These are;
;               1) List the drives that are currently substed
;               2) Subst a drive
;               3) Delete a Subst which is already in effect.
;
;       w_Subst make use of the special CDS' whose handles are in
;       the range one to 26. These CDS' are especially used by the
;       DOS to support name aliasing.
;
;
;       ENTRY:
;               al      Sub-function code
;                       0 - list substs
;                       1 - add subst
;                       2 - delete subst
;
;               bx      0-based drive designator
;               es:di   pointer to path string/buffer
;               cx      length of path string/buffer
;
;       EXIT:
;               Carry Clear (no error)
;                       ax = 0
;                       es:di may contain path string if ax = 0 on entry.
;               Carry Set (error)
;                       ax = error code
;
;       USES:
;               All but bp
;

        ASSUME  ES:NOTHING, DS:NOTHING, SS:TASKAREA
Procedure w_Subst,near,local
LocalVar        dirent, <SIZE find_buf> ; Result buf for w_3xFindFirst
LocalVar        drivelet, 3             ; Drive letter string
LocalVar        path, DWORD             ; Pointer to path
LocalVar        drive, WORD             ; Drive number
LocalVar        end_wfp, WORD           ; Offset to end of wfp
LocalVar        cds, <SIZE curdir>      ; Buffer for CDS struct
LocalVar        ws_renbuf,PMPATHLEN     ; temp buff
LocalVar        dummy,WORD              ; KLUDGE: see gshare.asm
LocalVar        dummycds, <SIZE curdir> ; Buffer for CDS struct

        EnterProc

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

        mov     drive, bx               ; Save drive number
        call    $TJMP                   ; Jump to appropriate sub-function
;
; Jump table
;
        DB      (wstTE - wstTS) / 2     ; Number of sub-functions
        DW      DOSHIGH2CODE:wst108     ; Invalid sub-function
wstTS:  DW      DOSHIGH2CODE:wstList    ; List substs
        DW      DOSHIGH2CODE:wstAdd     ; Add subst
        DW      DOSHIGH2CODE:wstDelete  ; Delete subst
wstTE   LABEL   WORD

;
; List substs
; If a CDS exists for the given 0-based drive number, check
; to see if it is substed. If it is copy the CDS text field
; back to the user's buffer.
;

wstList:mov     ax, drive               ; 0-based drive number
        mov     bl, CD_LOCAL            ; Only list joined drives
        call    ListCDSText
        jmp     wst99


;*      Add subst
;
;       Add a CDS for the given drive to the CDS RMP segment. This CDS will
;       have a handle equal to the one based drive number which it
;       represents. Before the CDS is added it is necessary to determine
;       that the drive and the path to which it is substed are in a '
;       useable' state. For the drive 'useable' means that it is not already
;       joined or substed and that it is not the current drive.  The path is
;       useable so long as it exists.


wstAdd: mov     path.Segmt, es          ; Save pointer to path
        mov     path.Offst, di
        mov     bx, drive               ; (bx) = 0-based drive number
        inc     drive                   ; 1-based drive number

;       disallow remapping of current drive

                                        ;72417 starts
        cmp     bl,ss:[LCurDrv]         ; are we remapping current drive?
                                        ;72417 ends
        LJZ     wst102                  ; yes, go return drive-busy

        mov     ax, drive               ; (ax) = 1-based drive number

;       Check that drive is not already substed or joined.

        call    GetCDS                  ; es:di -> CDS
        jc      wst20                   ; Jump if no CDS for drive
        test    es:[di.cd_flags], CD_LOCAL
        LJNZ    wst104                  ; Error if drive is already substed
        test    es:[di.cd_flags], CD_JOIN
        LJNZ    wst103                  ; Error if drive is joined

;*      Check that the directory path exists and is valid.
;       Start by converting to a WFP ("well formed path").

wst20:  lds     si, path                ; ds:si -> path
        ASSUME  ds:NOTHING
        lea     di, ws_renbuf           ; ss:di -> ws_renbuf
        SaveReg         <bp>
        call    TransPath               ; Convert path to WFP
        RestoreReg      <bp>
        LJC     wst100                  ; Error if path not found
        lea     si, ws_renbuf           ; ds:si -> wfp in ws_renbuf
        mov     path.Segmt, ds          ; Save pointer to WFP
        mov     path.Offst, si

;   Check to see if the last character of the path is '\'.  If so, just return
;   an error.  (Unless the last char is DBCS.)

        SaveReg <dx>
        xor     al,al                   ; (AL) = 0
        les     di,path                 ; (es:di) -> wfp
        mov     bx,di                   ; save offset to wfp
wst21:  scasb                           ; search for null (end of wfp)
        jne     short wst21
        mov     dx,di                   ; get offset to null char (+1)
        dec     dx
        dec     dx                      ; point to last char in path
        call    ExmDBCS                 ; check for DBCS char at end of path
        RestoreReg      <dx>
        jc      short wst21a            ; accept path with DBCS char at end
        cmp     byte ptr es:[di-2], '\' ; check for trailing '\'
        jne     wst21a                  ;SDD cp20 b719551
        mov     byte ptr es:[di-2],0    ;Strip off trailing \
        dec     di                      ;

wst21a: mov     al,[si]
        sub     al, 'A' - 1
        xor     ah, ah                  ; ax = 1-based drive letter of directory

;       Check that the drive that the directory is on is neither joined
;       nor substed.

        call    GetCDS                  ; es:di -> CDS
        jc      short wst22             ; Jump if no CDS for drive
        test    es:[di.cd_flags], CD_LOCAL
        LJNZ    wst106                  ; Error if drive is substed
        test    es:[di.cd_flags], CD_JOIN
        LJNZ    wst105                  ; Error if drive is join

;       Check that the directory is not on the drive that is being joined.

wst22:  cmp     ax, drive
        LJE     wst107                  ; Error if drives are the same

        les     di, path                ; es:di -> wfp in ws_renbuf
;
; Check that the directory exists by calling w_3xFindFirst.
; If it finds anything then we're alright.
;
; Append path with \*
;
        xor     al, al                  ; NUL character

wst22_0:scasb                           ; Search for end of wfp
        jne     short wst22_0
        dec     di                      ; Backup to NUL
        mov     end_wfp, di             ; Save index to end of wfp
        SaveReg         <cs>
        RestoreReg      <ds>
        lea     si, srchstr                     ; ds:si -> "\*"
;#61399       cmp       BYTE PTR es:[di - 2], ':'       ; Special case if we are
        cmp     BYTE PTR es:[di - 1], ':'       ; Special case if we are ;#61399
        je      short wst22_2                   ; substing a root directory
                                                ; don't bother searching,
                                                ; we know it's there!!!
        call    FStrCpy
;
; Set up arguments for w_3xFindFirst
;
; Save current DTA so we can use our own result buffer
;
                                                ;72417 moved from PTDA to TCB
        mov     cx,bp
        mov     bp,CurrTCB
        push    word ptr [bp].TCBDTAddr.Segmt   ; Save current DTA address
        push    word ptr [bp].TCBDTAddr.Offst
        mov     ax,ss
        mov     word ptr [bp].TCBDTAddr.Segmt,ax
        mov     bp,cx                           ; restore bp for lea
        lea     ax, dirent
        mov     bp,CurrTCB
        mov     word ptr [bp].TCBDTAddr.Offst,ax    ; DTA -> our result buffer
        mov     bp,cx
                                                ;72417 end

; Search for all files/directories
;
        mov     cx,ATTR_ANY             ; (CX) = Search Attributes

; Set up path
;
        TaskContext     <ds>
        lea     dx,ws_renbuf            ; (DS:DX) -> search string

; Call w_3xFindFirst
;
        CALLFAR w_3xFindFirst
        ASSUME  ds:NOTHING

        jnc     wst22_1                 ; Jump if anything is found
        cmp     ax, ERROR_PATH_NOT_FOUND
        LJE     wst200                  ; Error if path is bad
        jmp     wst100                  ; Error if any other path problems

;
; Terminate the directory search.
;

wst22_1:CALLFAR w_3xFind_Close          ; Terminate directory search

; Restore original DTA
;
        TaskContext     <ds>
                                        ;72417 moved from PTDA to TCB
        mov     si,CurrTCB
        pop     word ptr TASKAREA:[si].TCBDTAddr.Offst
        pop     word ptr TASKAREA:[si].TCBDTAddr.Segmt
                                        ;72417 end

        mov     si, end_wfp
        mov     BYTE PTR [si], 0        ; Put NUL character back at end of wfp

wst22_2:TaskContext     <ds>
;
; All is fine...
; Initialize the CDS to be added.
;
        lea     si, cds                 ; ds:si -> cds
;
; Drive is busy and substed
;
        mov     [si.cd_flags], CD_INUSE OR CD_LOCAL

        les     di, path                ; es:di -> wfp in ws_renbuf
        ASSUME  es:NOTHING

        CALLFAR StrLen                  ; cx = length of wfp
        dec     cx                      ; Don't count the NUL

        mov     [si.cd_fsi].cdi_end, cx ; Point in text where '. and '..'
                                        ; stop making sense
;
; Make sure path ends with '\' NUL
;
        cmp     BYTE PTR es:[di][2], 0
        jne     short wst23
        mov     BYTE PTR es:[di][2],'\' ; If we are at the root,
        mov     BYTE PTR es:[di][3],0   ;  make sure there is
                                        ;  a terminating '\'

wst23:  mov     [si.cd_fsd.cddFAT_id],NULLCDSID  ; NULL ID

;
; Make es:di -> cdi_text
; Make ds:si -> directory string
; Copy directory string to cdi_text
;
        SaveReg         <ds, si>
        SaveReg         <ds>
        RestoreReg      <es>
        lea     di, [si.cd_fsi].cdi_text
        lds     si, path                ; ds:si -> wfp in ws_renbuf
        CALLFAR DStrLen                 ; cx = length of wfp
        SaveReg         <cx>
        mov     bx,es
        call    SuBuffz
        LJC     wst200                  ; Error if GP fault
        RestoreReg      <cx>
;
; Get the DPB for the drive where the subst directory exists
;
        SaveReg         <ds, si>
        lds     si, path                ; ds:si -> wfp in ws_renbuf
        ASSUME  ds:NOTHING
        mov     ax, [si]
        xor     ah, ah
        sub     al, 'A'                 ; ax = 0-based drive number
        RestoreReg      <si, ds>
        call    Find_DPB                ; ds:si -> DPB
        RestoreReg      <di, es>
        LJC     wst111                  ; Error if DPB not found

        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
;
; Add the CDS to the CDS RMP segment
;
        TaskContext     <ds>
        lea     si, cds                         ; ds:si -> CDS to add
        add     cx, (cd_fsi+cdi_text)           ; Length of record
        mov     ax, drive                       ; ax = handle
        call    AddCDS
        LJC     wst112                          ; Error if adding CDS failed
        jmp     wst99

;*      Delete subst.
;
;       Delete the CDS associated with the substed drive. In order to do
;       this the drive has to be substed and not the current drive.


wstDelete:
        mov     bx, drive               ; (bl) = 0-based drive to be de-subst'd
        inc     drive

                                        ;72417 starts
        cmp     bl, ss:[LCurDrv]        ; is it the current drive?
                                        ;72417 ends
        jz      short wst102            ; yes, go return busy drive

        mov     ax, drive               ; (ax) = 1-based drive number.
;
; Make sure that the drive is substed and that it is
; not joined.
;
        call    GetCDS                  ; es:di -> CDS
        jc      short wst101                    ; Error if no CDS for drive
        test    es:[di.cd_flags], CD_LOCAL
        LJZ     wst101                          ; Error if drive is not substed
        test    es:[di.cd_flags], CD_JOIN
        LJNZ    wst103                          ; Error if drive is joined
;
; All is fine...
; Remove the CDS from the CDS RMP segment
;
        mov     ax, drive
        xor     bx, bx
        mov     ds, bx                  ; Invalidate ds
        call    DelCDS

; use the drive letter that is being deleted on the substitution and
; get the current CDS for that volume, then delete the CDS

; Each PTDA has an array (one for each possible drive: A-Z) of CDS handles
; which (if non-zero) are the handles for the RMP CDS which has the current
; directory for each drive.  If one is zero, the current directory is the
; root for that drive.


                                        ; b721881
        TaskContext     <ds>
        mov     si,drive                ; (ax) = 1-based drive number.
        dec     si                      ; make it 0-based
        add     si,si                   ; make drive number word index
        mov     AX,ds:[CDS_Handle+si]   ; (AX) = handle of current directory
        or      AX,AX
        jz      wst99                   ; no established c.d.
        call    GetCDS
        jc      wst99                   ;
        test    es:[di.cd_flags],CD_LOCAL ; has this drive been substed?
        jz      wst99
        xor     bx,bx
        mov     ds:[CDS_Handle+si],bx   ; (AX) = handle of current directory
        call    DelCDS

;
; Exit with no error.
;

wst99:  xor     ax, ax
;### PTR B720732 wstx:  mov     ss:[ptda_pCDS],0        ; Clear ptda_pCDS
wstx:   LeaveProc
        ret
;
; Following are all the errors that can be generated by w_Subst.
;

wst100: mov     ax, ERROR_PATH_NOT_FOUND
        jmp     SHORT wst200

wst101: mov     ax, ERROR_NOT_SUBSTED
        jmp     SHORT wst200

wst102: mov     ax, ERROR_BUSY_DRIVE
        jmp     SHORT wst200

wst103: mov     ax, ERROR_IS_JOINED
        jmp     SHORT wst200

wst104: mov     ax, ERROR_IS_SUBSTED
        jmp     SHORT wst200

wst105: mov     ax, ERROR_SUBST_TO_JOIN
        jmp     SHORT wst200

wst106: mov     ax, ERROR_SUBST_TO_SUBST
        jmp     SHORT wst200

wst107: mov     ax, ERROR_SAME_DRIVE
        jmp     SHORT wst200

wst108: mov     ax, ERROR_INVALID_FUNCTION
        jmp     SHORT wst200

wst111: mov     ax, ERROR_INVALID_BLOCK
        jmp     SHORT wst200

wst112: mov     ax, ERROR_NOT_ENOUGH_MEMORY

;
; Exit with ax set to error.
;

wst200: stc
        jmp     short wstx

EndProc w_Subst

BREAK <ListCDSText - list the Joins/Substs in effect>

;*** ListCDSText - list the Joins/Substs in effect
;
;       ListCDSText is used by both Join and Subst to list the
;       Joins/Substs that are currently in effect.
;       It accomplishes this by checking to see if the flag in
;       a CDS (associated with the given drive) equals the given
;       flag (dependent on whether the client was w_Join or w_Subst).
;       If it does then the text field of the CDS is copied
;       into the user's buffer.
;
;       ENTRY:
;               ax      - 0-based drive number
;               bl      - flag
;                       (CD_JOIN  - list joins)
;                       (CD_LOCAL - list substs)
;               es:di   -> string buffer
;               cx      - length of buffer.
;
;       EXIT:
;               Carry Clear
;                       ax = 0
;
;       USES:
;               ax, cx, ds, si, es, di
;

Procedure ListCDSText,<HYBRID,far16>

        mov     BYTE PTR es:[di], 0     ; Nullify string

        CALLFAR XCHGP                   ; ds:si -> string buffer
        inc     ax                      ; 1-based drive number
        SaveReg         <bx>
        call    GetCDS                  ; es:di -> CDS
        RestoreReg      <bx>
        jc      short lCDSt99           ; Jump if CDS not found

        CALLFAR XCHGP                   ; ds:si -> CDS, es:di -> string buffer

        test    [si.cd_flags], bl
        jz      short lCDSt99               ; Jump if drive not the type
                                            ;  that we are looking for

        lea     si, [si.cd_fsi].cdi_text    ; ds:si -> CDS.cdi_text
        SaveReg <bx>
        mov     bx,es
        call    SuBuffz                     ; Copy current directory string to
                                            ;  user's buffer
        RestoreReg <bx>

        jc      short lCDStx                ; Error if GP fault

lCDSt99:xor     ax, ax
lCDStx: ret

EndProc ListCDSText

BREAK <DriveBusy - check if drive is busy>

;*** DriveBusy - check if drive is busy
;
;       DriveBusy -     check if anyone has current directory or
;                       current drive on this drive.
;                       Do this by calling AsgCheckDrive and masking
;                       the results.
;
;       ENTRY:
;               bx - 1-based drive number
;
;       EXIT:
;               Carry Clear
;                       Drive is not being referenced
;               Carry Set
;                       Drive is being referenced
;
;       USES:
;               All but bp
;

Procedure DriveBusy,,local

        call    AsgCheckDrive           ; Check for references to drive
        and     ax, BUSY_DRIVE          ; Check current dirs. and drives only
        jz      short db200             ; Error if there are references to drive
        stc                             ; Drive is being referenced
db200:  ret

EndProc DriveBusy

BREAK <PathErr - check if a join or subst is being used>

;*** PathErr - check if a join or subst is being used
;
;       PathErr - check through other CDS' to determine if a join
;               or subst is being used.
;
;       ENTRY:
;               es:di   -> CDS to begin search (di = 0 if first time)
;               ds:si   -> path to check
;       EXIT:
;               Carry Clear
;                       no more CDS'
;               Carry Set
;                       es:di   -> offending CDS
;
;       USES:
;               ax, cx, ds, si, es, di, flags
;

Procedure PathErr,,local

        ASSUME  ds:NOTHING, es:NOTHING, ss:TASKAREA
        LocalVar        path, DWORD             ; Pointer to path
        LocalVar        pathsep, DWORD          ; Pointer to last '\' in path
        LocalVar        cds, DWORD              ; Pointer to cds
        LocalVar        pathlen, WORD           ; Length of path

        EnterProc

        mov     path.Segmt, ds          ; Save pointer to path
        mov     path.Offst, si

        mov     cds.Segmt, es           ; Save pointer to CDS to begin search
        mov     cds.Offst, di

        CALLFAR DStrLen                 ; Get length of path
        dec     cx                      ; Not including NUL
        mov     pathlen, cx             ; Save length of path

;
; Set di to point past last '\' in path
;

        mov     di, si
ptherr1_1:
        lodsb
        or      al, al
        je      short ptherr1_3         ; Jump if end of string
        TestDBCS                        ; double byte character?
        jz      short ptherr1_2         ; yes, jump down
        call    PathChrCmp              ; If al == '/' convert to '\'
        jnz     short ptherr1_1         ; Continue if not '\'
        mov     di, si
        inc     di                      ; ds:di -> char after last '\'
        jmp     short ptherr1_1

;       handle double byte characters

ptherr1_2:
        lodsb                           ; get training byte
        or      al, al                  ; are we done?
        jnz     short ptherr1_1         ; no, keep looping

ptherr1_3:
        mov     pathsep.Segmt, ds       ; Save path seperator
        mov     pathsep.Offst, di

;
; For each CDS, check if path is a prefix of cdi_text
;

ptherr2:les     di, cds                     ; es:di -> cds
        call    NextCDS                     ; es:di -> next CDS
        jc      short ptherr99              ; Jump if no CDS' left

        mov     cds.Segmt, es               ; Save CDS pointer
        mov     cds.Offst, di
        lds     si, path                    ; ds:si -> path
        lea     di, es:[di.cd_fsi].cdi_text ; es:di -> cd_text
        mov     cx, pathlen
        repe cmpsb                          ; strncmp(path, cd_text, pathlen)
        jnz     short ptherr2               ; Continue if strings differ

;
; Prefix matched
; If the last char of path is a '\' (i.e. pathsep -> NUL) return the CDS pointer
;
        lds     si, pathsep                 ; ds:si -> 1 char past last '\'
        cmp     BYTE PTR [si], 0
        je      short ptherr200             ; Jump if it is a NUL byte

;
; Prefix matched
; If the next cd_text char is a '\0' or '\' or '/' return the CDS pointer
;

        lds     si, cds                     ; ds:si -> cds
        lea     si, [si.cd_fsi].cdi_text    ; ds:si -> cdi_text
        add     si, pathlen                 ; ds:si -> first char after prefix
        cmp     BYTE PTR [si], 0
        je      short ptherr200             ; Jump if char is a NUL
        cmp     BYTE PTR [si], '/'
        je      short ptherr200             ; Jump if char is a '/'
        cmp     BYTE PTR [si],'\'
        je      short ptherr200             ; Jump if char is a '\'

ptherr99:
        clc
        jmp     short ptherrx

ptherr200:
        stc
        les     di, cds                 ; Return pointer to offending CDS
ptherrx:LeaveProc NoCheck
        ret

EndProc PathErr

BREAK <CheckDirPath - check the validity of a directory path>

;*** CheckDirPath - check the validity of a directory path
;
;       CheckDirPath - check if a directory path meets the criteria
;                       required by the join command. A valid directory
;                       must meet the following set of constraints:
;
;                               1) Must exist
;                               2) Must be a sub-directory of the root
;                               3) Must be empty
;                               4) Must not be busy.
;
;       ENTRY:
;               ds:si   -> directory to check
;       EXIT:
;               Carry Clear
;                       directory is valid
;               Carry Set
;                       ax = error code
;
;       USES:
;               all but bp
;

Procedure CheckDirPath,,local
        ASSUME  ds:NOTHING, es:NOTHING, ss:TASKAREA

        LocalVar        dirstr, 20              ; Directory search string
        LocalVar        path, DWORD             ; Pointer to path
        LocalVar        dirent, <SIZE find_buf> ; Result buffer for w_3xFindFirst

        EnterProc

        mov     path.Segmt, ds
        mov     path.Offst, si          ; Save pointer to path
;
; Check that the path is a sub-directory of the root
;
        les     di, path                ; es:di -> path
        cmp     BYTE PTR es:[di+3], 0   ; error if root directory
        je      short cp05
        CALLFAR StrLen                  ; Get length of path
        add     di, 3                   ; start after 'x:\'
        sub     cx, 3
        mov     al, '\'
        CALLFAR FindChar                ; any more path characters?
        jnz     cp10                    ; no, continue on
cp05:   mov     ax, ERROR_DIR_NOT_ROOT
        stc
        jmp     cp50                    ; yes, return an error

;
; Append path with \* so that we can check that the directory
; is exists and is empty.
;
cp10:   TaskContext     <es>
        lea     di, dirstr              ; es:di -> dirstr
        call    FStrCpy                 ; Copy path to dirstr
        dec     di                      ; Backup to NUL
        SaveReg         <cs>
        RestoreReg      <ds>
        lea     si, srchstr             ; ds:si -> '\*'
        call    FStrCpy
;
; Set up arguments for w_3xFindFirst
;
; Save current DTA so we can use our own result buffer
;
                                        ;72417 moved from PTDA to TCB
        mov     cx,bp
        mov     bp,CurrTCB
        push    word ptr [bp].TCBDTAddr.Segmt   ; Save current DTA address
        push    word ptr [bp].TCBDTAddr.Offst
        mov     ax,ss
        mov     word ptr [bp].TCBDTAddr.Segmt,ax
        mov     bp,cx                           ; Put back bp for lea
        lea     ax, dirent
        mov     bp,CurrTCB
        mov     word ptr [bp].TCBDTAddr.Offst,ax ; DTA -> our result buffer
        mov     bp,cx
                                        ;72417 end

        TaskContext     <ds>
        lea     dx, dirstr              ; (DS:DX) -> search string
                                        ;  (path appended with '\*')
        mov     cx, ATTR_ANY            ; (CX) = Search attributes

; Call w_3xFindFirst. If it fails, then it is an error.
;
        CALLFAR w_3xFindFirst
        LJC     cp40                    ; Error. Restore DTA and return.
        TaskContext     <ds>
        cmp     dirent.find_buf_pname[0], '.'
        jne     short cp101
        cmp     dirent.find_buf_pname[1], 0
        jne     short cp101             ; Error if anything but
                                        ;  '.' was found
        CALLFAR w_3xFindNext
        LJC     cp200                   ; Error if w_3xFindNext fails
        TaskContext     <ds>
        cmp     dirent.find_buf_pname[0], '.'
        jne     short cp101
        cmp     dirent.find_buf_pname[1], '.'
        jne     short cp101
        cmp     dirent.find_buf_pname[2], 0
        jne     short cp101             ; Error if anything but
                                        ;  '..' was found
        CALLFAR w_3xFindNext
        jnc     short cp101             ; Error if anything else found
        cmp     ax, ERROR_NO_MORE_FILES
        jne     short cp200             ; Error if any problems besides
                                        ;  no files found
;
; Check that path isn't busy
;
        lds     si, path                ; ds:si -> path
        DosContext      <es>
        xor     di, di
        call    PathErr
        ASSUME  es:NOTHING
        jc      short cp102             ; Error if path is busy

        xor     ax,ax                   ; Return no error.
cp20:   pushf                           ; Preserve error indications
        push    ax                      ; Preserve any error code
        CALLFAR w_3xFind_Close          ; Terminate directory search
        pop     ax
        popf
;
; Restore original DTA
;
cp40:   TaskContext     <ds>
                                        ;72417 moved from PTDA to TCB
        mov     di,CurrTCB
        pop     word ptr TASKAREA:[di].TCBDTAddr.Offst
        pop     word ptr TASKAREA:[di].TCBDTAddr.Segmt
                                        ;72417 end

cp50:   LeaveProc
        ret


cp101:  mov     ax, ERROR_DIR_NOT_EMPTY
        jmp     SHORT cp200

cp102:  cmp     es:[di.cd_handle], NUMDRIVE
        ja      short cp102_1
        cmp     es:[di.cd_flags], CD_INUSE OR CD_LOCAL
        je      short cp102_2
        cmp     es:[di.cd_flags], CD_INUSE OR CD_JOIN
        je      short cp102_3
        jmp     short cp104

cp102_1:mov     ax, ERROR_PATH_BUSY
        jmp     SHORT cp200

cp102_2:mov     ax, ERROR_IS_SUBST_PATH
        jmp     SHORT cp200

cp102_3:mov     ax, ERROR_IS_JOIN_PATH
        jmp     SHORT cp200

cp104:  mov     ax, ERROR_INVALID_FLAG_NUMBER

cp200:  stc                         ; Indicate an error occurred
        jmp     short cp20          ; Termiate search and return the error

EndProc CheckDirPath

HIGH2CODE    ENDS
        END
