;       SCCSID = @(#)path.asm   6.6 91/11/13
SCCSID  equ     "@(#)path.asm   6.6 91/11/13"
TITLE   PATH - Directory related system calls
NAME    PATH

;
;        IBM/Microsoft Confidential
;
;        Copyright (c) IBM Corporation 1987, 1989
;        Copyright (c) Microsoft Corporation 1987, 1989
;
;        All Rights Reserved
;
; Directory related system calls.  These will be passed direct text of the
; pathname from the user.  They need to be passed through the macro
; expander prior to being sent through the low-level stuff.
;
;       Global          Local
;
;       w_Current_Dir
;       w_RmDir
;       FSD_RmDir
;       w_ChDir
;       w_MkDir
;       FSD_MkDir
;       FSD_ChDir
;       FSD_ChDirFree
;
;
;  10/11/93  ChangeTeam  D74507  change w_Current_Dir to retn           ;74507;
;            INT21_47_SUCCESS on success.  Int21,47 has an undocumented ;74507;
;            retn code upon successful completion upon which many apps  ;74507;
;            (esp. Windows) rely.                                       ;74507;
; ----------------------------------------------------------------------;74507;
;  11/27/95  MLM defect 72417
;                 moved from PTDA to TCB: cMeta, Curr_Dir_End, NoSetDir,;72417;
;                 PathNameType, PCurDrv, WFP_Start                      ;72417;
; ----------------------------------------------------------------------;74507;
;  04/01/96  DRB defect 150371                                         ;150371
;                 System Anchor Block support                          ;150371;
; ---------------------------------------------------------------------;150371;
;

.xlist
;
; get the appropriate segment definitions
;
include dosseg.inc
.xcref
include defdbug.inc
include basemaca.inc
include osmaca.inc
include fnofs.inc
include oldmaca.inc
include mi.inc
include dossymc.inc
include curdir.inc
include error.inc
include filesys.inc
include fsc.inc
include mult.inc
include protmode.inc
include proc.inc
include tkexport.inc
include vpb.inc
include extattr.inc
include shutdown.inc
.cref
.list

        ModName , FNO_PATH_ASM

        EXTRN           AddCDS:NEAR
        EXTRN           DelCDS:NEAR
        EXTRN           GetCDS:NEAR
        EXTRN           GetVisDrv:NEAR
        EXTRN           PathDecVPBRef:NEAR
        EXTRN           PathIncVPBRef:NEAR
        EXTRN           RlseBuffr:NEAR
        EXTRN           SuBuffz:NEAR
        EXTRN           TransPath:NEAR
        EXTRN           TransPathNoJoin:NEAR
        EXTRN           ValidateCDS:NEAR
        EXTRN           Volume_Access_Lock:NEAR
        EXTRN           Volume_Access_Unlock:NEAR
        EXTRN           ScanCDS:NEAR


        EXTRNFAR        DOS_ChDir
        EXTRNFAR        DOS_MkDir
        EXTRNFAR        DOS_RmDir
        EXTRNFAR        DStrLen
        EXTRNFAR        StrLen
        EXTRNFAR        Rmp_Add
        EXTRNFAR        StrCpy
        EXTRNFAR        CNotify_Path_Wake
        EXTRNFAR        EAOP_selector_alloc
        EXTRNFAR        EAOP_selector_free
ifdef SECURITY
        EXTRNFAR        SecMakeDir                                        ;@SEC
        EXTRNFAR        SecChangeDir                                      ;@SEC
        EXTRNFAR        SecRemoveDir                                      ;@SEC
endif

        ReferTask       CDS_Handle,WORD     ; current directory handles

                                            ;72417 removed PTDA entries now
                                            ;      in TCB

        ReferTask       CurrTCB,WORD        ; Current thread control block
        ReferTask       PCurDrv,BYTE
        ReferTask       ProcType,BYTE       ; = PT_REALMODE if 3xBox process

        ReferGDT        GDT_FSC,WORD        ; GDT selector for FSC segment

ifdef SECURITY
        ReferFlat       _fSec,BYTE           ;Is security Enabled?         @SEC
endif

        PUBLIC _NextCDSHandle, NextCDSHandle
DATA       SEGMENT
_NextCDSHandle  label   WORD                    ; D150371
NextCDSHandle   DW      NUMDRIVE
DATA       ENDS


        FARCODE HIGH2
HIGH2CODE SEGMENT
        ASSUME  SS:TASKAREA


;***    w_Current_dir - Dump the current directory into buffer
;
;       ENTRY   BX:SI   - target buffer <- NOTICE!!! BX NOT DS!!!
;               CX      - length of target buffer
;               DL      - number of drive to fetch current directory for
;
;       EXIT    'C'     - Error
;                       AX = error_invalid_drive
;                       AX = error_buffer_overflow
;                              Length of current directory was greater
;                              than length of buffer provided by caller.
;                              Buffer is left unchanged.
;               'NC'    - No error
;                       CX = length of current directory string (NOT including
;                            NULL at end of string).
;                       DS:SI filled with text of current directory.
;
;       USES    AX, BX, CX, DX, DS, ES, DI, SI, Flags
;
        ASSUME  DS:NOTHING,ES:NOTHING,SS:TASKAREA
Procedure w_Current_Dir,<near,faronly,far16>
LocalVar gcd_openbuf,PMPATHLEN
LocalVar gcd_dummy,WORD                 ; KLUDGE: see gshare.asm
LocalVar gcd_curdirbuf,<SIZE curdir>    ; allocate stack buffer
                                        ; we won't overrun the stack because
                                        ; we are at the top of the kernel
                                        ; stack and won't be calling anyone
                                        ; with it allocated.
LocalVar gcd_dummycds,<size curdir>

        PMONLY
        EnterProc
        push    bx                                      ;### PTR B720732
        mov     bx,TASKAREA:[CurrTCB]                   ;### PTR B720732
        lea     ax,gcd_openbuf
        mov     TASKAREA:[bx].TCBpOpenBuf,ax            ; Initialize TCBpOpenBuf ;### PTR B720732
;### PTR B720732        mov     TASKAREA:[ptda_pOpenBuf],ax ; Initialize ptda_pOpenBuf
        lea     ax,gcd_curdirbuf
        mov     TASKAREA:[bx].TCBpTmpCDS.Offst,ax       ; Initialize TCBpTmpCDS ;### PTR B720732
;### PTR B720732        mov     TASKAREA:[ptda_pCDS],ax ; Initialize ptda_pCDS
        pop     bx                                      ;### PTR B720732

        MOV     AL,DL                   ; get drive number (0=def, 1=A)
        call    GetVisDrv               ; grab it
        jc      gcd80                   ; Error. Return.
;
; The drive is valid. Now validate the CDS and then return the current
; directory string.
;
        SaveReg <BX,SI>                 ; save destination
;
; Random optimization nuked due to some utilities using GetCurrentDir to do
; media check.
;;      LDS     SI,ThisCDS
;;      TEST    [SI].cd_flags,CD_ISNET
;;      JNZ     DoCheck
;;      CMP     [SI].cd_fsd.cddFAT_id,0
;;      JZ      GetDst
;;DoCheck:
;
        lea     DI,gcd_openbuf
        push    CX                      ; save length of buffer
        call    ValidateCDS             ; ES:DI -> CDS
        pop     DX                      ; DX = length of buffer
        SaveReg <ES,DI>                 ; DS:SI -> CDS
        RestoreReg  <SI,DS>
;;GetDst:
        RestoreReg  <DI,BX>             ; (BX:DI) -> real destination
        JC      gcd80

        push    [si].cd_OwnerFSC        ; save offset of FSC
        MOV     AX,[SI.cd_fsi].cdi_end
        lea     si,[si].cd_fsi.cdi_text ; DS:SI -> current directory string
        ADD     SI,AX
        CMP     BYTE PTR [SI],'\'       ; root or subdirs present?
        JNZ     CurrentCopy
        INC     SI
CurrentCopy:
        CALLFAR DStrLen                 ; CX = length of current directory
        pop     ax                      ; ax = offset of FSC
        cmp     DX, CX                  ; is buffer long enough?
        jb      gcd70                   ; No. Return error.

; AX    =  offset of FSC
; CX    =  maximum buffer length
; DX    =  length of user buffer
; DS:SI -> Current directory string
; BX:DI -> target in user space

        push    bx
        mov     bx,CurrTCB
        test    TASKAREA:[bx].TCBNewFlags,TCBNEWFILES ; newfiles?
        pop     bx
        jz      gcd40                   ; no, go uppercase current directory

        cmp     ax,FSC_LOCAL            ; FAT?
        jne     gcd50                   ; no, go copy current directory

gcd40:  TaskContext <es>
        SaveReg <di>
        lea     di,gcd_curdirbuf        ; es:di -> stack buffer
        SaveReg <es,di>                 ; save pointer to stack buffer
        CALLFAR StrCpy                  ; copy and uppercase current dir
        RestoreReg <si,ds>              ; ds:si -> stack buffer
        RestoreReg <di>
        SaveReg <si,ds>                                                ;b729227
        call    SuBuffz                 ; copy string to user space
        RestoreReg <ds,si>                                             ;b729227
    assume es:nothing
        jmp     short gcd60

; DS:SI -> Current directory string
; BX:DI -> target in user space
; CX    =  maximum buffer length
; DX    = length of user buffer

gcd50:
        SaveReg <si,ds>                                                ;b729227
        call    SuBuffz                 ; copy string to user space
        RestoreReg <ds,si>                                             ;b729227

;       If carry is set, a fault occured.  However, the process is already
;       marked for death, so simply return normally.

gcd60:  mov     cx,dx                   ; CX = restore length of user buffer
;        xor     AL,AL                   ; AX = 0, 'NC' ;commented out->d74507;
        mov     ax,INT21_47_SUCCESS     ; indicate success             ;d74507;
        clc                             ; make sure carry flag is clear;d74507;
                                        ;  above 'xor' used to clear it;d74507;

;### PTR B720732 gcdx:  mov     SS:[ptda_pOpenBuf],0    ; Clear ptr to openbuf
;### PTR B720732        mov     SS:[ptda_pCDS],0        ; Clear ptr to dummycds

gcdx:   LeaveProc
        ret                             ; return with 'NC'
;
; Set error code
; CX = length of current directory
;
gcd70:  mov     AX, ERROR_BUFFER_OVERFLOW
        stc                             ; indicate error to caller
        jmp     short gcdx
;
; The drive was invalid.
;
gcd80:  mov     ax,ERROR_INVALID_DRIVE
        jmp     short gcdx                      ; make noise

EndProc w_Current_Dir


;***    w_RmDir - Delete directory if empty
;
;       ENTRY   DS:DX Points to ASCIIZ name
;
;       EXIT    STD XENIX Return
;               'NC' = no error
;               'C'  = error, AX is error indicator
;                 AX = ERROR_PATH_NOT_FOUND If path bad
;                 AX = ERROR_ACCESS_DENIED If
;                         Directory not empty
;                         Path not directory
;                         Root directory specified
;                         Directory malformed (. and .. not first two entries)
;                 AX = ERROR_CURRENT_DIRECTORY
;                         User tries to delete a current directory or macro
;                 AX = ERROR_FILENAME_EXCED_RANGE
;                         Pathname component too long
;
;       USES    ax, bx, cx, dx, si, di, ds, es, flags
;
;       NOTE    We lock the volume for FAT in this routine because that is
;               where we should do it. However, we unlock it in DOS_RmDir
;               because of a deadlock condition with it and trying to
;               release the EAs on a volume where we have not yet opened the
;               EA file. YUK!!!
;
;       PSEUDOCODE
;          logical wfp = TransPathNoJoin
;          if (error || metachar)
;             return error
;          if (fat)
;             Volume_Access_Lock
;          if (logical wfp is used by some CDS)
;                Volume_Access_Unlock
;                return error
;          }
;          physical wfp = TransPath
;          FSD_RmDir
;          if (error)
;             RlseBuffr
;
        ASSUME DS:NOTHING,ES:NOTHING
Procedure w_RmDir,<near,faronly,far16>
LocalVar wrd_lpszPath,DWORD             ; -> dir path supplied by caller
LocalVar wrd_freevpb,WORD               ; 0 = don't free, 1 = free
LocalVar wrd_openbuf,PMPATHLEN
LocalVar wrd_dummy,WORD                 ; KLUDGE: see gshare.asm
LocalVar wrd_dummycds,<SIZE curdir>

        PMONLY
        EnterProc <ds,dx,0>             ; save pointer to dir path
        lea     si,wrd_dummycds
        push    di
        mov     di,TASKAREA:[CurrTCB]                   ;### PTR B720732
        mov     TASKAREA:[di].TCBpTmpCDS.Offst,si       ; Initialize TCBpTmpCDS ;### PTR B720732
;### PTR B720732        mov     TASKAREA:[ptda_pCDS],si ; Initialize ptda_pCDS
        lea     si,wrd_openbuf
        mov     TASKAREA:[di].TCBpOpenBuf,si            ; Initialize TCBpOpenBuf ;### PTR B720732
;### PTR B720732        mov     TASKAREA:[ptda_pOpenBuf],si ; Initialize ptda_pOpenBuf
        pop     di

; call TransPathNoJoin to get the logical path (join not expanded) to use in
; CDS check

        mov     si,dx                   ; ds:si -> dir path
        lea     di,wrd_openbuf          ; ss:di -> bufer for TransPath
        SaveReg <bp>
        call    TransPathNoJoin         ; WFP_Start = logical path
        RestoreReg <bp>
        LJC     wrd200                  ; error, go error exit

                                        ;72417 moved from PTDA to TCB
        SetBP_CurrTCB
        cmp     [bp].TCBcMeta,-1        ; path contains meta chars?
        UnSetBP_CurrTCB
        LJNE    wrd210                  ;   yes, go error exit

illegalname = PNType_LOCDEV + PNType_PSDEV + PNType_NMPIPE
        SetBP_CurrTCB
        test    TASKAREA:[bp].TCBPathNameType,illegalname
        UnSetBP_CurrTCB
        LJNZ    wrd210                  ; Illegal object. Go error exit
                                        ;72417 end

; Scan CDS to make sure the dir path is not in use.
; bugbug 09-JAN-91 dillon - if sombody ChDirs into the logical directory
; between the ScanCDS and before we lock the physical volume we could be
; hosed.  I considered locking the logical volume here and then releasing
; it after I locked the physical drive but that won't work (what if logical
; and physical are the same (non-JOIN'ed case).  If this is a problem we
; must do a volume lock is DOS_Chdir as well.

        TaskContext <ds>
                                        ;72417 moved from PTDA to TCB
        mov     si,CurrTCB
        mov     si,ds:[si].TCBWFP_Start ; ds:si -> wfp
        xor     di,di                   ; scan all CDS
        call    ScanCDS                 ; is dir in use?
    assume ds:nothing,es:nothing
        LJNC    wrd290                  ; yes, go error exit
                                        ;72417 end

; call TransPath to get the physical path to operate on

        lds     si,wrd_lpszPath         ; ds:si -> dir path
    assume ds:nothing
        lea     di,wrd_openbuf
        SaveReg <bp>
        call    TransPath               ; WFP_Start = physical path
        RestoreReg <bp>
        jc      wrd200

ifdef SECURITY
; See if security is interested                                           ;@SEC
        CpuMode 386                                                       ;@SEC
        FlatContext <ds>                                                  ;@SEC
        cmp     _fSec,0                                                   ;@SEC
        jz      wrd05                                                     ;@SEC
        CALLFAR SecRemoveDir                                              ;@SEC
        jnc     wrd05                                                     ;@SEC
        jmp     wrd220                                         ;106079    ;@SEC

wrd05: TaskContext <ds>                                                   ;@SEC
       CpuMode Reset                                                      ;@SEC
endif

; if fat, get exclusive access to volume. This is here so we don't block
; waiting for access after CDS check.
; We purposely load TCBThisFSC.Segmt into ds as a simple sanity check.

        xor     dl,dl                   ; dl = flag = 0 (assume not FAT)
        mov     si,CurrTCB              ; ss:si -> TCB
        lds     si,TASKAREA:[si].TCBThisFSC ; ds:si -> fsc
        cmp     si,FSC_LOCAL            ; FAT file system?
        jne     wrd10                   ;   no, skip volume lock

; lock volume to serialize rmdir, rename, etc.

        mov     cx,VPB_ACC_EXCL         ; exclusive lock
        call    Volume_Access_Lock      ; lock volume (may block)
        mov     wrd_freevpb,1           ; free vpb

; Scan CDS to make sure the dir path is not in use.

wrd10:  TaskContext <ds>
                                        ;72417 moved from PTDA to TCB
        mov     si,CurrTCB
        mov     si,[si].TCBWFP_Start    ; ds:si -> wfp
                                        ;72417 end
        xor     di,di                   ; scan all CDS
        call    ScanCDS                 ; is dir in use?
    assume ds:nothing,es:nothing
        jnc     wrd290                  ; yes, go error exit

;
; Since this is a "path-based" call, there has been no referent put in the
; VPB for this call. We need to increment the ref-count in the VPB in order
; to ensure that the VPB does not go away while we are working on it.
;
        call    PathIncVPBRef           ; Increment reference count
        call    FSD_RmDir
        mov     wrd_freevpb,0           ; don't free vpb (dos_rmdir did)
        call    PathDecVPBRef           ; Remove our referent in the VPB
    assume ds:nothing
        jc      wrdx
        pushf
        push    ax                      ;### Notify
        mov     ax,4                    ;### Rmdir
        CALLFAR CNotify_Path_Wake       ;### Notify
        pop     ax                      ;### Notify
        popf

wrdx:   pushf
        cmp     wrd_freevpb,0           ; free vpb?
        je      wrdxx                   ; no
        mov     cx,VPB_ACC_EXCL         ; exclusive lock
        call    Volume_Access_Unlock    ; lock volume (may block)
wrdxx:  popf
        LeaveProc
        ret

; translate TransPath's error code

wrd200: cmp     ax,ERROR_FILENAME_EXCED_RANGE
        je      wrd220
wrd210: mov     ax,ERROR_PATH_NOT_FOUND
wrd220: stc
        jmp     short wrdx

wrd290:
;;;
;;; 15-Jul-1988 Nagara - Old code was returning ERROR_CURRENT_DIR
;;; if ScanCDS returns NC.
;;; It should return ERROR_CURRENT_DIR only if WFP is the CURRENT_DIR
;;; for some process; else it should return ERROR_ACCESS_DENIED
;;;
        or      al,al           ; ScanCDS returns in al, the next char
                                ; in thisCDS after successful match of
                                ; given WFP.
        stc                     ; set carry for error
        mov     ax,ERROR_CURRENT_DIRECTORY
        jz      wrdx            ; exact match? then it is current directory
                                ; for some process; cannot be removed
        mov     ax,ERROR_ACCESS_DENIED
        jmp     short wrdx
;;;
;;; END

EndProc w_RmDir


;**     FSD_RmDir - dispatch rmdir to proper file system
;
;       ENTRY   [TCBThisFSC]   = FSC of file system where dir is to be removed
;               [WFP_Start]    = physical WFP (join path expanded) of dir
;               [Curr_Dir_End] = -1 or points to end of current directory
;               [ThisCDS]      = CDS of drive where dir is
;
;       EXIT    if 'NC' - success
;               if 'C' - error
;                 ax = error code
;
;       USES    ax,bx,cx,dx,si,di,ds,es,flags

Procedure FSD_RmDir
    assume ds:nothing,es:nothing

        mov     si,[CurrTCB]            ; ss:si -> TCB
        lds     si,TASKAREA:[si].TCBThisFSC ; ds:si -> FSC
        cmp     si,FSC_LOCAL            ; what file system?
        jb      frd20                   ; fsd, go call fsd worker

; call FAT worker.  Do special shutdown processing.
        push    GSEL GDT_FSC
        pop     ds

        cmp     ds:[fss_ShutdownFlags],NotShutdown
        jne     short frd06

frd02:  inc     ds:[fss_SDWaitCount]
        SaveReg <ds,bp>
        CALLFAR DOS_RmDir
        RestoreReg <bp,ds>
        pushf

        cmp     ds:[fss_ShutdownFlags],NotShutdown
        jne     short frd08

        dec     ds:[fss_SDWaitCount]
frd04:  popf
        jc      frd200
        ret

frd06:  invoke  ShutdownCheck           ; handle shutdown detection on entry
        jmp     short frd02

frd08:  dec     ds:[fss_SDWaitCount]    ; handle shutdown detection on exit
        jnz     short frd04

        invoke  WakeShutdownThread      ; we must wake up the shutdown thread
        jmp     short frd04

    ASSUME DS:NOTHING
frd20:  mov     di,TASKAREA:CurrTCB             ; (DI) = current TCB     ;### PTR B720732
        les     di,TASKAREA:[di].TCBThisCDS     ; (ES:DI) = CDS pointer  ;### PTR B720732
;### PTR B720732 frd20: les     di,[ThisCDS]            ; es:di -> cds
        lea     ax,[di].cd_fsi          ; es:ax -> cd_fsi
        push    es
        push    ax                      ; push pointer to cd_fsi
        lea     ax,[di].cd_fsd          ; es:ax -> cd_fsd
        push    es
        push    ax                      ; push pointer to cd_fsd
        push    ss
                                        ;72417 moved from PTDA to TCB
        mov     bx,bp
        mov     bp,CurrTCB
        push    [bp].TCBWFP_Start       ; push pointer to dir path
        mov     ax,[bp].TCBCurr_Dir_End
        cmp     ax,-1                   ; is Curr_Dir_End set?
        je      frd30                   ; no, pass -1 to fsd
        sub     ax,[bp].TCBWFP_Start    ; ax = iCurDirEnd
frd30:  push    ax                      ; push iCurDirEnd
        mov     bp,bx
                                        ;72417 end
        call    ds:[si].FS_RMDIR        ; call fsc worker
        xor     bx,bx
        cmp     bx,ax                   ; set carry if ax != 0
        ret

; bugbug 16-apr-88 peterwu - RlseBuffr abuse
; DOS_RmDir should be mature enough to do its own RlseBuffr on error exit

frd200: call    RlseBuffr
        stc
        ret

EndProc FSD_RmDir


;***    w_ChDir - Top-level change directory system call.
;
;       This call is responsible for setting up the CDS for the specified
;       drive appropriately.  We do this by converting the path passed to
;       a WFP, verifying its existance, then creating a new CDS with the
;       new information contained in it.  In order to save space in the
;       CDS segment, we don't store information for drives which are at
;       the root and we don't split off a new CDS when we do a chdir to
;       the directory which was previously the current directory.
;
;       We have to do things a little bit different when the path refers to a
;       JOIN'ed CDS.  This is trouble as there are two CDS's at work.  If we
;       call TransPath, we will get the PHYSICAL CDS that the path refers to
;       and the PHYSICAL WFP that the input path refers to.  This is
;       perfectly good for the validation but not for currency.  We call
;       TransPathNoJoin to process the path but to return the logical CDS
;       and the logical path.  We then copy the logical path into the logical
;       CDS, but update the DPB pointer to point to the physical drive.
;       We also have to copy the cdfsd from the physical CDS to the logical
;       CDS (save on stack) (this preserves the cluster for FAT).
;
;       ENTRY   DS:DX -> ASCIIZ path
;
;       EXIT    'NC' = no error
;               'C'  = error, AX is error code
;                 AX = ERROR_PATH_NOT_FOUND, directory not found
;                 AX = ERROR_NOT_ENOUGH_MEMORY, no space for CDS
;                 AX = ERROR_FILENAME_EXCED_RANGE, pathname component too long
;
;       USES    AX, BX, CX, DX, DI, SI, DS, ES, flags
;
;       PSEUDOCODE {
;
;           Get and save drive number
;           TransPath
;           if (error)
;               return error
;           if (local device || pseudo-character device || named pipe)
;               return error
;           if (metachars)
;               return error
;           if (ThisCDS.Offst == -1)
;               internal error
;           if (FAT)
;               Volume_Access_Lock(file_op)
;           FSD_ChDir                        ; see if directory exists
;           if (error) {
;               if (FAT)
;                   Volume_Access_Unlock(file_op)
;               return error
;           }
;           if (join) {
;               keep devptr of physical CDS
;               TransPathNoJoin             ; get CDS for logical path
;               restore devptr from physical CDS
;           }
;           make sure length of new curdir is below max
;           copy new dir name into CDS
;           set up rest of CDS (owner, etc.)
;           if (new CurDir != root) {               /* no handle needed */
;               if (CD_handle > NUMDRIVE) {         /* already have handle */
;                   GetCDS
;                   if (RMP_CDS == ptda_pCDS)       /* CurDir did not change */
;                       return
;               }
;               get free handle
;               set up cd_handle and cd_refcnt
;               RlseBuffr
;               Rmp_Add        ; add cds to rmp segment
;               if (error) {
;                   if (FAT)
;                       Volume_Access_Unlock(file_op)
;                   return
;                   }
;           }
;           if (old CD_handle > NUMDRIVE)           /* old RMP_CDS existed */
;               DelCDS
;           save new handle in PTDA handle table
;           if (FAT)
;               Volume_Access_Unlock(file_op)
;           return
;       } /* w_ChDir */

        ASSUME  DS:NOTHING,ES:NOTHING,SS:TASKAREA
Procedure w_ChDir,<near,faronly,far16>
LocalVar        WCD_PathName, DWORD     ; path passed in (ds:dx)
LocalVar        WCD_OldHandle,WORD      ; old handle of current directory
LocalVar        WCD_LogicalDrive,BYTE   ; input drive number (zero-based)
LocalVar        WCD_NewFiles,BYTE       ; current newfiles value
LocalVar        WCD_openbuf,PMPATHLEN
LocalVar        WCD_dummy,WORD          ; KLUDGE: see gshare.asm
LocalVar        WCD_dummycds,<SIZE curdir>

        PMONLY
        EnterProc   <ds,dx>             ; save PathName
        lea     si,WCD_dummycds
        mov     bx,TASKAREA:[CurrTCB]                   ;### PTR B720732
        mov     TASKAREA:[bx].TCBpTmpCDS.Offst,si       ; Initialize TCBpTmpCDS ;### PTR B720732
;### PTR B720732        mov     TASKAREA:[ptda_pCDS],bx ; Initialize ptda_pCDS
        lea     si,WCD_openbuf
        mov     TASKAREA:[bx].TCBpOpenBuf,si            ; Initialize TCBpOpenBuf ;### PTR B720732
;### PTR B720732        mov     TASKAREA:[ptda_pOpenBuf],bx ; Initialize ptda_pOpenBuf

;   Save current value of NewFiles value and Force it to accept longnames
;###    mov     bx,CurrTCB
        mov     al,TASKAREA:[bx].TCBNewFlags
        mov     WCD_NewFiles,al         ;
        or      TASKAREA:[bx].TCBNewFlags,TCBNEWFILES

;   Get and save specified drive number

        mov     si,dx                   ; DS:SI -> ASCIIZ path
        mov     ax,[si]                 ; get first two chars of path
        cmp     ah,':'                  ; start with d:?
        jnz     wchd1                   ; no, use default drive
        and     al,NOT 20h              ; map to upper case
        sub     al,'A'                  ; and make zero-based
        jmp     short wchd2
wchd1:
                                        ;72417 now uses ss:
        mov     al,ss:[PCurDrv]         ; get the current drive
wchd2:  mov     WCD_LogicalDrive,al     ; save the input drive

;   Do name translation

        lea     di,WCD_openbuf          ; spot for translated name
        SaveReg <bp>
        call    TransPath               ; go process the path and get real CDS
                                        ; (DS) = TASKAREA
        jnc     ChDirCrack              ; no errors, try path

        RestoreReg <bp>
        cmp     ax,ERROR_FILENAME_EXCED_RANGE
        je      ChDirErr                ; let error be returned
        jmp     short ChDirErrP         ; return PathNotFound

ChDirErrP1:
        RestoreReg <bp>
ChDirErrP:
        mov     ax,ERROR_PATH_NOT_FOUND
ChDirErr: stc
        jmp     wchdExit

wcdie1: InternalError <w_ChDir - ThisCDS == -1>

ChDirCrack:
    SegAssert SS,<DS>,<w_ChDir - DS != SS (SS = TASKAREA)>
    ASSUME      DS:TASKAREA

;
; Devices, named pipe, and UNC names are not allowed here.
; NOTE that even though we allow RmDir and MkDir on UNC, we do not
; allow ChDir on it because we have no place to store the current
; dir for a UNC.
;
illegalname = PNType_LOCDEV OR PNType_PSDEV OR PNType_NMPIPE OR PNType_UNC

                                        ;72417 moved from PTDA to TCB
        SetBP_CurrTCB
        test    TASKAREA:[bp].TCBPathNameType,illegalname
        UnSetBP_CurrTCB
        jnz     ChDirErrP1              ; illegal name, go return error

;   meta characters are not allowed

        SetBP_CurrTCB
        cmp     [bp].TCBcMeta,-1        ; No meta chars allowed.
        UnSetBP_CurrTCB
                                        ;72417
        jne     ChDirErrP1

;   Cannot do a ChDir (yet) on a raw CDS.  This is treated as a path not 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 -> CDS
        cmp     di,-1                   ;   if (ThisCDS == NULL)
        jz      wcdie1                  ;       InternalError ();

ifdef SECURITY
; See if security is interested                                           ;@SEC
        CpuMode 386                                                       ;@SEC
        FlatContext <ds>                                                  ;@SEC
        cmp     _fSec,0                                                   ;@SEC
        jz      wchdsec                                                   ;@SEC
        CALLFAR SecChangeDir                                              ;@SEC
        jnc     wchdsec                                                   ;@SEC
        jmp     wchdExit                                                  ;@SEC

wchdsec:                                                                  ;@SEC
       TaskContext <ds>                                                   ;@SEC
       CpuMode Reset                                                      ;@SEC
endif

;   For FAT case, must lock volume access (shared access)

        mov     bp,CurrTCB
        cmp     [bp].TCBThisFSC.Offst,FSC_LOCAL ; is it FAT FS?
        jne     wcd3                            ; no, skip lock

        mov     cx,VPB_ACC_SHARE        ; shared access
        call    Volume_Access_Lock      ; get access to volume

;   Find out if the directory exists.

wcd3:   xor     ax,ax                   ; AX = 0 (means set cur dir)
.errnz (CHDIR_SET)
;
; Since this is a "path-based" call, there has been no referent put in the
; VPB for this call. We need to increment the ref-count in the VPB in order
; to ensure that the VPB does not go away while we are working on it.
;
        call    PathIncVPBRef           ; Increment reference count
        call    FSD_ChDir               ; go handle routing, etc.
        call    PathDecVPBRef           ; Remove our referent in the VPB

        RestoreReg <bp>                 ; get frame pointer back
        LJC     wchd10                  ; error, go release volume and return

;   Get back CDS to see if a join was seen.  Set the currency pointer (only if
;   not network).  If none was seen, all we need to do is copy in the text
;
        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
        test    es:[di].cd_flags,CD_JOIN
        LJZ     GotCDS

; The above lock locked the physical volume.  We must unlock it.  After we find
; the logical volume, we may lock it later.

        mov     bx,CurrTCB
        cmp     TASKAREA:[bx].TCBThisFSC.Offst,FSC_LOCAL    ; is this a FAT FS?
        jne     wcdxx                   ; no, skip unlock

        mov     cx,VPB_ACC_SHARE        ; shared access
        SaveReg <es>
        call    Volume_Access_Unlock    ; release volume, preserves flags,ax
        RestoreReg  <es>
wcdxx:

; The CDS was joined.  Let's go back and grab the logical CDS and logical WFP.
; first, save cluster and DPB pointer
;
        push    word ptr es:[di.cd_devptr].Offst
        push    word ptr es:[di.cd_devptr].Segmt

; Save the cdfsd (we need to use it later to build the real CDS)

.errnz (SIZE cdfsd - 8)
        test    es:[di.cd_fsi].cdi_flags,CDI_ISROOT ; check for root
        jnz     wcd5                                ; root implies 0s

; Non-root - we must save the cd_fsd
        push    word ptr es:[di].cd_fsd
        push    word ptr es:[di].cd_fsd+2
        push    word ptr es:[di].cd_fsd+4
        push    word ptr es:[di].cd_fsd+6
        jmp     short wcd6

; Root case implies that the cd_fsd is meaningless
wcd5:   push    0
        push    0
        push    0
        push    0

wcd6:   lds     si,WCD_PathName         ; ds:si -> original text
        lea     di,WCD_openbuf          ; spot for translated name
        SaveReg <di,bp>
        call    TransPathNoJoin         ; Translate path, no join
        RestoreReg  <bp,si>
        LJC     wchd_ier

; We unlocked the physical volume above (if locked).  Now check the logical
; volume.

; For FAT case, must lock volume access

        mov     di,CurrTCB
        cmp     TASKAREA:[di].TCBThisFSC.Offst,FSC_LOCAL    ; is it FAT FS?
        jne     wcdzz                           ; no, skip lock

        mov     cx,VPB_ACC_SHARE                ; shared access
        SaveReg <es>
        call    Volume_Access_Lock              ; get access to volume
        RestoreReg  <es>
wcdzz:

        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                      ; get new CDS
        or      es:[di.cd_flags],CD_JOIN        ; note that c.d. is joined

; Restore the saved cdfsd

        pop     word ptr es:[di].cd_fsd+6
        pop     word ptr es:[di].cd_fsd+4
        pop     word ptr es:[di].cd_fsd+2
        pop     word ptr es:[di].cd_fsd

; Put joined DPB pointer into new CDS

        pop     word ptr es:[di.cd_devptr].Segmt
        pop     word ptr es:[di.cd_devptr].Offst

;   ES:DI point to the logical CDS
;
;   WFP_Start points to the text.  See if it is long enough

GotCDS: TaskContext <ds>
                                        ;72417 moved from PTDA to TCB
        mov     si,CurrTCB
        mov     si,[si].TCBWFP_Start    ; DS:SI -> text for directory
                                        ;72417 end
        CALLFAR DStrLen                 ; (CX) = Length of current directory
;
;   Choose the maximum curdir length that we can have, then determine if
;   we are going to exceed it.
;
        mov     dx,PMDIRSTRLEN
        cmp     ProcType,PT_REALMODE
        jne     wchd5
        mov     dx,RMDIRSTRLEN          ; Max curdir length for 3x box
wchd5:  cmp     cx,dx                   ; new curdir too long?
        jbe     wchd5b                  ; no, go continue chdir
        mov     ax,ERROR_PATH_NOT_FOUND
        stc
        jmp     wchd10                  ; error, go release volume and return

;   copy new CurDir into the CDS

wchd5b: lea     di,[di].cd_fsi.cdi_text ; ES:DI -> location in CDS to put text
        SaveReg <cx>
        shr     cx,1                    ; convert to words (plus carry if odd)
    rep movsw                           ; copy in the new directory name
        jnc     wchd5c                  ; even # of bytes; we're done
        movsb

wchd5c: RestoreReg <cx>                 ; (CX) = length of current directory
    .errnz      ((cd_fsi+cdi_text+PMDIRSTRLEN)-SIZE curdir)
        add     cx,cd_fsi+cdi_text              ; CX = size of CDS

;   Update owner & mark valid
        push    bx                              ;### PTR B720732
        mov     bx,CurrTCB                      ;### PTR B720732
;### PTR B720732  mov di,CurrTCB
        les     di,ss:[bx].TCBThisFSC                   ; ES:DI -> FSC
        lds     si,TASKAREA:[bx].TCBThisCDS     ; (ES:DI) = CDS pointer  ;### PTR B720732
;### PTR B720732 lds    si,ThisCDS                              ; DS:SI = ptda_pCDS
        pop     bx                              ;### PTR B720732
        mov     ds:[si].cd_OwnerFSC,di
        or      ds:[si].cd_fsi.cdi_flags,CDI_ISVALID    ; mark it valid/current

        mov     ax,[si.cd_handle]       ; get old directory handle
        mov     WCD_OldHandle,ax

;   Is the new CurDir at the root?  If so, then we don't need handle

        mov     bx,[si.cd_fsi].cdi_end  ; get pointer to root
        cmp     byte ptr [si.cd_fsi.cdi_text+bx],'\'
        jne     wchd6                   ; != '\'
        inc     bx                      ; skip past '\'
wchd6:  test    byte ptr [si.cd_fsi.cdi_text+bx],0FFh   ; c.d. to root?
        jnz     wchd6a                  ; not null, not root, go on
        mov     [si.cd_handle],0        ; yes, don't add to CDS seg.
        or      ds:[si.cd_fsi].cdi_flags,CDI_ISROOT     ; mark it root
        jmp     short wchd9

;   if CDS is new (set up by InitCDS), cd_handle = 0.
;   if CDS is JOIN/SUBST/REMOTE, 1 <= cd_handle <= 26
;   NUMDRIVE = 26

wchd6a: cmp     ax,NUMDRIVE             ; do we already have a handle
        jbe     wchd6b                  ; no, go get one
        call    GetCDS                  ; get the RMP_CDS
        SaveReg <si,cx>

;   did we cd to the directory we were already in?

    rep cmpsb                           ; CDS unchanged?
        RestoreReg <cx,si>
        LJZ     wchd9b                  ; no changes, we're done

;   we know we are not at a root.

wchd6b: and     ds:[si.cd_fsi].cdi_flags,NOT CDI_ISROOT ; mark it non-root

;   Go find an available handle

        push    ds
        DosContext ds
        mov     ax,NextCDSHandle        ; last-obtained handle
    ASSUME      DS:NOTHING
        pop     ds

wchd7:  inc     ax                      ; check the next handle
        jnz     wchd8                   ; ax > NUMDRIVE (valid handle)
        mov     ax,NUMDRIVE+1           ; ax <= NUMDRIVE, wrap around correctly

wchd8:  call    GetCDS                  ; is this handle in use?
        jnc     wchd7                   ; yes, try another
;
;       AX = unused (new) CDS handle
;       DS:SI point to CDS
;       CX = length of new CDS
;
        push    ds
        DosContext ds
        mov     NextCDSHandle,ax        ; this one is now the last-obtained handle
        pop     ds
    ASSUME      DS:NOTHING

;   Set up CDS, and save it

        call    AddCDS
        mov     ax,ERROR_NOT_ENOUGH_MEMORY
        jc      wchd10                  ; error, go unlock volume and return

;   If the old-CDS handle was valid (> 26), then we must go remove our reference
;   to it

wchd9:  mov     AX,WCD_OldHandle        ; get back old handle
        cmp     ax,NUMDRIVE             ; previous reference?
        jbe     wchd9a                  ; no, no need to delete
        call    DelCDS                  ; delete a reference

;   Now we can store our new handle in the PTDA handle table

wchd9a: mov     bl,WCD_LogicalDrive     ; get drive number
        xor     bh,bh
        shl     bx,1                    ; turn into word index
        mov     ax,[si.cd_handle]
        mov     CDS_Handle[bx],ax
wchd9b: xor     al,al                   ; return with 'NC', al = 0

;   All (almost) done.  First we must unlock the volume (FAT only)

wchd10: pushf                           ; save carry (error?)
        mov     di,CurrTCB
        cmp     TASKAREA:[di].TCBThisFSC.Offst,FSC_LOCAL    ; is it FAT FS?
        jne     wcd11                   ; no, skip unlock

        mov     cx,VPB_ACC_SHARE        ; shared access
        call    Volume_Access_Unlock    ; release volume, preserves flags,ax

wcd11:  popf
wchdExit:
        mov     bl,WCD_NewFiles         ; restore old value of NewFiles
        mov     di,CurrTCB
        mov     TASKAREA:[di].TCBNewFlags,bl ; restore old value of NewFiles
;### PTR B720732        mov     SS:[ptda_pOpenBuf],0         ; Clear ptr to openbuf
;### PTR B720732        mov     SS:[ptda_pCDS],0             ; Clear ptda_pCDS
        LeaveProc
        ret

wchd_ier:
        InternalError   <w_ChDir/TransPathNoJoin>
EndProc w_ChDir,NoCheck


;***    w_MkDir  --  Make a directory entry
;
;       w_MkDir creates a new subdirectory.
;
;       ENTRY   DS:DX -> ASCIIZ name for new directory
;               BX:SI -> Extended Attribute (EA) structure (NULL means none)
;
;       EXIT    'NC' -  No Error
;
;               'C'  -  Error
;                       AX = ERROR_PATH_NOT_FOUND if path bad
;                       AX = ERROR_ACCESS_DENIED  If
;                            Directory cannot be created
;                            Node already exists
;                            Device name given
;                            Disk or directory(root) full
;                       AX = ERROR_FILENAME_EXCED_RANGE
;                            Pathname component too long
;
;       USES    CX
;
;       PSEUDOCODE
;          TransPath
;          if (no error & no metachar)
;             DOS_MkDir
;             if (no error)
;                return
;             else {
;                RlseBuffr
;                return error
;             }
;

        ASSUME  DS:NOTHING,ES:NOTHING,SS:TASKAREA
Procedure w_MkDir,<near,faronly,far16>
LocalVar  wmd_openbuf,PMPATHLEN
LocalVar  wmd_dummy,WORD                ; KLUDGE: see gshare.asm
LocalVar  wmd_dummycds,<SIZE curdir>
        PMONLY
        EnterProc
        lea     di,wmd_dummycds
        push    bx
        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,wmd_openbuf          ; spot for translated name
        mov     TASKAREA:[bx].TCBpOpenBuf,di            ; Initialize TCBpOpenBuf ;### PTR B720732
;### PTR B720732        mov     TASKAREA:[ptda_pOpenBuf],di ; Initialize ptda_pOpenBuf
        pop     bx
        SaveReg <SI,BX>
        MOV     SI,DX                   ; get source
        call    TransPath
        RestoreReg  <BX,SI>
        JC      wmd20

illegalname = PNType_LOCDEV or PNType_NMPIPE + PNType_PSDEV
                                        ;72417 moved from PTDA to TCB
        SetBP_CurrTCB
        test    TASKAREA:[bp].TCBPathNameType,illegalname ; illegal path?
        UnSetBP_CurrTCB
        jnz     wmd35                   ; yes, go error exit

        SetBP_CurrTCB
        CMP     [bp].TCBcMeta,-1
        UnSetBP_CurrTCB
                                        ;72417 end
        JNE     wmd30

; restrict subdir path length to <= MAXPATHLEN (260) bytes to prevent
; breaking findfirst and other path-based APIs and utilities (e.g. chkdsk).

        lea     di,wmd_openbuf          ; spot for translated name
        TaskContext ES                  ; es:di -> canned path
        CALLFAR StrLen                  ; cx = path length including nul
;
;   @DB76563 -
;   Choose the maximum curdir length that we can have, then determine if
;   we are going to exceed it.
;
        mov     dx,PMDIRSTRLEN
        cmp     ProcType,PT_REALMODE
        jne     wmkd5
        mov     dx,RMDIRSTRLEN          ; Max curdir length for 3x box
wmkd5:  cmp     cx,dx                   ; new curdir too long?
;
;   @DB76563 - End Insertion

        ja      wmd30                   ; yes, don't allow it

ifdef SECURITY
; See if security is interested                                           ;@SEC
        CpuMode 386                                                       ;@SEC
        FlatContext <ds>,cx                                               ;@SEC
        cmp     _fSec,0                                                   ;@SEC
        jz      wmdsec                                                    ;@SEC
        CALLFAR SecMakeDir                                                ;@SEC
        jnc     wmdsec                                                    ;@SEC
        jmp     wmd50                                                     ;@SEC

wmdsec:                                                                   ;@SEC
       TaskContext <ds>,cx                                                ;@SEC
       CpuMode Reset                                                      ;@SEC
endif

;
;
; Since this is a "path-based" call, there has been no referent put in the
; VPB for this call. We need to increment the ref-count in the VPB in order
; to ensure that the VPB does not go away while we are working on it.
;
        call    PathIncVPBRef           ; Increment reference count
        TaskContext DS
        CALL    FSD_MkDir               ; do work (Dos_RmDir or Dos_MkDir)
        call    PathDecVPBRef           ; Remove our referent in the VPB
    ASSUME      ES:NOTHING
        JC      wmd40                   ; jump down if error
        pushf
        push    ax                      ;### Notify
        mov     ax,3                    ;### Mkdir
        CALLFAR CNotify_Path_Wake       ;### Notify
        pop     ax                      ;### Notify
        popf
wmdx:   LeaveProc
        RET

wmd20:  CMP     AX,ERROR_FILENAME_EXCED_RANGE
        JE      wmd50                   ; Don't translate file too long err

wmd30:  MOV     AX,ERROR_PATH_NOT_FOUND
        JMP     SHORT wmd50

wmd35:  mov     ax,ERROR_ACCESS_DENIED
        jmp     short wmd50

wmd40:  CALL    RlseBuffr               ; release buffer from Dos_MkDir
wmd50:  STC
        jmp     short wmdx              ; return with 'C', ax = error code

EndProc w_MkDir

;***    FSD_MkDir - make directory
;
;       ENTRY   [WFP_Start] Points to WFP string ("d:/" must be first 3
;                       chars, NUL terminated)
;               [CURR_DIR_END] Points to end of Current dir part of string
;                       ( = -1 if current dir not involved, else Points
;                        to first char after last "/" of current dir part)
;               [ThisCDS] Points to CDS being used
;                       (Low word = -1 if NUL CDS (Net direct request))
;               [TCBThisFSC] = FSC of directory path
;               BX:SI -> Extended Attribute (EA) structure (NULL means none)
;
;       EXIT    if 'NC' - success
;               if 'C' - error
;                 ax = error code
;
;       USES    ax,bx,cx,dx,si,di,ds,es,flags

Procedure FSD_MkDir
    assume ds:nothing,es:nothing
        mov     di,[CurrTCB]            ; ss:si -> TCB
        les     di,TASKAREA:[di].TCBThisFSC     ; ds:si -> FSC
        cmp     di,FSC_LOCAL            ; what file system?
        jb      fmd20                   ; fsd, go call fsd worker

; call FAT worker.  Do special shutdown processing.

        push    GSEL GDT_FSC
        pop     es

        cmp     es:[fss_ShutdownFlags],NotShutdown
        jne     short fmd06

fmd02:  inc     es:[fss_SDWaitCount]
        SaveReg <es,bp>
        CALLFAR DOS_MkDir
        RestoreReg <bp,es>
        pushf

        cmp     es:[fss_ShutdownFlags],NotShutdown
        jne     short fmd08

        dec     es:[fss_SDWaitCount]
fmd04:  popf
        ret

fmd06:  invoke  ShutdownCheck           ; handle shutdown detection on entry
        jmp     short fmd02

fmd08:  dec     es:[fss_SDWaitCount]    ; handle shutdown detection on exit
        jnz     short fmd04

        invoke  WakeShutdownThread      ; we must wake up the shutdown thread
        jmp     short fmd04

; setup call to FS_MKDIR
; es:di -> FSC
; bx:si -> EA struc

fmd20:  mov     dx,si                   ; bx:dx -> EA struc (free 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 -> CDS
        lea     ax,[si].cd_fsi          ; ds:ax -> cd_fsi
        push    ds
        push    ax                      ; push pointer to cd_fsi
        lea     ax,[si].cd_fsd          ; ds:ax -> cd_fsd
        push    ds
        push    ax                      ; push pointer to cd_fsd
        push    ss
                                        ;72417 moved from PTDA to TCB
        mov     si,bp
        mov     bp,CurrTCB
        push    [bp].TCBWFP_Start       ; push pointer to directory name
        mov     ax,[bp].TCBCurr_Dir_End ; ax = -1 or -> end of dir
        cmp     ax,-1                   ; ax = -1?
        je      fmd30                   ; yes, use -1 as iCurDirEnd
        sub     ax,[bp].TCBWFP_Start    ; no, compute iCurDirEnd
fmd30:  push    ax                      ; push iCurDirEnd
        push    bx
        push    dx                      ; push pointer to EA struc
        mov     al,TASKAREA:[bp].TCBPathNameType
        mov     bp,si
                                        ;72417 end
        and     ax,PNType_NEWFILE       ; ax = 0 or 0x0040
        push    ax                      ; flags (dir attributes)
        call    es:[di].FS_MKDIR        ; call fsd worker
        xor     bx,bx
        cmp     bx,ax                   ; set carry if ax != 0
        ret

EndProc FSD_MkDir


;***    FSD_ChDir - Change/Verify Current Directory (routing level)
;
;       FUNCTION
;
;           This routine handles the routing to the proper FSD (, FAT, or
;           redirector) for ChDir & CurDir (and related functions).
;
;       ENTRY   [WFP_Start]
;                 CHDIR_SET    -> Potential new Current Directory
;                 CHDIR_VERIFY -> Current Directory path (for DOS_ChDir)
;               [CURR_DIR_END]  = offset to 1st character past current directory
;                                 (-1 => current directory not applicable)
;               [ThisCDS]      -> current CDS (Must not be NULL)
;               [TCBThisFSC]   -> FSC
;               AX              = OpFlag (CHDIR_SET or CHDIR_VERIFY)
;
;       EXIT    'NC' - No Error
;
;               'C'  - Error
;                   AX = error code
;
;       USES    AX, BX, CX, DS, DI, SI, BP, DS, ES and flags
;
;       PSEUDOCODE {
;
;           if (root)
;               return                              /* don't bother with root */
;
;           if (FSC.Offst = FSC_LOCAL)              /* FAT case */
;               DOS_ChDir (OpFlag)
;           else
;                                              /* fsd case */
;                   if (CHDIR_SET) {
;                       ICurDirEnd = CurDirEnd - WFP_Start
;                       FSC.FS_ChDir (CHDIR_SET, pcdfsi, pcdfsd, pDir, iCurDirEnd)
;                   }
;                   else
;                   if (CHDIR_VERIFY) {
;                       if (cd_OwnerFSC != FSC)     /* not the same FSD */
;                           FSD_ChDirFree           /* clean up, if needed */
;
;                       FSC.FS_ChDir (CHDIR_VERIFY, pcdfsi, pcdfsd, NULL, 0)
;                   }
;           return
;       } /* FSD_ChDir */

Procedure FSD_ChDir,NEAR

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

;   We don't have to validate a root CurDir ("d:\")

        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

;   If this is a set, we must examine WFP (to determine if root)

        cmp     ax,CHDIR_SET
        jne     fcd03                               ; not Set, go on

                                                    ;72417 moved from PTDA to TCB
        mov     di,CurrTCB
        mov     di,[di].TCBWFP_Start                ; SS:DI -> WFP
                                                    ;72417 end
        add     di,ds:[si.cd_fsi].cdi_end           ; SS:DI -> end of root
        test    BYTE PTR ss:[di],-1                 ; check for null
        LJZ     fcd80                               ; null means root case
        test    BYTE PTR ss:[di+1],-1               ; check for null (next char)
        jnz     fcd06                               ; not null, not root

        or      ds:[si.cd_fsi].cdi_flags,CDI_ISROOT ; mark as root
        jmp     fcd80                               ; root, just exit

;   If this is a verify, we just look at the CDS (to check for root)

fcd03:  cmp     ax,CHDIR_VERIFY                     ; check for Verify
        LJNE    fcdie                               ; better be Verify

        test    ds:[si.cd_fsi].cdi_flags,CDI_ISROOT ; check for root CDS
        LJNZ    fcd80                               ; root, just return

;   Get the FSC pointer

fcd06:  mov     di,CurrTCB
        les     di,ss:[di].TCBThisFSC               ; ES:DI -> FSC

;   If this is a verify, we must handle an old FSD if CDS has one

        cmp     ax,CHDIR_VERIFY                     ; is this a verify
        jne     fcd09                               ; no, go on

        cmp     di,ds:[si].cd_OwnerFSC              ; is FSC == OwnerFSC
        je      fcd09                               ; Same FSC, go on

        SaveReg <es,di,ax>
        SaveReg <ds,si>

        mov     ax,ds:[si].cd_handle                ; AX = CDS handle
        call    FSD_ChDirFree                       ; go tell old FSD

;   We must zero out the cd_fsd, because each FSD can use a different structure

        RestoreReg  <di,es>                 ; ES:DI -> CDS
        SaveReg <es,di>
        LEA     DI,[DI.cd_fsd]              ; ES:DI -> CDS.cd_fsd
        XOR     AX,AX
        MOV     CX,SIZE cdfsd
        SHR     CX,1                        ; convert to words (+ carry if odd)
    REP STOSW
        JNC     fcd07                       ; byte count is even, we're done
        STOSB                               ; store odd byte

fcd07:  RestoreReg  <si,ds>                 ; DS:SI -> CDS

        RestoreReg  <ax,di,es>              ; ES:DI -> FSC, AX = OpFlag

        and     ds:[si].cd_fsi.cdi_flags,NOT CDI_ISVALID    ; mark invalid

fcd08:  mov     ds:[si].cd_OwnerFSC,di              ; update the owner

;   Now we can handle the normal routing

fcd09:  cmp     di,FSC_RESERVED                     ; check for special cases
        jb      fcd30                               ; FSD to handle it

;   FAT case - DOS_ChDir does the work - do special Shutdown processing
; ShutdownGate  function name, exit label, seg with FSC segment (DS or ES)

        ShutdownGate  DOS_ChDir, fcd20, ES

; the above jumps to fcd20 on completion
    ASSUME  DS:NOTHING

fcd20:  ret                                         ; done, return

;   FSD case - call FSD to handle request

;   push opflag (for both operations)

fcd30:  push    ax                                  ; push opflag
        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

;   Push pointer to fs independent part of CDS

        push    ds
        lea     bx,ds:[si].cd_fsi
        push    bx                                  ; push pcdfsi

;   For both operations, push pointer to fs dependent part of CDS

        push    ds
        lea     bx,ds:[si].cd_fsd
        push    bx                          ; push pcdfsd (for all three cases)

;   Third & fourth parameters varies depending on the operation

        cmp     ax,CHDIR_SET                        ; check for SET
        jne     fcd60                               ; not set, go push 0s

;   For Set, push pointer to CurDir (ASCIIZ) string & Curr_Dir_End (sort of)

        push    ss
                                                    ;72417 moved from PTDA to TCB
        mov     cx,bp
        mov     bp,CurrTCB
        push    [bp].TCBWFP_Start                   ; push pDir
        mov     ax,[bp].TCBCurr_Dir_End
        cmp     ax,-1                               ; is it meaningful
        je      fcd50                               ; no, just push it (-1)
        sub     ax,[bp].TCBWFP_Start                ; calculate iCurDirEnd
fcd50:  push    ax                                  ; push iCurDirEnd
        mov     bp,cx
                                                    ;72417 end
        jmp     short fcd70                         ; go to call

;   For Verify, push 0s for pointer & iCurDirEnd

fcd60:  xor     ax,ax                               ; (AX) = 0
        push    ax                                  ; push NULL pDir
        push    ax
        push    ax                                  ; push iCurDirEnd = 0

;   All operations make this call!  (common code)

fcd70:  call    es:[di].FS_CHDIR                    ; call FSD
        xor     cx,cx                               ; check for error
        cmp     cx,ax                               ; 'C' if AX != 0

fcd80:  ret

fcdie:  InternalError <FSD_ChDir - invalid OpFlag>

EndProc FSD_ChDir

;***    FSD_ChDirFree - Free Current Directory (CDS) - (routing level)
;
;       FUNCTION
;
;           This routine handles the routing to the proper FSD (FSD or FAT)
;           for DelCDS (and FSD_ChDir FSD changing).  If the
;           ownerFSC = FSC_BAD, then the fs dependent part is invalid
;
;       ENTRY   AX = CDS Handle
;
;       EXIT    'NC' - No Error
;
;       USES    AX, BX, CX, DX, DI, SI, DS, ES and flags
;
;       PSEUDOCODE {
;
;           if (handle > NUMDRIVE) {                        /* normal handle */
;               GetCDS                                      /* get RMP_CDS */
;               if (cd_OwnerFSC != FSC_BAD)                 /* owner valid? */
;                   if (!CDI_ISROOT) {                      /* skip if root */
;                       Copy cd_fsd to local var
;                       Save cd_ownerFSC
;                       RMP_CDS.cd_ownerFSC = FSC_BAD       /* mark it clean */
;                       RMP_CDS.CDI_ISVALID = FALSE
;                       /* we have copy of CD_fsd, so free works without conflict */
;
;                       if (SavedOwner = FSC_LOCAL) {       /* FAT case */
;                           DOS_ChDir (CHDIR_FREE)
;                           if ('C')
;                               InternalError
;                       }
;                       else {                              /* fsd case */
;                           SavedOwner.FS_ChDir (CHDIR_FREE, NULL, pcdfsd, NULL, 0)
;                           if ('C')
;                               InternalError
;                       }
;                   }
;           }
;           return
;       } /* FSD_ChDirFree */

Procedure FSD_ChDirFree,NEAR

LocalVar    FCDF_SavedOwner,WORD
LocalVar    FCDF_cd_fsd, <SIZE cd_fsd>

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

        EnterProc

        cmp     ax,NUMDRIVE                     ; is it a normal handle
        jbe     fcdf40                          ; no, just return

        call    GetCDS                          ; ES:DI -> RMP_CDS
        jc      fcdfie                          ; CDS had better be there

        cmp     es:[di].cd_OwnerFSC,FSC_BAD     ; check for work to do
        je      fcdf50                          ; bad, no work, return

        test    es:[di.cd_fsi].cdi_flags,CDI_ISROOT     ; is this a root
        jnz     fcdf40                          ; it is a root, just return

        mov     bx,FSC_BAD
        xchg    es:[di].cd_OwnerFSC,bx          ; mark no owner; BX = old owner
        mov     FCDF_SavedOwner,bx              ; save old owner

        and     es:[di.cd_fsi].cdi_flags,NOT CDI_ISVALID    ; mark invalid

        SaveReg <es,di>
        RestoreReg  <si,ds>                     ; DS:SI -> RMP_CDS
        lea     si,[si].cd_fsd                  ; DS:SI -> RMP_CDS.cd_fsd

        TaskContext es
        lea     di,FCDF_cd_fsd                  ; ES:DI -> local cd_fsd
        mov     cx,SIZE cdfsd                   ; CX = number of bytes tomove
        shr     cx,1                            ; convert to words (+carry if odd)
    rep movsw
        jnc     fcdf10                          ; even # of bytes; done moving
        movsb                                   ; move odd byte

    ASSUME  ES:NOTHING

fcdf10: cmp     FCDF_SavedOwner,FSC_RESERVED    ; check for reserved cases
        je      fcdf60                          ; let FAT handle it

    ASSUME  DS:NOTHING

;   FSD case - call FSD to handle request

;   push opflag (for all three operations)

fcdf20: mov     di,GSEL GDT_FSC
        mov     es,di
        mov     di,FCDF_SavedOwner          ; ES:DI -> FSC

        push    CHDIR_FREE                  ; push opflag

;   For Free, just push a NULL pointer

        xor     ax,ax                       ; (AX) = 0
        push    ax                          ; push NULL pcdfsi
        push    ax

;   Push pointer to fs dependent part of CDS

        push    ss
        lea     bx,FCDF_cd_fsd
        push    bx                          ; push pcdfsd

;   Push 0s for pointer & Curr_Dir_End

        push    ax                          ; push NULL pDir
        push    ax
        push    ax                          ; push iCurDirEnd = 0

        call    es:[di].FS_CHDIR            ; call FSD
                                            ; Ignore any errors from FSD.
fcdf40: clc                                 ; No error
fcdf50: LeaveProc
        ret

fcdf55: jnc     fcdf40                      ; No error. Return.
fcdfie: InternalError <FSD_ChDirFree - GetCDS failed>

; This is at the end because it removes the need for 4 LJxx's

;   FAT case - DOS_ChDir does the work - do special Shutdown processing
; ShutdownGate  function name, exit label, seg with FSC segment (DS or ES)

fcdf60: mov     ax,CHDIR_FREE               ; AX (OpFlag) = 2
        ShutdownGate  DOS_ChDir, fcdf55, ES

; the above jumps to fcdf55 on completion

EndProc FSD_ChDirFree


HIGH2CODE ENDS
        END
