;       SCCSID = @(#)fsctl.asm  6.5 91/11/18
TITLE   FSCTL - FSD control and assorted FSD utilities
NAME    FSCTL

SCCSID  equ     "@(#)fsctl.asm  6.5 91/11/18"

;       IBM/Microsoft Confidential
;
;       Copyright (c) IBM Corporation 1987, 1989
;       Copyright (c) Microsoft Corporation 1987, 1989
;
;       All Rights Reserved
;
;   These routines are responsible for attaching and detaching FSDs,
;   as well as querying FSD information, and doing generic FSD control
;   functions similar to IOCtl functions.
;
;   Routines included in this module: (order of appearance)


;   PUBLIC            LOCAL
;-------------------------------------------

;   w_FSCtl
;                     AttachToFSD
;                     AttachDevice
;                     AttachDrive
;                     call_attach_fsd
;                     DetachFromFSD
;                     RemCharDevEntry
;   w_qFSAttach
;                     q_dev_drv
;                     fillin_dev
;                     fillin_drv
;                     q_ord_dev
;                     q_ord_drv
;                     call_query_fsd
;                     ord_FindCharDevEntry
;                     ord_FindDrive
;                     load_2_fields
;                     load_3_fields
;                     fill_fields_0
;                     fill_fields_FAT
;                     update_drv
;   w_FSAttach
;                     handle_route
;                     path_route
;                     fsd_route
;                     call_control_fsd
;   FSD_FSCtl
;   DOS_FSCtl
;   $QMaxEASize
;
;   MODIFICATION HISTORY:
;    - 11/27/95 - MLM defect 72417
;                 moved from PTDA to TCB:
;                 WFP_Start, Curr_Dir_End, cMeta

include fnofs.inc
include dosseg.inc
.xlist
.xcref
include basemaca.inc
include osmaca.inc
include oldmaca.inc
include argframe.inc
include chardd.inc
include curdir.inc
include dossymc.inc
include dpb.inc
include error.inc
include extattr.inc
include filesys.inc
include fsc.inc
include inbuf.inc
include mi.inc
include protmode.inc
include sft.inc
include tkexport.inc
include vpb.inc
include osconst.inc
include pgexport.inc
include vmexport.inc
include codelock.inc
include rmp.inc
.cref
.list
        ModName , FNO_FSCTL_ASM

        ReferTask       CurrTCB,WORD            ; current thread ctl block

                                                ;72417 removed PTDA entries now
                                                ;      in TCB

        ReferTask       CDS_Handle,WORD         ; table of CDS handles

        ReferGlobal     CharDevRMPRec,DWORD     ; device record seg.
        ReferGlobal     DevPrefix,BYTE          ; "\DEV\" string
        ReferGlobal     SpoolDevSem,DWORD
        ReferGlobal     uchLastDrive,BYTE

        ReferGDT        GDT_VPB,WORD
        ReferGDT        GDT_FSC,WORD

        EXTRNFAR        AddCharDevEntry
        EXTRNFAR        AddSpoolDevEntry
        EXTRNFAR        PathDecVPBRef
        EXTRNFAR        AddGlobalCDS
        EXTRNFAR        Canonicalize
        EXTRNFAR        CheckThisDevice
        EXTRNFAR        DelCDS
        EXTRNFAR        DecVPBRef
        EXTRNFAR        DriveFromText
        EXTRNFAR        DStrLen
        EXTRNFAR        FindCharDevEntry
        EXTRNFAR        FindSpoolDevEntry
        EXTRNFAR        FreeTempBuffer
        EXTRNFAR        fscFind
        EXTRNFAR        FStrCpy
        EXTRNFAR        FuWord
        EXTRNFAR        FuBuffz
        EXTRNFAR        GenRemotehVPB
        EXTRNFAR        GetCDS
        EXTRNFAR        GetCDSFromDrv
        EXTRNFAR        GetTempBuffer
        EXTRNFAR        GetVisDrv
        EXTRNFAR        NameTrans
        EXTRNFAR        PathIncVPBRef
        EXTRNFAR        PathPref
        EXTRNFAR        ProbeBuff
        EXTRNFAR        RemCharDevEntry
        EXTRNFAR        RemSpoolDevEntry
        EXTRNFAR        Rmp_Wait
        EXTRNFAR        Rmp_FindN
        EXTRNFAR        ScanCDS
        EXTRNFAR        ScanPathChar
        EXTRNFAR        SetCharDevFields
        EXTRNFAR        SFFromHandleLock
        EXTRNFAR        SFFromHandle
        EXTRNFAR        SFLock
        EXTRNFAR        SFUnlock
        EXTRNFAR        StrCmp
        EXTRNFAR        StrCpy
        EXTRNFAR        StrLen
        EXTRNFAR        SuBuff
        EXTRNFAR        SuBuffz
        EXTRNFAR        SuByte
        EXTRNFAR        SuWord
        EXTRNFAR        SuDWord
        EXTRNFAR        TextFromDrive
        EXTRNFAR        $TJMP
        EXTRNFAR        TransPath
        EXTRNFAR        canpath
        EXTRNFAR        WhatVolume
        EXTRNFAR        VPBFree
        EXTRNFAR        Clear_Kernel_Sem
        EXTRNFAR        ScanTasks
        EXTRNFAR        Take_Kernel_Sem
        EXTRNFAR        CNotify_Path_Wake
        EXTRNFAR        fscNext
        EXTRNFAR        FreeDriveLetter     ;99999
        EXTRNFAR        AllocateDriveLetter ;99999

        EXTRN32         CodeLockProc
        EXTRN32         CodeUnlockProc
        EXTRN32         _KSEMRequestMutex
        EXTRN32         _KSEMReleaseMutex


FSD_ATTACH      equ  0
FSD_DETACH      equ  1
FSD_SPOOLATTACH equ  2
FSD_SPOOLDETACH equ  3

FSD_QUERY     equ  2

FSD_DEVICE    equ  0
FSD_DRIVE     equ  1

RES_CHAR      equ  1
PSEUDO_CHAR   equ  2
LOCAL_DRIVE   equ  3
REMOTE_DRIVE  equ  4

INFO_LEV1     equ  1
INFO_LEV2     equ  2
INFO_LEV3     equ  3

LENGTH_0_FIELDS         equ 5
LENGTH_WORD_FIELD       equ 2
FIRST_2_FIELDS_LEN      equ 4
LENGTH_2WORD_FIELD      equ 4

    public    FAT_FSD_name
    public    UNKNOWN_FSD_name
DATA segment

UNKNOWN_FSD_name  db   'UNKNOWN',0
UNKNOWN_FSD_name_len  equ   $ - UNKNOWN_FSD_name
LEN_UNK_RESIDENT_FIELDS       equ UNKNOWN_FSD_name_len + 4   ; string + 2 words

FAT_FSD_name    db   'FAT',0
FAT_FSD_name_len  equ $ - FAT_FSD_name
LENGTH_RESIDENT_FIELDS equ FAT_FSD_name_len + 4 ; string + 2 words

    public  PRN_Device_Name,LPT1_Device_Name
PRN_Device_Name    db      'PRN',0
LPT1_Device_Name    db     'LPT1',0

users_buflen    dw   0               ; length of user's buf for Qattach
DATA ends

argdat_union  STRUC
au_pfsi      dd   0
au_pfsd      dd   0
au_ppath      dd   0
au_icde      dw   0
argdat_union  ENDS


             FARCODE    HIGH4
NMPIPESWAPCODE    SEGMENT

        PUBLIC  _StartFSCTLGROUP
_StartFSCTLGROUP label byte

;***    w_FSAttach - Attach Drive/pseudo-Char Device to FSD.
;
;   w_FSAttach allows the attachment of a drive to a remote FSD, or a
;   pseudo-char device name to a local or remote FSD.
;   If we are attaching a drive letter to an FSD, we want to make sure that
;   there is not already a drive by that name. This is ascertained from the
;   flags field in the CDS structure.  Otherwise, we can get a drive number from
;   the ASCIIZ drive name, and obtain/create a CDS structure from the drive
;   number.  Once we have the CDS, its independent part has a pointer to an FSC.
;   Using the FSC, we can get a pointer for the FSD.  We then fill in
;   the FSC portion of the CDS with the FSC from fscFind.  There are other
;   fields that need to be filled in in the CDS, ie. cdi_end, cdi_text,
;   cd_handle, cd_flags, etc.
;   If we are attaching a pseudo-char device to an FSD, we need to remap the
;   already existing installed device with the pseudo device.  Already existing
;   pseudo devices cannot be remapped, they must be detached, and then re-
;   attached.  This holds true only for protect mode devices, as real mode
;   devices cannot be remapped.  AddCharDevEntry is the worker here.
;   For detaching a drive letter, we must get the CDS for that drive.  Then we
;   can, after checking that it is not the prefix of any other CDS, remove it
;   completely.  If there is someone mucking around in that CDS, or that CDS is
;   the prefix of another, we must fail the attempt to detach at that time.
;   For detaching a pseudo-device, we call RemCharDevEntry which does all the
;   dirty work for us.
;
;
;   INPUT     AX     ===   OpFlag
;                           0 = attach
;                           1 = detach
;                           2 = register   (spooler)
;                           3 = deregister (spooler)
;            DS:SI  -->   FSDName
;            SS:BX  -->   parameter packet (see FSAttachStruc)
;
;   EXIT      'NC' if successful
;             'C' if error
;                 AX = error_invalid_drive
;                       error_invalid_path
;                       error_invalid_fsd_name
;                       error_invalid_level
;                       error_buffer_overflow
;
;   CALLS     fscFind, PathPref, AttachToFSD, DetachFromFSD
;
;   USES  AX, BX, CX, DX, DI, SI, ES, DI, FLAGS
;
;   ALGORITHM
;   if opflag out of range, error
;   find FSC
;   if no FSC, error
;   if devicename != "d:"
;        if devicename != "\dev\devicename", error
;   if attach, call AttachToFSD
;   else, call DetachFromFSD
;

        ASSUME DS:NOTHING, ES:NOTHING, SS:TASKAREA
Procedure w_FSAttach,FARONLY
LocalVar fsa_drvflag,WORD               ; flag = FSD_DEVICE(0) or FSD_DRIVE(1)
LocalVar fsa_fscptr,DWORD               ; ptr to FSC
LocalVar fsa_openbuf,PMPATHLEN          ; temp buffer
LocalVar fsa_dummy,WORD                 ; KLUDGE: see gshare.asm
LocalVar fsa_dummycds,<SIZE curdir>

;        PMONLY

        CodeLock FSCTL_GROUP

        EnterProc <FSD_DEVICE>          ; FLAG = device to start
        push    bx
        mov     bx,TASKAREA:[CurrTCB]                   ;### PTR B720732
        lea     di,fsa_openbuf
        mov     TASKAREA:[bx].TCBpOpenBuf,di            ; Initialize TCBpTmpCDS ;### PTR B720732
;### PTR B720732        mov     TASKAREA:[ptda_pOpenBuf],di ; Initialize ptda_pOpenBuf
        lea     di,fsa_dummycds
        mov     TASKAREA:[bx].TCBpTmpCDS.Offst,di       ; Initialize TCBpTmpCDS ;### PTR B720732
;### PTR B720732        mov     TASKAREA:[ptda_pCDS],di ; Initialize ptda_pCDS
        pop     bx                                      ;### PTR B720732

; check the opflag for range error

.errnz (FSD_SPOOLDETACH - 3)
        cmp     ax,FSD_SPOOLDETACH          ; check range of opflag
        ja      fsa10                       ; error bad level
        cmp     ax,FSD_SPOOLATTACH          ; Spooler operation?
        jb      fsa02                       ; no

        call    SpoolAttachDetach           ; Do spooler operation
        jmp     short fsa08

; find the FSC entry associated with the FSD passed in.

fsa02:  SaveReg <ax>
        CALLFAR fscFind                     ; ES:DI -> FSC
        RestoreReg <ax>
        jc      fsa14                       ; CY = error, bad fsd
        mov     fsa_fscptr.Offst,di
        mov     fsa_fscptr.Segmt,es         ; save fsc ptr for future use
;
; We have been given a path.
;
; Check for "d:" or "\dev\devicename".
;
        lds     si,TASKAREA:[bx].FSA_szDeviceName   ; DS:SI -> device name
        call    Path_Check                  ; NC and ZR -> drive
        jc      fsa08                       ; error ; AX = error code
        jnz     fsa03                       ; device
; a drive letter is given for attach        ;
        SaveReg <si,ax>
        CALLFAR DriveFromText                   ; AL = 1 based drive #
                                                ; DS:SI -> beyond string
        dec     al                              ; AL = 0 based drive #
        SaveReg <ds>
        DosContext      DS
        cmp     al,[uchLastDrive]           ; are we allowed to attach for
        RestoreReg      <ds>
        RestoreReg      <ax,si>
        ja      fsa16                       ; this drive no ?
        inc     fsa_drvflag                 ; drvflag = DRIVE
;
; Since we know we have a drive, we will test the FSD to see if it can handle
; requests for REMOTE drives.  If the FS_ATTRIBUTE says LOCAL, then we can't
; do any work on REMOTE drives.
;
        les     di,fsa_fscptr               ; ES:DI -> FSC
        les     di,es:[di].FS_ATTRIBUTE     ; ES:DI -> FS_ATTRIBUTE
        test    word ptr es:[di],FS_ATTR_REMOTE
        jz      fsa14                       ; LOCAL FSD , error
                                            ; NZ = REMOTE FSD, ok
        les     di,fsa_fscptr               ; ES:DI -> FSC
;
; Determine whether to ATTACH or DETACH.
;   ax = 0 if attach, 1 if detach
;   ds:si -> device name/or drive (taskarea: openbuf)
;   es:di -> FSC
;   fsa_drvflag = 0 if device, 1 if drive
;
fsa03:  mov     dx,fsa_drvflag
        or      ax,ax
        jnz     fsa04
        call    AttachToFSD                 ; ATTACH!
        jmp     SHORT fsa08

fsa04:  call    DetachFromFSD               ; DETACH!

;### PTR B720732 fsa08: mov     SS:[ptda_pOpenBuf],0        ; Clear ptda_pOpenBuf
;### PTR B720732        mov     SS:[ptda_pCDS],0            ; Clear ptda_pCDS
fsa08:  LeaveProc
        CodeUnlock FSCTL_GROUP
        ret

fsa10:  mov     ax,ERROR_INVALID_LEVEL      ; bad opflag error
        jmp     SHORT fsa17

fsa12:  mov     ax,ERROR_INVALID_PATH       ; bad path
        jmp     SHORT fsa17

fsa14:  mov     ax,ERROR_INVALID_FSD_NAME   ; else,no FSD by that name
        jmp     SHORT fsa17

fsa16:  mov     ax,ERROR_INVALID_DRIVE      ; bad drive
fsa17:  stc
        jmp     SHORT fsa08


EndProc  w_FSAttach



;***    SpoolAttachDetach - SpoolAttach/SpoolDetach.
;
;   INPUT     AX:       Operation
;                           2 == Attach
;                           3 == Detach
;             SS:BX  -->   parameter packet (see FSAttachStruc)
;
;   EXIT      CY - if error
;                  AX = error code
;
;   ALGORITHM
;
;       if (arguments ok) {
;           Request_Semaphore;
;           Look for devicename in Spool record segment.
;           if (record found)
;               if (operation == SPOOLDETACH) {
;                   Free spool object entry;
;               } else error;
;           else
;               if (operation == SPOOLATTACH) {
;                   Create spool object entry;
;               } else error;
;           Release Semaphore;
;       } else error
;
        ASSUME DS:NOTHING, ES:NOTHING, SS:TASKAREA
Procedure SpoolAttachDetach,NEAR
LocalVar   sad_Oper,WORD                        ;   Operation
LocalVar   sad_SpoolAt,DWORD                    ;   Pointer to SpoolAttach structure
LocalVar   sad_TempBuf,DWORD                    ;   Pointer to Temp Buffer
LocalVar   sad_DevName,DWORD                    ;   Pointer to Dev Name

        EnterProc <ax>

        push    bx
        push    bx
        CALLFAR GetTempBuffer                   ; ES:DI -> temp buffer
        pop     bx
        mov     sad_TempBuf.Segmt,es
        mov     sad_TempBuf.Offst,di            ; Save the pointer
        cmp     [sad_Oper], FSD_SPOOLDETACH     ; Detach operation?
        je      sad10                           ; yes, ignore parameters

; Check parameter packet

        mov     ax, ss:[bx].FSA_cbDataLength    ; AX:  Buffer length
        cmp     ax, (size Struct_SpoolAttach)   ; correct size ?
        jne     sad96                           ; No

        mov     ds, ss:[bx].FSA_pDataBuffer.Segmt
        mov     si, ss:[bx].FSA_pDataBuffer.Offst ; DS:SI -> Structure
        mov     sad_SpoolAt.Segmt, ds
        mov     sad_SpoolAt.Offst, si

; Make sure name is pseudo-device
;
sad10:  pop     bx
        lds     si,TASKAREA:[bx].FSA_szDeviceName   ; DS:SI -> device name
        call    Path_Check
        jc      sad95                           ; not valid
        jz      sad95                           ; not valid
        mov     sad_DevName.Segmt,ds
        mov     sad_DevName.Offst,si            ; Save pointer to name

;   Request SpoolDev Semaphore
;
        SaveReg <ds,es,ax>
        CpuMode 386
        FlatContext <ds, es>, ax
        CCALL32  _KSEMRequestMutex,<<offset FLAT:SpoolDevSem>,-1>
        CpuMode Reset
        RestoreReg <ax,es,ds>

;   See if device is spooled
;   DS:SI -> device name

        CALLFAR FindSpoolDevEntry               ; DS:SI -> Spool entry record
        jc      sad50                           ; device is not spooled
;
; Device is spooled
;
        cmp     [sad_Oper], FSD_SPOOLDETACH     ; Is this a detach operation?
        jne     sad92                           ; No, invalid operation

        lds     si, sad_DevName                 ; DS:SI -> Device name
        CALLFAR RemSpoolDevEntry
        jmp     short sad80

;
; Device is not spooled
;
sad50:  cmp     [sad_Oper], FSD_SPOOLATTACH     ; Is this an attach operation?
        jne     sad92                           ; No, invalid operation

        lds     si, sad_DevName                 ; DS:SI -> Device name
        les     di, sad_SpoolAt                 ; ES:DI -> Parameters
        SaveReg <es, di>
        CALLFAR AddSpoolDevEntry
        RestoreReg <di, es>
        jc      sad80
        mov     bx, es:[di].SA_hNmPipe          ; BX == handle of named pipe
        CALLFAR SFFromHandle                    ; ES:DI -> SFT of named pipe
        jc      sad80
        or      es:[di].sf_flags2, SFF2_SPOOLED ; set spool flag in SFT

;   Release SpoolDev Semaphore
;
sad80:  push    ax
        pushf
sad82:  CpuMode 386
        FlatContext <ds, es>, ax
        CCALL32  _KSEMReleaseMutex,<offset FLAT:SpoolDevSem>
        CpuMode Reset

sad90:  les     di,sad_TempBuf                  ; ES:DI -> temp buffer
        CALLFAR FreeTempBuffer                  ; Free the temp buffer
        popf
        pop     ax
        LeaveProc
        Ret

;   Error cases

sad92:  mov     ax, ERROR_INVALID_LEVEL
        stc
        jmp     short sad80


sad95:  mov     ax,ERROR_INVALID_PATH
        jmp     short sad98

sad96:  pop     bx
        mov     ax,ERROR_INVALID_LEVEL
sad98:  push    ax
        stc
        pushf
        jmp     short sad90

EndProc SpoolAttachDetach



;***    AttachToFSD - Attach Drive/pseudo-Char Device to FSD.
;
;       If we are attaching PRN or LPT1, we need to do some special work
;       since the two names are interchangeable. We first attach LPT1 by
;       calling the FS_ATTACH entry point for the FSD. Then, for PRN, we
;       just make another device entry but copy over all the information
;       from the LPT1 entry to the PRN entry.
;
;   INPUT     ES:DI  -->   FSC for FSD
;             DS:SI  -->   "devicename" or "d:"
;             SS:BX  -->   parameter packet (see FSAttachStruc)
;             DX = FSD_DEVICE(0) if device
;                  FSD_DRIVE(1) if drive
;
;   EXIT      CY - if error
;                  AX = error code
;
;   ALGORITHM
;
;       if (drive)
;           AttachDrive()
;       else
;           if (PRN or LPT1)
;               AttachDevice(LPT1,CALLFSD)
;               if no error
;                   AttachDevice(PRN,!CALLFSD)
;                   if error
;                       DetachDevice(LPT1,CALLFSD)
;
;
;   USES  AX, BX, CX, SI, DI, DS, ES, FLAGS
;
        ASSUME DS:NOTHING, ES:NOTHING, SS:TASKAREA
Procedure AttachToFSD,NEAR
LocalVar        atf_fPRNDevice,WORD         ; TRUE if we are doing attach for PRN or LPT1
LocalVar        atf_pFSC,DWORD              ; Pointer to FSC
LocalVar        atf_offArgFrame,WORD        ; Offset of argument frame on stack (FSAttachStruc)

        or      dx,dx                   ; Drive or device?
    .errnz (FSD_DEVICE)
        jnz     afsd45                  ; Drive.
;
; Try to attach the device.
;
; We need to handle the case of PRN and LPT1 specially since the names refer
; to the same device. What we do is that if the name specified is LPT1 or PRN,
; we first call AttachDevice for PRN and then call it again for LPT1. This will
; ensure that we always check for the existence of the device in the right
; order. It will also assure us that if PRN does not exist, then LPT1 will not
; exist either. Some cleaning up is necessary if the attach for PRN works but
; the one for LPT1 fails for some reason.
;
        EnterProc <0,es,di,bx>          ; Assume not for PRN or LPT1

;
; Check to see if it is for PRN or LPT1
;
        DosContext es
        lea     di,es:[PRN_Device_Name] ; ES:DI -> "PRN"
        CALLFAR StrCmp                  ; Attaching PRN?
        jz      afsd5                   ; Yes. Do special attach
        lea     di,es:[LPT1_Device_Name]; ES:DI -> "LPT1"
        xor     ax,ax                   ; Assume we have to call FS_ATTACH
        CALLFAR StrCmp                  ; Attaching LPT1?
        jnz     afsd20                  ; No. Contiune normal attach.
afsd5:  DosContext ds
        lea     si,[LPT1_Device_Name]   ; DS:SI -> "LPT1"
        les     di,atf_pFSC             ; ES:DI -> FSC
        call    AttachDevice            ; Attach LPT1
        jc      afsd40                  ; Error. Return to caller.
        or      atf_fPRNDevice,1        ; Remember that this is a special attach
        DosContext ds
        mov     ax,1                    ; We don't call FS_ATTACH for this one
        lea     si,[PRN_Device_Name]    ; DS:SI -> "PRN"
        mov     bx,atf_offArgFrame      ; SS:BX -> FSAttachStruc
;
; SS:BX -> FSAttachStruc
; DS:SI -> name of device to attach
; AX = flag for whether to call FS_ATTACH or not (==1 if we should not call FSD)
;     If AX == 1, CX:DX = devfsd entry for LPT1 returned by FSD to use for PRN.
;
afsd20: les     di,atf_pFSC             ; ES:DI -> FSC
        call    AttachDevice            ; Attach the device
        jnc     afsd40                  ; Attach was successful. Return.
;
; The attach operation was not successful. If we failed and atf_fPRNDevice
; indicates that we already attached PRN, then we have to detach PRN before
; returning the error.
;
        test    atf_fPRNDevice,1        ; Special attach for PRN?
        jz      afsd35                  ; No. Return error to caller.
;
; We need to detach LPT1.
; We set up the parameters for the call to DetachDevice to call the FSD.
;
        SaveReg <AX>                    ; Preserve error code from AttachDevice
        mov     bx,atf_offArgFrame      ; SS:BX -> FSAttachStruc
        DosContext ds
        lea     si,[LPT1_Device_Name]   ; DS:SI -> "LPT1"
        les     di,atf_pFSC             ; ES:DI -> FSC
        mov     TASKAREA:[bx].FSA_iOpFlag,FSD_DETACH    ; OpFlag
        mov     TASKAREA:[bx].FSA_szDeviceName.Segmt,DS
        mov     TASKAREA:[bx].FSA_szDeviceName.Offst,SI ; DeviceName@
        xor     ax,ax                   ; We want to call the FSD
        call    DetachDevice
        RestoreReg <AX>                 ; AX = error code from AttachDevice
afsd35: stc
afsd40: LeaveProc
        jmp     short afsd50            ; Return any errors
;
;  Try to attach the remote drive.
;
afsd45: SaveReg <ds,si>
        call    AttachDrive
        RestoreReg <si,ds>
        jc      afsd50              ;### Notify
        pushf
        push    ax                      ;### Notify
        mov     ah, ds:[si]             ;### Get drive letter
        mov     al,0ah                  ;### Attach
        CALLFAR CNotify_Path_Wake       ;### Notify
        pop     ax                      ;### Notify
        popf
afsd50: ret

EndProc AttachToFSD



;***    AttachDrive - Attach Remote Drive to FSD.
;
;   FUNCTION  This procedure will attach a drive to a remote FSD.
;             It checks to make sure that the drive does not already exist.
;
;   INPUT     ES:DI  -->   FSC for FSD
;             DS:SI  -->   "drive name"
;             SS:BX  -->   parameter packet (see FSAttachStruc)
;
;   EXIT      CY - if error
;                  AX = error code
;
;   USES  AX, BX, CX, SI, DI, DS, ES, FLAGS
;
;   NOTE  attaching of local drives is not allowed
;
;   ALGORITHM
;
;       GetCDSFromDrive
;       fill in CDS
;       call call_attach_fsd
;       if successful
;           add_CDS
;
;
        ASSUME DS:NOTHING, ES:NOTHING; SS:TASKAREA
Procedure   AttachDrive, NEAR, LOCAL
LocalVar adrv_StackPtr,WORD             ; Pointer to user parms on stack
LocalVar adrv_vpb,<size vpb>    ; temporary VPB
LocalVar adrv_drive,BYTE                ; drive letter (ascii)

        EnterProc <BX>                  ; Store stack frame pointer
;
; If we are attaching a remote drive, we want to make sure that there is not
; already a drive by that name; we can't attach it unless it is
; detached first.  GetCDSFromDrv uses pCDS, not RMP, so it's OK to keep
; a pointer to CDS around.
;
        CALLFAR DriveFromText                   ; AL = 1 based drive #
                                                ; DS:SI -> beyond string
        dec     al                              ; AL = 0 based drive #
        CALLFAR GetCDSFromDrv                   ; DS:SI -> CDS
                                                ; AH=":" AL=0 based drive #
        LJC     adrv40                          ; CY = bad drive
;
; If inuse flag is 1, then drive can't be remapped
;
        test    ds:[si].cd_flags,CD_INUSE       ; physical drive
        LJNZ    adrv45                          ; error, bad drive
;
; Fill in the CDS structure with whatever we can
;   es:di -> FSC@
;   ds:si -> CDS structure
;   AX = "0 based drive#: "
;   DX = 0 if device, 1 if drive
;
        ; Call AllocateDriveLetter to mark this drive as in use @99999 begin
        mov     bx,1                            ; drive is remote
        CALLFAR AllocateDriveLetter
        LJC     adrv45                          ; error, drive got
                                                ;  taken away.  @99999 end
        mov     ds:[si.cd_devptr].Offst,di
        mov     ds:[si.cd_devptr].Segmt,es      ; CDS -> FSC
        mov     ds:[si].cd_OwnerFSC,di          ; save owner
ifdef FSSTRICT
        cmp     ds:[si].cd_flags,0                ; had better be 0
        jz      adrv16
InternalError <AttachDrive - new cds has non-zero flags word>
adrv16:
endif
        or      ds:[si].cd_flags,CD_ISNET+CD_INUSE  ; NET(remote)+INUSE
        or      ds:[si.cd_fsi].cdi_flags,CDI_ISROOT ; mark it root
        add     al,"A"                              ; AL = 1 based drive letter
        mov     adrv_drive,al                       ; remember drive letter
        mov     WORD PTR ds:[si.cd_fsi.cdi_text],ax
        mov     WORD PTR ds:[si.cd_fsi.cdi_text+2],"\"  ; text = "D:\ "
        mov     ds:[si.cd_fsi].cdi_end,CDI_END_ROOT     ; end -> "\"

        mov     cx,ds
        lea     si,[si].cd_fsd                      ; CX:SI -> CDS.cd_fsd
        mov     TASKAREA:[adrv_vpb.vpb_FSC.Offst],di
        mov     TASKAREA:[adrv_vpb.vpb_FSC.Segmt],es
        mov     dx,ss
        lea     ax,TASKAREA:[adrv_vpb.vpb_fsd]      ; (DX:AX) -> vpbfsd

;
; Set up for call to call_attach_fsd.
;
;   ES:DI -> FSC@
;   CX:SI -> CDS.cd_fsd
;   DX:AX -> VPBFSD
;
        mov     bx,adrv_StackPtr            ; SS:BX -> stack frame parms
        call    call_attach_fsd
;
; if we attached a drive successfully, we add the CDS to RMP
; if we attached a drive unsuccessfully, we leave
;
        jc      adrv30                      ; Unsuccessful. Go exit.

;
; ATTACHED a DRIVE successfully. Add CDS to RMP and VPB to BMP here.
; We can add the VPB to BMP without worrying about someone finding the VPB
; and trying to access the drive before it's ready because the only way a
; VPB for a remote drive can be found is through the CDS.
;
        lea     di,adrv_vpb                 ; (TASKAREA:DI) -> temp vpb
        mov     al,adrv_drive               ; (AL) = drive letter
        CALLFAR GenRemotehVPB               ; (AX) = hVPB
        jc      adrv60                      ; error,go detach drive

        mov     si,TASKAREA:CurrTCB             ; (DI) = current TCB     ;### PTR B720732
        lds     si,TASKAREA:[si].TCBThisCDS     ; (ES:DI) = CDS pointer  ;### PTR B720732
;### PTR B720732 lds    si,ThisCDS                  ; (DS:SI) -> CDS
        ASSUME DS:NOTHING, ES:NOTHING
        mov     [si].cd_fsi.cdi_hVPB,ax     ; store hVPB
        push    ax
        mov     cx,(cd_fsi.cdi_text + 4)    ; size of this CDS
        mov     al,adrv_drive
        sub     al,"A"-1
        xor     ah,ah                       ; AX = 1 based drive # (CDS handle)
        AssertNoBuf                         ; assure no outstanding buffer
        CALLFAR AddGlobalCDS                ; add the new CDS
        jc      adrv50                      ; CY = error no memory, go detach drive and unlink VPB
        pop     ax

adrv_ret:
        LeaveProc
        ret

adrv30: SaveReg <ax>                        ; @99999 begin
        mov     al, adrv_drive
        sub     al, "A"
        CALLFAR FreeDriveLetter             ; Free up the drive letter we allocated @99999.
                                            ; This is done as part of detach_rem_drive
                                            ; for the other error conditions
        RestoreReg <ax>                     ; @99999 end
        stc
        jmp     short adrv_ret

adrv40: mov     ax,ERROR_INVALID_DRIVE      ; Bad drive
        stc
        jmp     SHORT adrv_ret

adrv45: mov     ax,ERROR_ALREADY_ASSIGNED   ; Drive already exists.
        stc
        jmp     SHORT adrv_ret

;
; Rmp_Add of CDS failed.  preserve error code in AX.  Free VPB and call
; FS_ATTACH(DETACH)
;
adrv50: mov     cx,bp                       ; (cx) = (bp)
        pop     bp                          ; (bp) = hVPB
        push    cx                          ; Save old BP
        CALLFAR DecVPBRef                   ; Remove referent set up by GenRemotehVPB
        pop     bp                          ; restore bp
;
; Bmp_Add of VPB failed or someone else attached the drive while we were
; blocked in the FS_ATTACH call, so we need to call FS_ATTACH(DETACH).
;
adrv60: mov     di,TASKAREA:CurrTCB             ; (DI) = current TCB     ;### PTR B720732
        les     di,TASKAREA:[di].TCBThisCDS     ; (ES:DI) = CDS pointer  ;### PTR B720732
;### PTR B720732 adrv60: les    di,ThisCDS                  ; (ES:DI) -> CDS
        push    ax
        lea     ax,TASKAREA:adrv_vpb.vpb_fsd
        mov     dx,ss                       ; (DX:AX) -> vpbfsd
        call    detach_rem_drive
        pop     ax
        stc
        jmp short adrv_ret                  ; go return

EndProc AttachDrive


;***    AttachDevice - Attach Pseudo-Char Device to FSD.
;
;   FUNCTION  This procedure will attach a pseudo-char device to the FSD.
;             It checks to make sure that a pseudo-char device of that name
;             does not already exist.
;             We pass in a flag in AX to indicate whether we should call
;             the FSD (FS_ATTACH). This is for the case of PRN/LPT1 where
;             we only call the FSD for LPT1, and then copy over the data
;             to the device entry for PRN without calling the FSD.
;
;   INPUT   ES:DI  -->   FSC for FSD
;           DS:SI  -->   "devicename"
;           SS:BX  -->   parameter packet (see FSAttachStruc)
;           AX           Flag indicating whether to call FSD or not
;                           (=1 if we should not call the FSD)
;           CX:DX  =     devfsd to put in the device entry IFF AX == 1
;
;   EXIT      CY - if error
;                  AX = error code
;             NC if no error
;                  CX:DX = devfsd ("cookie" returned by FSD for device)
;
;   USES  AX, BX, CX, SI, DI, DS, ES, FLAGS
;
;   ALGORITHM
;
;       GetTempBuffer()
;       if (!FindCharDevEntry(PSEUDO))
;           AddCharDevEntry(pseudo device)
;       endif
;       if (CALLFSD)
;           call call_attach_fsd
;       if unsuccessful
;           call RemCharDevEntry
;       FreeTempBuffer()
;
        ASSUME DS:NOTHING, ES:NOTHING; SS:TASKAREA
Procedure   AttachDevice, NEAR, LOCAL
LocalVar adev_fFSD,WORD                 ; Flag for whether to call FSD or not
LocalVar adev_devname,DWORD             ; Pointer to device name
LocalVar adev_StackPtr,WORD             ; Pointer to user parms on stack
LocalVar adev_pFSC,DWORD                ; Pointer to FSC
LocalVar adev_devfsd,DWORD              ; Temp. devfsd.  can't pass pointer
                                        ; to the real one since the char dev
                                        ; entries are stored in RMP.
    LocalVar adev_pTempBuf,DWORD        ; Pointer to temp buffer

        EnterProc <ax,ds,si,bx,es,di,cx,dx> ; Save flag, pointer to devname, stack
                                            ; frame ptr, FSC pointer, and devfsd.
        CALLFAR GetTempBuffer           ; ES:DI -> temp buffer
        mov     adev_pTempBuf.Segmt,es
        mov     adev_pTempBuf.Offst,di
;
; Make sure pseudo device of same name doesn't exist
;
        mov     al,DEVTYPE_PSEUDO       ; AL = pseudo
        SaveReg <ds,si>
        CALLFAR FindCharDevEntry        ; DS:SI -> deventry record
        RestoreReg <si,ds>
        LJNC    adev40                  ; Record found. Duplicate error.
;
; If pseudo device overlaps with existing installed device, remember it
;
        mov     al,DEVTYPE_REAL         ; AL = real
        SaveReg <ds,si>
        les     di,adev_pTempBuf        ; ES:DI -> temp buffer
        CALLFAR FindCharDevEntry        ; DS:SI -> deventry record
        RestoreReg <si,ds>
        mov     bl,DEVFLAGS_INSTOVERLAP
        jnc     adev10                  ; Record found. Pseudo device is replacing rm dev

        mov     al,DEVTYPE_BIMODAL      ; AL = bimodal
        SaveReg <ds,si>
        les     di,adev_pTempBuf        ; ES:DI -> temp buffer
        CALLFAR FindCharDevEntry        ; DS:SI -> deventry record
        RestoreReg <si,ds>
        mov     bl,DEVFLAGS_INSTOVERLAP
        jnc     adev10                  ; Record found. Pseudo device is replacing rm dev

        xor     bl,bl                   ; flags = no overlapping device
                                        ; ES:DI -> FSC
;
; Add the device entry into the character device table. Mark it invisible so
; that noone can use it until the FSD has attached it.
;
adev10: mov     ax,(ACT_DEVERREXISTS shl 8) + DEVTYPE_PSEUDO
        or      bl,DEVFLAGS_INVISIBLE   ; Mark it as in the process of attach
        les     di,adev_pFSC            ; ES:DI -> FSC
        CALLFAR AddCharDevEntry         ; remap the device
                                        ; ES:DI -> deventry record
        lea     si,adev_devfsd
        mov     cx,ss                   ; (CX:SI) -> devfsd
        jc      adev30                  ; error, go return (AX) = error code
        test    adev_fFSD,1             ; Should we call FSD?
        jnz     adev20                  ; No. Make device entry visible.
        xor     ax,ax                   ; no VPBFSD for device
        mov     dx,ax                   ; (DX:AX) = 0
;
; Set up for call to call_attach_fsd.
;
;   CX:SI -> devfsd
;   DX:AX = 0
;
        mov     bx,adev_StackPtr        ; SS:BX -> stack frame parms
        les     di,adev_pFSC            ; ES:DI -> FSC
        call    call_attach_fsd
        jnc     adev20                  ; NC if successful
;
; ATTACH unsuccessful. Remove device from DevEntry Table.
;
        lds     si,adev_devname         ; ds:si -> device name
        ASSUME DS:NOTHING
        SaveReg <ax>                    ; save possible error code
        mov     al,DEVTYPE_PSEUDO       ; The last one added will be removed - which is this one.
        les     di,adev_pTempBuf        ; ES:DI -> temp buffer
        CALLFAR RemCharDevEntry         ; remove device from list
        RestoreReg <ax>                 ; restore error code
        ASSUME DS:NOTHING
        stc
        jmp  SHORT adev30

;
; The ATTACH was successful. Make the device entry visible now.
;
adev20: mov     cx,adev_devfsd.hiword
        mov     dx,adev_devfsd.loword   ; (CX:DX) = devfsd contents
        lds     si,adev_devname         ; ds:si -> device name
        ASSUME DS:NOTHING
        mov     al,DEVTYPE_PSEUDO
        les     di,adev_pTempBuf        ; ES:DI -> temp buffer
        CALLFAR SetCharDevFields        ; turn off invisible flag and fill in
                                        ; devfsd
adev30: les     di,adev_pTempBuf        ; ES:DI -> temp buffer
        pushf
        CALLFAR FreeTempBuffer          ; Free the temp buffer
        popf
        LeaveProc
        ret

adev40: mov     ax,ERROR_ALREADY_ASSIGNED       ; bad pseudo device
        stc
        jmp     SHORT adev30

EndProc AttachDevice


;***    detach_rem_drive - abort an attach of a remote drive
;
;       this routine is called when a BMP add of a VPB or an RMP add of a CDS
;       fails to let the FSD know that the drive is not attached.
;
;   INPUT     ES:DI -> initialized CDS
;             DX:AX -> VPBFSD
;
;   EXIT:     CX = 0
;
;             NC if successful
;             internal error if failure
;
;   CALLS     FS_ATTACH
;
;   USES      AX, BX, CX, DX, SI, DI, ES, flags
;
        ASSUME DS:NOTHING, ES:NOTHING, SS:TASKAREA
Procedure detach_rem_drive,NEAR,LOCAL

        push    FSD_DETACH
        push    es
        lea     bx,es:[di].cd_fsi.cdi_text      ; (ES:AX) -> pDeviceName
        mov     byte ptr es:[bx]+2,0            ; "D:" not "D:\"
        push    bx
        push    dx
        push    ax                              ; vpbfsd@
        push    es
        lea     ax,es:[di].cd_fsd               ; (ES:AX) -> pcdfsd
        push    ax
        push    0                               ; DataBuffer@
        push    0
        push    0                               ; DataLength@
        push    0
        les     di,es:[di].cd_devptr            ; (ES:DI) -> FSC

        call    ES:[DI].FS_ATTACH               ; call the FSD

        xor     cx,cx
        cmp     cx,ax                           ; CY if AX != 0
        jc      drdie                           ; panic! go internal error

        SaveReg <ax>                            ; @99999 begin
        lea     bx,es:[di].cd_fsi.cdi_text      ; (ES:AX) -> pDeviceName
        mov     al,byte ptr es:[bx]             ; drive letter in AX
        sub     al,"A"
        CALLFAR FreeDriveLetter                 ; Free up the drive letter we allocated.
        RestoreReg <ax>                         ; @99999 end

        ret

drdie:  InternalError <detach_rem_drive: detach failed>

EndProc detach_rem_drive


;***    DetachFromFSD - Detach Drive/pseudo-Char Device from FSD.
;
;       If we are detaching a device, and it is LPT1 or PRN, we need to do
;       some special work. We "detach" LPT1 first by calling the FSD and
;       removing its entry from the device table. We then detach PRN by just
;       removing its entry from the table. We do these in this order so that
;       there is little cleanup necessary if the FSD fails the operation.
;
;       INPUT     ES:DI  -->   FSC for FSD
;                 DS:SI  -->   "devicename" or "d:"
;                 SS:BX  -->   parameter packet (see FSAttachStruc)
;                 DX = FSD_DEVICE(0) if device
;                      FSD_DRIVE(1) if drive
;
;       EXIT      CY - if error
;                      AX = error code
;
;       ALGORITHM
;
;       if (drive)
;           DetachDrive()
;       else
;           if (LPT1 or PRN)
;               DetachDevice(LPT1,CALLFSD)
;               if no error
;                   DetachDevice(PRN,!CALLFSD)
;
;
;       USES  AX, CX, DX, SI, DI, DS, ES, FLAGS
;
        ASSUME DS:NOTHING, ES:NOTHING, SS:TASKAREA
Procedure DetachFromFSD,NEAR,LOCAL
LocalVar        dfsd_pFSC,DWORD             ; Pointer to FSC
LocalVar        dfsd_offArgFrame,WORD       ; Offset of argument frame on stack (FSAttachStruc)

;       PMONLY

        or      dx,dx                       ; DETACH drive?
    .errnz (FSD_DEVICE)
        jnz     dfsd60                      ; Yes, go detach the drive.
;
; We are detaching a device. If this is PRN or LPT1, we have to make sure
; that we also detach the corresponding other device since PRN and LPT1 are
; interchangeable names.
;
        EnterProc <es,di,bx>                ; Save FSC and argument pointers

;
; Check to see if it is for PRN or LPT1
;
        DosContext es
        lea     di,es:[PRN_Device_Name]     ; ES:DI -> "PRN"
        CALLFAR StrCmp                      ; Detaching PRN?
        jz      dfsd20                      ; Yes. Do special detach
        lea     di,es:[LPT1_Device_Name]    ; ES:DI -> "LPT1"
        xor     ax,ax                       ; We have to call FS_ATTACH
        CALLFAR StrCmp                      ; Detaching LPT1?
        jnz     dfsd50                      ; No. Contiune normal detach.
;
; Detaching LPT1 or PRN. We detach both of them, first detaching LPT1.
;
dfsd20: DosContext ds
        lea     si,[LPT1_Device_Name]       ; DS:SI -> "LPT1"
        les     di,dfsd_pFSC                ; ES:DI -> FSC
        call    DetachDevice                ; Detach PRN
        jc      dfsd55                      ; Error. Return to caller.
        mov     ax,1                        ; Don't call FSD for this
        DosContext ds
        lea     si,[PRN_Device_Name]        ; DS:SI -> "PRN"

        mov     bx,dfsd_offArgFrame         ; SS:BX -> FSAttachStruc
;
; SS:BX -> FSAttachStruc
; DS:SI -> name of device to attach
; AX = flag for whether to call FSD or not (==1 if we should not call FSD)
;
dfsd50: les     di,dfsd_pFSC                ; ES:DI -> FSC
        call    DetachDevice                ; No, detach the device
dfsd55: LeaveProc
        jmp     short dfsd70
dfsd60: SaveReg <ds,si>
        call    DetachDrive                 ; Detach the drive
        RestoreReg <si,ds>
        jc      dfsd70                  ;### Notify
        pushf
        push    ax                      ;### Notify
        mov     ah, ds:[si]             ;### Get drive letter
        mov     al,0bh                  ;### Attach
        CALLFAR CNotify_Path_Wake       ;### Notify
        pop     ax                      ;### Notify
        popf
dfsd70: ret

EndProc  DetachFromFSD


;***    DetachDevice - Detach Pseudo-Char Device from FSD.
;
;   FUNCTION  This code will set up for the detach of a device
;             by setting up for removal of a device from the device list.
;             After calling the FSD, if there was an error, we return with
;             the error.  If successful, then we remove the device from the
;             device list.
;             We get an indication in AX as to whether we should call the
;             FSD. This is used for the PRN<=>LPT1 mapping where we should
;             only call FS_ATTACH for LPT1, and quietly do the work for PRN
;             internally.
;
;   INPUT     ES:DI  -->   FSC for FSD
;             DS:SI  -->   devicename
;             SS:BX  -->   parameter packet (see FSAttachStruc)
;             AX           Flag for whether to call FSD or not
;                            (==1 if we should not call FSD)
;
;   EXIT      CY - if error
;                  AX = error code
;
;   CALLS     FindCharDevEntry, call_attach_fsd, RemCharDevEntry
;
;   USES  AX, CX, DX, SI, DI, DS, ES, FLAGS
;
;   ALGORITHM
;       GetTempBuffer()
;       find_device
;       if found
;           call call_attach_fsd
;       if successful
;           call RemCharDevEntry
;       FreeTempBuffer()
;
        ASSUME DS:NOTHING, ES:NOTHING, SS:TASKAREA
Procedure DetachDevice,NEAR,LOCAL
LocalVar ddev_fFSD,WORD                 ; Flag indicating whether to call FSD or not
LocalVar ddev_fsc,DWORD                 ; Pointer to FSC
LocalVar ddev_devname,DWORD             ; DS:SI ; device name ptr
LocalVar ddev_devfsd,DWORD              ; Temp. space for devfsd returned by FSD
LocalVar ddev_pTempBuf,DWORD            ; Pointer to temp buffer

;        PMONLY
        EnterProc <ax,es,di,ds,si>          ; Save flag, FSC and device name pointers
;
; Allocate a temp buffer for use by FindCharDevEntry
;
        CALLFAR GetTempBuffer               ; ES:DI -> Temp buffer
        mov     ddev_pTempBuf.Segmt,es
        mov     ddev_pTempBuf.Offst,di
;
; First find the device. Then  call the FSD, and if no error,
; remove the device from the list.
;
        mov     al,DEVTYPE_PSEUDO           ; look for pseudo
        SaveReg <bx>
        CALLFAR FindCharDevEntry            ; DS:SI -> dev entry
        RestoreReg <bx>
        jc      ddev30                      ; can't find device(bad path)
        test    ddev_fFSD,1                 ; Should we call FSD?
        jnz     ddev10                      ; No. Just remove device entry.
        TaskContext <es>
        lea     di,ddev_devfsd              ; (ES:DI) -> copy of devfsd
        lea     si,[si].DES_DevFsd          ; (DS:SI) -> real devfsd
        movsw
        movsw                               ; copy devfsd
        lea     si,ddev_devfsd
        mov     cx,ss                       ; (CX:SI) -> copy of devfsd
        xor     ax,ax
        mov     dx,ax                       ; (AX:DX) -> 0 (no vpb for device)
;
; Call the FSD to detach the device.
;
;       SS:BX -> stack frame parms
;       CX:SI -> devfsd
;       DX:AX = 0
;
        les     di,ddev_fsc                 ; ES:DI -> FSC
        ASSUME ES:NOTHING
        call    call_attach_fsd             ; CX:SI = 0 for device
        jc      ddev20                      ; Error from FSD. Fail the detach.
;
; The detach was successful. We remove the device from deventry table.
;
ddev10: mov     ax,DEVTYPE_PSEUDO
        lds     si,ddev_devname             ; DS:SI -> device name
        ASSUME DS:NOTHING
        les     di,ddev_pTempBuf            ; ES:DI -> temp buffer
        CALLFAR RemCharDevEntry             ; remove device from list
        jc      ddevie                      ; panic!!
ddev20: les     di,ddev_pTempBuf            ; ES:DI -> temp buffer
        pushf
        CALLFAR FreeTempBuffer              ; Free the temp buffer
        popf                                ; Restore error indications
        LeaveProc
        ret

ddev30: mov     ax,ERROR_INVALID_PATH          ; bad pseudo device
        jmp     SHORT ddev20

ddevie: InternalError <DetachFromFSD: device disappeared beneath us>

EndProc DetachDevice


;***    DetachDrive - Detach Drive from FSD.
;
;   FUNCTION  This code will set up for the detach of a drive by
;             by removing a CDS. We do this irrespective of whether the
;             the drive is a current drive for any process or not. We let
;             the FSD decide if that is what it wants to do or not. We also
;             provide a dirty hook for the REDIR to AsgCheckDrive so that it
;             can determine if the drive a current drive for any process.
;             After calling the FSD, if there was an error, we return with
;             the error.  If successful, we delete the CDS for the drive.
;
;   INPUT     ES:DI  -->   FSC for FSD
;             DS:SI  -->   "d:"
;             SS:BX  -->   parameter packet (see FSAttachStruc)
;
;   EXIT      CY - if error
;                  AX = error code
;
;   CALLS     DriveFromText, GetCDS, ScanCDS, call_attach_fsd, DelCDS
;
;   USES  AX, CX, DX, SI, DI, DS, ES, FLAGS
;
;   ALGORITHM
;       call GetCDS
;       if (refcnt != 1) || (CDS is prefix of some path)
;           fail detach
;       call call_attach_fsd
;       if successful
;           call DelCDS
;
        ASSUME DS:NOTHING, ES:NOTHING, SS:TASKAREA
Procedure DetachDrive,NEAR,LOCAL
LocalVar ddrv_fsc,DWORD
LocalVar ddrv_devname,DWORD             ; DS:SI ; device name ptr
LocalVar ddrv_cdfsd,<size cdfsd>        ; copy of curdir fsd section
LocalVar ddrv_cdhandle,WORD             ; handle to CDS
LocalVar ddrv_hvpb,WORD         ; handle to VPB
LocalVar ddrv_drvnum,WORD               ; drive number

;        PMONLY
        EnterProc <es,di,ds,si>         ; Save pointers to FSC and drive string

;
; We are detaching a remote drive. We want to make sure that there is
; a drive by that name.
;
        CALLFAR DriveFromText           ; AL = 1 based drive #
                                        ; DS:SI -> beyond string
;
; Simplified version of GetCDS without init'ing if it doesn't exist.
; Only looking through the global macros here.
;
        cmp     al,NUMDRIVE             ; VALID DRIVE?
        ja      ddrv20                  ; no, bad drive

        xor     ah,ah                   ; AX = handle
        mov     ddrv_drvnum,ax          ; save the drive number
        SaveReg <bx,es,di>              ; save stack frame and FSC ptr   ;### cp20 PTR b721526
        CALLFAR GetCDS                  ; look up a macro
                                        ; ES:DI -> CDS
        RestoreReg <si,ds,bx>           ; restore stack frame and FSC ptr ;### cp20 PTR b721526
        jc      ddrv20                  ; CDS is bad (bad drive)
                                        ; ES:DI -> CDS   DS:SI -> FSC     ;### cp20 PTR b721526
        cmp     si, es:[di].cd_OwnerFSC ; compare FSC.Offst with          ;### cp20 PTR b721526
        jne     ddrv20                  ; FSC address to see if same fsd  ;### cp20 PTR b721526
        SaveReg <es,di>
        RestoreReg <si,ds>              ; DS:SI -> CDS

;
; We have to copy the part of the cds passed to the FSD (cdfsd) because the
; FSD could block and the address could become invalid.
;
        mov     ax,[si].cd_handle       ; save handle of CDS
        mov     ddrv_cdhandle,ax
        mov     ax,[si].cd_fsi.cdi_hVPB ; (AX) = hVPB
        mov     ddrv_hvpb,ax            ; save handle of VPB
        lea     si,[si].cd_fsd          ; (DS:SI) -> CDS.cdfsd
        TaskContext <ES>,di
        lea     di,ddrv_cdfsd           ; (ES:DI) -> local copy of CDS.cdfsd
        mov     cx,size cdfsd
        rep movsb                       ; copy cdfsd to local copy
        mov     cx,ss
        lea     si,ddrv_cdfsd           ; (CX:SI) -> local copy of CDS.cdfsd
        mov     di,ax                   ; (DI) = hVPB
        VPBContext es,di                ; (ES:DI) -> VPB
        mov     dx,es
        lea     ax,[di].vpb_fsd         ; (DX:AX) -> VPB.vpbfsd
;
; We now call the FSD to detach the drive.
;
;       SS:BX -> stack frame parms
;       CX:SI -> CDS.cd_fsd
;       DX:AX -> VPBFSD
;
        les     di,ddrv_fsc             ; ES:DI -> FSC
        call    call_attach_fsd
        jc      ddrv10                  ; CY = error, leave w/err from FSD
;
; DETACHED a DRIVE successfully. Invalidate all CDS's that are on
; this volume and delete the VPB from BMP. Do this for all processes.
;
; DS:SI = ES:DI -> CDS
;
        mov     ax,ddrv_drvnum          ; AX = drive number
        dec     ax                      ; (ax) = 0 based drive number
        call    DelDerefDrive           ; remove all CDS references

; Free up the drive letter for reallocation
;99999 begins
        CALLFAR FreeDriveLetter         ; Free up the drive letter we allocated.
;99999 ends

;
; Now remove the reference for this drive from the VPB. We do this in case
; there are other references still on the VPB for the drive, e.g. unfinished
; search handles that would need to be closed eventually. This way, we keep
; the VPB around until all the searches are done, and then it will get freed.
; we mark the vpb invalid so that continued searches will fail and so that
; the vpb isn't found if we re-attach the drive.

        push    bp                      ; save bp
        mov     bp,ddrv_hvpb            ; (BP) = hVPB
        VPBContext es,bp
        or      es:[bp].vpb_flags,VPBINVALID ; mark vpb invalid
        CALLFAR DecVPBRef               ; Remove VPB referent
        pop     bp                      ; restore bp
        clc                             ; NC for good return
ddrv10: LeaveProc
        ret

ddrv20: mov  ax,ERROR_INVALID_DRIVE     ; bad drive
        stc
        jmp  SHORT ddrv10

EndProc DetachDrive


;***    call_attach_fsd - call the FS_ATTACH entry point
;
;   FUNCTION  This merely pushes all the necessary parameters for a
;             call to FS_ATTACH in the appropriate FSD.
;
;   INPUT     SS:BX -> stack frame parms (see FSAttachStruc)
;             CX:SI -> CDS.cd_fsd if drive
;                   -> DEVFSD if device
;             ES:DI -> FSC
;             DX:AX -> VPBFSD if drive
;                      0 if device
;
;   EXIT:     CX = 0
;
;             NC if successful
;             CY if error
;                AX = error code if error
;
;   CALLS     FS_ATTACH
;
;   USES  AX, CX, DX, ES
;
        ASSUME DS:NOTHING, ES:NOTHING, SS:TASKAREA
Procedure call_attach_fsd,NEAR

         SaveReg <bx>

         push TASKAREA:[bx].FSA_iOpFlag               ; OpFlag
         push TASKAREA:[bx].FSA_szDeviceName.Segmt
         push TASKAREA:[bx].FSA_szDeviceName.Offst    ; DeviceName@
         push dx
         push ax                                      ; vpbfsd@
         push cx
         push si                                      ; cdfsd@
         push TASKAREA:[bx].FSA_pDataBuffer.Segmt
         push TASKAREA:[bx].FSA_pDataBuffer.Offst     ; DataBuffer@
         push ss
         lea  ax,TASKAREA:[bx].FSA_cbDataLength       ; DataLength@
         push ax

; FSD can trash AX, BX, CX, DX, ES

         call ES:[DI].FS_ATTACH                       ; call the FSD

         xor  cx,cx
         cmp  cx,ax                                   ; CY if AX != 0

         RestoreReg <bx>

         ret

EndProc call_attach_fsd


;***    DelDerefDrive - delete and dereference CDSs for specified drive
;
;       ENTRY   AX = 0 - based drive number
;
;       EXIT    nothing
;
;       USES    BX, CX, DX
;

        ASSUME  DS:NOTHING,ES:NOTHING
Procedure   DelDerefDrive,HYBRID

        LocalVar    ddd_drive,WORD
        EnterProc

        SaveReg <ax,ds,si,es,di>        ; Save regs
        mov     ddd_drive,ax            ; ax = drive number, 0-based
        inc     ax                      ; make drive number 1-based
        CALLFAR GetCDS                  ; get the CDS if it exists
        jc      ddd1

        CALLFAR DelCDS                  ; delete the macro

;   Now need to scan PTDAs to find processes which have current directories
;   on the drive and delete their CDSs. This is done by calling the OS2 kernel
;   routine ScanTasks.  ScanTasks takes as a parameter, a pointer to a subroutine
;   to be executed within each TaskContext.  The routine we pass is ddd_etsk
;   (DelDerefDrive_ScanTasks)

; WARNING: Since ScanTasks and ddd_etsk are in different segments, ddd_etsk
;          needs to generate a far return.
;
ddd1:   mov     bx,TK_TG_ALL OR TK_SF_WORKER    ; find all processes
        MOVFAROFFSET di,ddd_etsk,NMPIPESWAPCODE ; -> ScanTasks worker routine
        CALLFAR ScanTasks
        RestoreReg <di,es,si,ds,ax>
        LeaveProc
        ret

EndProc DelDerefDrive


;**     ddd_etsk - ScanTasks helper for DelDerefDrive.
;
;   This routine is used to delete the Per Task CDSs that refer to the drive
;   being dereferenced. This is done by calling the OS2 kernel routine ScanTasks.
;   with ddd_etsk as a parameter.  ScanTasks then invokes ddd_etsk on each PTDA
;   in the system.
;
;       ENTRY   ES = Next PTDA entry
;               BP = DeleteDerefDrive frame pointer
;
;       EXIT    'C' set if CDS found
;                       ddd_drive set to matching drive number
;               'C' clear if CDS not found
;
;       USES    AX, BX, CX, DX, DS
;
;   WARNING! Due to the internal workings of the ScanTasks routine, ddd_etsk
;           must be in the same code segment as the CallBack mechanism that
;           transfers control to the ScanTasks Kernel routine.  See netinit1.asm
;           for further details on this mechanism.
;           If ScanTasks is moved out of HIGH3CODE, then this routine should
;           be made into a FAR routine so that the far return is made at the
;           end of it.
;
        ASSUME DS:NOTHING, ES:NOTHING, SS:TASKAREA
Procedure ddd_etsk,faronly

        mov     bx,ddd_drive            ; BX = drive number to be found
        add     bx,bx                   ; make into word index
        xor     ax,ax
        xchg    ax,es:CDS_Handle[bx]    ; AX = CDS handle, array entry zeroed
        or      ax,ax                   ; CDS exist?
        jz      ddde9                   ; no, nothing to do
        SaveReg <si,di,bp,es>
        CALLFAR DelCDS                  ; delete the CDS
        RestoreReg  <es,bp,di,si>
ddde9:
        clc                             ; continue scanning PTDAs
        ret
EndProc ddd_etsk


;***    w_qFSAttach - Attach Drive/pseudo-Char Device to FSD.
;
;   w_qFSAttach allows the retrieval of information about an attached remote
;   FSD, a local FSD, a character device, or a pseudo-char device name
;   attached to a local or remote FSD.
;   If we are called to return information on a specific drive, we obtain
;   the CDS for that drive.  Based on that, we can fill in information in
;   the fsReturnInfo structure, and pass it to the FSD to fill in the rest.
;   (for remote drives and local non-FAT drives)
;   If the drive is locally attached, we trace the CDS through the DPB to the
;   VPB and use its pointer to extract the FSC entry.  If this entry is
;   RESERVED, then this local drive belongs to the FAT FS.  In this case, we
;   package up the fsReturnInfo with some 0s and NULLS, and return to the
;   caller.
;   If we are to return info about a specific device, we must use the device
;   manager to find the device in the list of device names.  From this, we
;   can use the DevEntry pointer to the FSC to fill in the data in the
;   fsReturnInfo structure, and pass it to the FSD.(for pseudo-chars only)
;   If the device is a resident character device, we just return the name
;   of the device back to the caller, and the FSD and FSA data areas are
;   0s and NULLs.
;   If called to get info about an ordinal based device, we must find the
;   search through the RMP segment for devices until we have found the
;   "ordinalith" entry in the device list.  Then it is the same as the specific
;   device case above.
;   If called to get info about an ordinal based drive, we loop through the
;   list of attached(existing) drives until the "ordinalith" one is found.
;   In a case where drives A, B, C, and Z are present, and the Query is for
;   ordinal 4, we would return information about drive Z.  Once found, the
;   information is obtained as in the case for the specific drive case above.
;
;   INPUT     AX     ===   FSAInfoLevel
;                            1 = query specific device/drive
;                            2 = query ordinal device
;                            3 = query ordinal drive
;             DS:SI  ===   Device Name
;             SS:BX  -->   parameter packet (see FSQAttachStruc)
;
;   EXIT      NC - success
;             CY - error
;                  AX = error_invalid_drive
;                       error_buffer_overflow
;                       error_no_more_items
;                       error_invalid_level
;                       error_invalid_path
;
;   CALLS     PathPref, q_dev_drv, q_ord_dev, q_ord_drv
;
;
;   USES  AX, CX, DX, SI, DI, DS, ES, FLAGS
;
;   ALGORITHM:
;
;   if infolevel out of range, then error
;   if specific device/drive query
;        call q_dev_drv
;   else if ordinal device query
;        call q_ord_dev
;   else if ordinal drive query
;        call q_ord_drv
;   end
;
        ASSUME DS:NOTHING, ES:NOTHING, SS:TASKAREA
Procedure w_qFSAttach,<faronly,far16>
LocalVar qfsa_dummycds,<SIZE curdir>
LocalVar qfsa_openbuf,PMPATHLEN
LocalVar dummy,WORD                     ; KLUDGE: see gshare.asm
;        PMONLY
        CodeLock FSCTL_GROUP
        EnterProc

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

; First, let's check the FSA Info Level, then we can figure out what to do
; based on it.

         or   ax,ax
         jz   qfsa100                       ; 0 is invalid

         cmp  ax,INFO_LEV3
         ja   qfsa100                       ; > 3 is invalid

; Now we check the info level to determine the action to take

qfsa03:  dec  ax
         jnz  qfsa20                        ; not specific, check ordinal dev

; Here we are querying a specific device or drive

;   ds:si -> "devicename"  OR  "d:"

         mov    ax,ds                       ; ax = segment of device/drive name
         or     ax,ax                       ; null pointer?
         je     qfsa120                     ; yes, go fault the user
         call q_dev_drv                     ; do the query for the drive/device
         jmp  SHORT qfsa50                  ; NC = ok, CY = error, AX = code

; Next, let's check the FSA Info Level for an ordinal based device query
; rather than do a search specific, we search based on an ordinal which is
; an index into the list of char/pseudo devices.  If we don't find it, error
; This list can change at any time based on attaches/detaches being performed.

qfsa20:  dec  ax
         jnz  qfsa30                        ; not ordinal dev, try ordinal drv
         mov  ax,TASKAREA:[bx].FSQA_iOrdinal
         test ax,-1
         jz   qfsa102                       ; ZR = ordinal of 0, invalid param.
         call q_ord_dev                     ; do the query for the device
         jmp  SHORT qfsa50                  ; NC = ok, CY = error, AX = code

; If we get here, we are doing a query based on an ordinal drive
; We will search into the list if drives, picking only the valid/attached ones
; until we find the "ordinalth" one.  If we do not find it, then error.
; this list can change at any time based on attaches/detaches being performed.

qfsa30:  mov  ax,TASKAREA:[bx].FSQA_iOrdinal
         test ax,-1
         jz   qfsa102                       ; ZR = ordinal of 0, invalid param.
         call q_ord_drv                     ; do the query for the drive
                                            ; NC = ok, CY = error, AX = code

;### PTR B720732 qfsa50:        mov     SS:[ptda_pCDS],0            ; Clear ptda_pCDS
;### PTR B720732        mov     SS:[ptda_pOpenBuf],0        ; Clear ptda_pOpenBuf
qfsa50: LeaveProc
        CodeUnlock FSCTL_GROUP
         ret

qfsa100: mov  ax,ERROR_INVALID_LEVEL
         stc
         jmp  short qfsa50

qfsa102: mov  ax,ERROR_INVALID_PARAMETER
         stc
         jmp  short qfsa50

; if we get here, the user is querying a specific device or drive and the
; device/drive name pointer had a selector == zero.  this is treated as a
; null selector by getusrbufnz.  cause the user process to die.

qfsa120: xor    bx,bx
        mov     si,bx               ; bx:si -> transfer buffer
        mov     cx,1                ; transfer length
        mov     dh,ACCTYPE_RW       ; transfer type
        CALLFAR ProbeBuff           ; cause user process to die
        jmp     short qfsa50

EndProc  w_qFSAttach


;***     q_dev_drv - specific device/drive query
;
;   FUNCTION: This routine will first check for device or drive.  If it is
;             for a device, then it will try to find a pseudo-char device
;             in the deventry table first.  If it cannot find a pseudo-device
;             by that name, it will search for a bimodal device.  If it can't
;             find either, then error.  If it found a pseudo or bimodal, it
;             will call fillin_dev to fill in the user's data area with
;             appropriate information.  If the device was bimodal, it will
;             return after that.  If the device was pseudo, it will call
;             call_query_fsd to push the parameters onto the stack, and call
;             the FSD.
;             If the call was for a specific drive, it will call DriveFromText
;             to get the drive number.  Then it will call fillin_drv to fill in
;             the appropriate fields in the user's buffer.  If the drive is
;             local and FAT, the call will return after that.  Else, if the
;             drive is either remotely attached, or local non_FAT, this will
;             call call_query_fsd to push the parm and call the FSD.
;
;
;   ENTRY:  DS:SI  -->   "device name"
;           SS:BX  -->   parameter packet (see FSQAttachStruc)
;
;   EXIT: NC - successful
;             CX = length to store into user's buffer length
;         CY - error
;             AX = error code
;
;   CALLS:    FindCharDevEntry, fillin_dev, fillin_drv, DriveFromText
;             call_query_fsd, GetTempBuffer, FreeTempBuffer
;
;   USES: AX, BX, CX, DX, ES, DI, DS, SI, FLAGS
;
;   ALGORITHM:
;
;   call gettempbuffer and save ptr
;   if valid drive name
;        set drive flag
;   else error
;   if device
;        set device flag
;        if !"\dev\" as device prefix, then error
;   if device
;        if find pseudo
;             set pseudo flag
;        if find bimodal
;             set bimodal flag
;        else error
;        call fillin_dev
;        if bimodal, return
;        else call call_query_fsd
;   else
;        call drivefromtext
;        call fillin_drv
;        if local fat, return
;        else call call_query_fsd
;   end
;   call freetempbuffer
;
;       NOTE    It is OK to overlap the usage of the temp buffer for
;               device detection (when calling FindCharDevEntry) and for
;               storing the return information since the two uses never
;               overlap. i.e. we detect that it is a device before we even
;               start to fill in return information.
;
        ASSUME DS:NOTHING, ES:NOTHING, SS:TASKAREA
Procedure q_dev_drv
LocalVar qdd_stkfrm,WORD                    ; holder for stack fraame @
LocalVar qdd_bufptr,DWORD                   ; holder for temp buffer @
LocalVar qdd_devfsd,DWORD                   ; local copy of devfsd
LocalVar qdd_pathname,DWORD                 ; ptr to WFP for given path name

;       PMONLY
        CALLFAR GetTempBuffer               ; ES:DI -> temp buffer

        EnterProc <bx,es,di>                ; save stack frame pointer
                                            ; and temp buffer @
         SaveReg <es, di>                   ; save address of temp buffer

         call   Path_Check                  ;
         LJC    qdd100                      ; error
         jz     qdd20                       ; DRIVE!
;
; DS:SI -> device name (with the \DEV\ prefix removed)
; device ! store path name properly
;
         mov    qdd_pathname.Offst,si       ; Set up our pointer to adjust for
         mov    qdd_pathname.Segmt,ds       ; the canonicalized path.
;
; Gunk to find pseudo/resident char device here
;
qdd04:   mov  al,DEVTYPE_PSEUDO             ; AL = pseudo
         les  di,qdd_bufptr                 ; ES:DI -> temp buffer
         CALLFAR FindCharDevEntry           ; DS:SI -> deventry record
         mov  bx,qdd_stkfrm                 ; SS:BX -> stack frame
         jc   qdd10                         ; CY = no record found
         xor  ax,ax                         ; ax = 0 if pseudo
         les  di,qdd_bufptr                 ; ES:DI -> temp buffer

         lea    dx,qdd_devfsd               ; (SS:DX) -> local copy of devfsd
         call fillin_dev                    ; fill in data buffer for device
         jc   qdd100                        ; CY = error, leave

         push   qdd_stkfrm
;
;   top of stack = parameter frame pointer
;   DS:SI -> FSC
;   CX:AX -> if drive CDS dependent else if device devfsd
;   BX:DX -> if drive vpbfsd else if device 0
;   ES:DI -> user's buffer.cbFSAData
;
         call   call_query_fsd              ; push parms and call FSD
         pop    bx                          ; (SS:BX) -> stack frame

         jmp    SHORT qdd100
;
; If couldn't find pseudo, try and find resident/bimodal
;
qdd10:   mov  al,DEVTYPE_BIMODAL            ; AL = bimodal
;
; Reset DS:SI -> "devicename"
;
         lds    si,qdd_pathname             ; Try the name given by transpath
         les    di,qdd_bufptr               ; ES:DI -> temp buffer
         CALLFAR FindCharDevEntry           ; DS:SI -> deventry record
         mov    bx,qdd_stkfrm               ; BX -> stack frame
         jnc    qdd12                       ; NC = device found

; if have not found pseudo, or bimodal yet, look for real mode device.

         mov    al,DEVTYPE_REAL             ; AL = real

; reset DS:SI -> "devicename"

         lds    si,qdd_pathname             ; Try the name given by transpath

         les    di,qdd_bufptr               ; ES:DI -> temp buffer
         CALLFAR FindCharDevEntry           ; DS:SI -> deventry record
         mov    bx,qdd_stkfrm               ; SS:BX -> stack frame
         jc     qdd104                      ; CY = no device found

qdd12:   mov    ax,RES_CHAR                 ; AX = 1 if resident
         les    di,qdd_bufptr               ; ES:DI -> temp buffer

         call   fillin_dev                  ; fill in data buffer for device

         jmp    SHORT qdd100                ; resident, don't call FSD

; Here we find a drive based on the drivename passed in

qdd20:   CALLFAR DriveFromText              ; AL = 1 based drive#
         SaveReg <ax>
         dec  ax                            ; AL = 0 based drive# for call
         CALLFAR GetCDSFromDrv              ; DS:SI -> CDS
         RestoreReg <ax>
         jc   qdd102                        ; CY = drive out of range
         test ds:[si].cd_flags,0ffh         ; test if present
         jz   qdd102                        ; ZR = drive not present

         call fillin_drv                    ; fillin data buffer for drive

; fillin_drv returns:
; NC if REMOTE DRIVE and no error
; CY and AX = error code for REMOTE/LOCAL DRIVES and error
; CY and AX = 0 and CX = length of data buffer for LOCAL DRIVES with no error

         jnc  qdd22                         ; NC = call FSD

         or   ax,ax                         ; any error code?
         jz   qdd100                        ; no, leave (NC)
         stc
         jmp  SHORT qdd100                  ; error, leave w/CY

qdd22:   push   qdd_stkfrm

;          top of stack = parameter frame pointer
;          DS:SI -> FSC
;          CX:AX -> if drive CDS dependent else if device devfsd
;          BX:DX -> if drive vpbfsd else if device 0
;          ES:DI -> user's buffer.cbFSAData

         call   call_query_fsd              ; call the FSD
         pop    bx

qdd100:  RestoreReg <di, es>                ; DI -> offset of temp buffer
         pushf
         CALLFAR FreeTempBuffer             ; release temp buffer
         popf
         LeaveProc
         ret

qdd102:  mov  ax,ERROR_INVALID_DRIVE
         stc
         jmp  SHORT qdd100

qdd104:  mov  ax,ERROR_INVALID_PATH
         stc
         jmp  SHORT qdd100

EndProc  q_dev_drv


;***     fillin_dev - fill in data buffer for devices
;
;   FUNCTION: This routine fills in the appropriate fields of the user's buffer
;             with information extracted from the device entry record.  First,
;             it points to the FSC, and extracts the device name.  It prefixes
;             it with "\dev\" and loads it along with its length into the
;             user's buffer.  Then, if it is a bimodal device, it fills in the
;             FSD information with 0, and returns.  If it is a pseudo device,
;             it fills in the name, and length of name of the FSD, and returns.
;
;   ENTRY:  AX = 0 if pseudo char device
;              = 1 if resident char device
;           SS:BX -->   parameter packet (see FSQAttachStruc)
;           SS:DX -->   devfsd to fill in from char dev entry if pseudo dev
;           DS:SI -->   device entry record
;           ES:DI -->   Temp Buffer
;
;   EXIT: NC = successful
;             if PSEUDO, DS:SI -> FSC@
;                        CX:AX -> devfsd filled in
;                        ES:DI -> user's buffer.cbFSAData
;                        BX:DX = 0
;             if RESIDENT, CX = length of used data buffer to return to
;                               user via "st_ax" thop
;         CY = error
;             AX = error code
;
;   CALLS:    DStrLen, StrCpy, StrLen, FStrCpy, load_2_fields,
;             load_3_fields, fill_fields_0
;
;   USES: AX, BX, CX, DX, ES, DI, SI, DS, FLAGS
;
;   ALGORITHM:
;
;   get device name from device entry record
;   prefix device name with "\DEV\" in temp buffer
;   call load_3_fields
;   if resident char(bimodal) device
;        call fill_fields_0
;        return
;   else
;        get FSC from device entry record
;        get FSD name from FSC
;        call load_2_fields
;        set up for call_query_fsd
;        return
;   end
;
        ASSUME DS:NOTHING, ES:NOTHING, SS:TASKAREA
Procedure fillin_dev,NEAR
LocalVar fdev_type,WORD                     ; 0 = pseudo, 1 = resident
LocalVar fdev_buf,DWORD                     ; holder for temp buffer
LocalVar fdev_devfsd,WORD                   ; offset of devfsd on stack
LocalVar fdev_devptr,DWORD                  ; ptr to char dev entry (invalid if ProcBlock)
LocalVar fdev_fsc,DWORD                     ; FSC@ holder

;       PMONLY
        EnterProc <ax,es,di,dx,ds,si>       ; FLAG = AX, buffer = ES:DI
                                            ; devptr = DS:SI, devfsd = dx

; Here we fill in information for either type of device in the data buffer
; First, we get the type, then the device name from the deventry record and
; copy its length and name into the data buffer that we return to the user.

; NOTE this code is the same for pseudo and resident

         mov    ax,ds:[si].DES_RoutePtr.Offst ; ax = offset of FSC || DevHdr(ignored)
         mov    fdev_fsc.Offst,ax
         mov    ax,ds:[si].DES_RoutePtr.Segmt ; ax = segment FSC || DevHdr(ignored)
         mov    fdev_fsc.Segmt,ax             ; fdev_fsc -> FSC

         push   ds
         pop    es
         lea    di,[si].DES_Name        ; ES:DI -> device name in dev entry
         mov    si,di                   ; DS:SI -> device name in dev entry
         xor    al,al
         mov    cx,MAXPATHLEN
         repne  scasb                   ; find end of string (es:di) -> one past null
         mov    cx,di                   ; cx = offset of user-specified name
         sub    cx,si                   ; cx = length of string (including null)
.ERRNZ (DES_Name + PDEVNAMELEN + PDEVNAMELEN - size CharDevEntry)
.ERRNZ (DES_UserName - PDEVNAMELEN - DES_Name)
         SaveReg <ds,di>                ; save pointer to user-specified name
         les    di,fdev_buf                   ; ES:DI -> temp buffer
         DosContext ds
         mov    si,offset DOSGROUP:DevPrefix ; DS:SI -> "\DEV\ "
         CALLFAR FStrCpy
         dec    di                      ; ES:DI -> past "\DEV\"
         RestoreReg <si,ds>             ; DS:SI -> user specified name in dev entry
         rep    movsb                   ; copy user-specified name
         lds    si,fdev_buf             ; DS:SI -> "\DEV\devicename " (tempbuffer)

         mov    dx,bx                       ; DX -> stack frame
         mov    di,TASKAREA:[bx].FSQA_pDataBuffer.Offst
         mov    bx,TASKAREA:[bx].FSQA_pDataBuffer.Segmt ; BX:DI -> user buffer

         test fdev_type,-1
         jnz  fdev0                         ; resident
         mov  ax,PSEUDO_CHAR
         jmp  SHORT fdev2

fdev0:   mov  ax,RES_CHAR

; AX = word to write to user's
; BX:DI -> user's buffer.iType (first field)
; DS:SI -> device name in temp buffer
; SS:DX -> stack frame

fdev2:   call load_3_fields                 ; load type, cbName, szName
         jc   fdev20                        ; protection error

; AX = length of buffer remaining
; DS:SI = garbage
; BX:DI -> user buffer.cbFSDName

         test fdev_type,-1
         jnz  fdev10                        ; resident

; Found a pseudo device.  Fill in the FSD part of the data structure for it.
; copy devfsd info into local devfsd.

         SaveReg <di,ax>
         lds    si,fdev_devptr              ; (DS:SI) -> char dev entry
         mov    ax,[si].DES_DevFsd.loword
         mov    di,fdev_devfsd              ; (SS:DI) -> temp devfsd on stack
         mov    TASKAREA:[di].loword,ax     ; store loword of devfsd from char dev entry
         mov    ax,[si].DES_DevFsd.hiword
         mov    TASKAREA:[di].hiword,ax     ; store hiword of devfsd from char dev entry
         lds  si,fdev_fsc                   ; DS:SI -> FSC@
         mov  di,si                         ; DI = offset of FSC

; Get the FSD Name from the FSD memory

         lds  si,ds:[di].FS_NAME            ; DS:SI -> FSD Name
         RestoreReg <ax,di>                 ; BX:DI -> cbFSDName.
                                            ; AX = length of remaining user buffer
         call load_2_fields                 ; load cbFSDName, szFSDName
         jc   fdev20                        ; CY = protection error
         mov  es,bx                         ; ES:DI -> cbFSAData
         lds  si,fdev_fsc                   ; DS:SI -> FSC@
         mov    ax,fdev_devfsd
         mov    cx,ss                       ; CX:AX -> devfsd
         xor  bx,bx
         mov  dx,bx                         ; BX:DX = 0 (null VPB for device)

         jmp  SHORT fdev20

; Found a resident char device.  Fill in FSD part of the structure with "FAT"
; AX = length of buffer remaining
; DS:SI = garbage
; BX:DI -> user buffer.cbFSDName

fdev10:  call fill_fields_0                 ; fill in 0s and NULL for FAT device

fdev20:  LeaveProc
         ret

EndProc  fillin_dev

;***     fillin_drv - fill in data buffer for drives
;
;   FUNCTION: based on the drive# passed in, this function will obtain a text
;             string for that drive(if valid), and fill it in the data buffer.
;             it will also based on whether or not the drive is local/remote,
;             fill in the rest of the data buffer with information.  if the
;             drive is local and FAT, the FSD name is FAT, and the
;             FS_ATTACH entry point is never called.
;
;   ENTRY:  AL = 1 based drive#
;           DS:SI  -->   ThisCDS
;           ES:DI  -->   Temp Buffer
;           SS:BX  -->   parameter packet (see FSQAttachStruc)
;
;   EXIT:  CY -
;             IF REMOTE DRIVE or LOCAL DRIVE and ERROR
;                  AX = error code
;             IF LOCAL DRIVE and NO ERROR
;                  AX = 0
;                  CX = length of data buffer to return to user
;                       via "st_cx" thop (DON'T CALL FSD FOR LOCAL DRIVES)
;          NC -
;             IF REMOTE DRIVE
;                  ES:DI -> user's buffer.cbFSAData to pass to FSD to fill in
;                  DS:SI -> FSC to use to call FSD
;                  CX:AX -> CDSd to pass to FSD
;                  BX:DX -> VPBfsd to pass to fsd
;
;   CALLS     TextFromDrive, StrLen, GetCDS, ValidateCDS, load_2_fields,
;             load_3_fields, fill_fields_FAT, update_drv
;
;   USES: AX, BX, CX, DX, ES, DS, DI, SI, FLAGS
;
;   ALGORITHM:
;
;   call textfromdrive
;   call load_3_fields
;   if local drive
;        call update_drv
;   if local non-FAT drive
;        get FSD name from FSC
;        call load_2_fields
;   if local FAT drive
;        call fill_fields_FAT
;   if remote drive
;        get FSC name from CDS
;        call load_2_fields
;        set up for call_query_fsd
;        return
;   end
;
        ASSUME DS:NOTHING, ES:NOTHING, SS:TASKAREA
Procedure fillin_drv,NEAR
LocalVar fdrv_drvflg,WORD                   ; 0 = local, 1 = remote
LocalVar fdrv_buf,DWORD                     ; holder for temp buffer @
LocalVar fdrv_cds,DWORD                     ; holder for CDS@
LocalVar fdrv_ptr,DWORD                     ; holder for user buffer @

;       PMONLY
        EnterProc <0,es,di,ds,si>                   ; FLAG = local, buf = ES:DI, CDS = DS:SI

        SaveReg <bx>                        ; save parm frame pointer

; First, we fill in the type, drive name and length of the name in the data
; buffer.
; NOTE this code is the same for local and remote


         dec  al                            ; AL = 0 based drive#
         CALLFAR TextFromDrive              ; ES:DI -> past "d:" in temp buffer
         mov  BYTE PTR es:[di],0            ; NULL terminate

         mov  di,TASKAREA:[bx].FSQA_pDataBuffer.Offst
         mov  bx,TASKAREA:[bx].FSQA_pDataBuffer.Segmt ; BX:DI -> user buffer

         test ds:[si].cd_flags,CD_ISNET     ; REMOTE?
         jnz  fdrv4
         mov  ax,LOCAL_DRIVE                ; LOCAL!
         jmp  SHORT fdrv6

fdrv4:   mov  ax,REMOTE_DRIVE               ; REMOTE!
         inc  fdrv_drvflg                   ; drvflg = 1 if remote

; AX = word to write to user's
; DS:SI -> CDS
; BX:DI -> user's buffer.iType (first field)

fdrv6:  lds     si,fdrv_buf                 ; DS:SI -> "d: " in temp buffer

; DS:SI -> "d: " in temp buffer
; now, we will load up the user's buffer fom the temp buffer.

         RestoreReg <dx>                    ; DX -> stack frame

         call load_3_fields                 ; load type, cbName, szName
         LJC  fdrv30                        ; protection error
         lds  si,fdrv_cds                   ; DS:SI -> CDS

; AX = length of buffer remaining
; DS:SI = CDS
; BX:DI -> user buffer.cbFSDName

; We split here based on whether the drive is local or remote.

         mov  fdrv_ptr.Offst,di
         mov  fdrv_ptr.Segmt,bx             ; fdrv_ptr -> user buffer.cbFSDName

         test fdrv_drvflg,-1                ; drvflg = 1 if remote
         jnz  fdrv20

; WE ARE LOCAL!!!!

         SaveReg <ax>                       ; preserve length around call
         call update_drv                    ; reset currency and get latest CDS
         RestoreReg <cx>                    ; CX = length remaining
         jc   fdrv30                        ; CY = error, AX = code

         mov  ax,cx                         ; AX = length remaining

; ES:DI -> FSC for drive/volume
; DS:SI -> DPB for drive/volume

; Now that we have the FSC, we can determine what kind of FSD is "attached"

         cmp  di,FSC_RESERVED               ; FSC is FAT/non-FAT LOCAL
         je   fdrv10                        ; FSC = LOCAL FAT

; Local non-FAT FSD falls through here.  The volume is now "current", so the
; pointer to the FSC should be valid.  Use it to get the FSDName and length.

         lds  si,es:[di].FS_NAME            ; DS:SI -> FSDName

         mov  di,fdrv_ptr.Offst
         mov  bx,fdrv_ptr.Segmt             ; BX:DI -> user buffer.cbFSDName

; AX = length of buffer remaining
; BX:DI -> user buffer.cbFSDName
; DS:SI -> FSD name in FSC

         call load_2_fields                 ; load cbFSDName, szFSDName, (CX) = length remaining
         jc   fdrv22                        ; CY = error, AX = code
         mov    ax,ERROR_BUFFER_OVERFLOW    ; assume error
         sub    cx,LENGTH_WORD_FIELD        ; enough room for cbFSAData?
         jc     fdrv22                      ; no, go return error
         DosContext ds,ax
         mov    ax,users_buflen             ; (AX) = users buflen
         sub    ax,cx                       ; (AX) = length of data in buffer
         mov    cx,ax                       ; (CX) = length of data in buffer

; store 0 in cbFSAData
         xor  ax,ax                         ; AX = 0 to move into buffer
         CALLFAR SuWord                     ; cbFSAData = 0
         xor  ax,ax                         ; AX = 0, no error
         stc                                ; CY = LOCAL, don't call FSD
         jmp  SHORT fdrv30                  ; finish up

; Local FAT FS, fill in fields with 0s.

fdrv10:  mov  di,fdrv_ptr.Offst
         mov  bx,fdrv_ptr.Segmt             ; BX:DI -> user buffer.cbFSDName

; AX = length of buffer remaining
; BX:DI -> user buffer.cbFSDName
; DS:SI -> DPB for disk/volume

         call fill_fields_FAT               ; load with FAT info
         jc   fdrv30                        ; CY = error, AX = code
         xor  ax,ax                         ; AX = 0, no error
         stc                                ; CY = LOCAL, don't call FSD
         jmp  SHORT fdrv30                  ; return to not call the FSD


; Remote FSD, get FSDName from CDS->FSC->FS_NAME
; AX = length of buffer remaining
; BX:DI -> user buffer.cbFSDName
; DS:SI -> CDS

fdrv20:  mov  cx,di
         les  di,ds:[si].cd_devptr          ; ES:DI -> FSC
         SaveReg <es,di>                    ; save FSC@
         SaveReg <ds,si>                    ; save CDS@
         lds  si,es:[di].FS_NAME            ; DS:SI -> FSDName
         mov  di,cx

         call load_2_fields                 ; load cbFSDName, szFSDName

         RestoreReg <si,ds>                 ; DS:SI -> CDS@
         jc   fdrv22                        ; CY = protection error
         mov  es,bx                         ; ES:DI -> cbFSAData
         mov  cx,ds
         lea  ax,ds:[si].cd_fsd             ; CX:AX -> CDS dependent
         mov    si,ds:[si].cd_fsi.cdi_hVPB  ; (SI) = hVPB
         VPBContext ds,si                   ; (DS:SI) -> VPB
         mov    bx,ds
         lea    dx,[si].vpb_fsd             ; (BX:DX) -> VPBFSD

fdrv22:  RestoreReg <si,ds>                 ; DS:SI -> FSC@

fdrv30:  LeaveProc
         ret

fdrv50:  mov  ax,ERROR_BUFFER_OVERFLOW
         jmp  SHORT fdrv30

EndProc  fillin_drv


;***     q_ord_dev - ordinal device query
;
;   FUNCTION:   If we are querying a device by ordinal, we need to index
;               into the RMP list of devices to find the appropriate one.
;               we then call fillin_dev to fill in the data buffer with
;               information about the device.  and finally, we call
;               call_query_fsd to call the FSD entry point.
;
;   ENTRY:  SS:BX  -->   parameter packet (see FSQAttachStruc)
;           AX == ordinal of device to find
;
;   EXIT:  NC = successful
;          CY = error
;             AX = error code
;
;   CALLS: ord_FindCharDevEntry, fillin_dev, call_query_fsd, GetTempBuffer,
;          FreeTempBuffer
;
;   USES: AX, CX, DX, FLAGS
;
;   ALGORITHM:
;
;   call gettempbuffer and save ptr
;   call ord_FindCharDevEntry
;   if RESIDENT DEVICE
;        set resident flag
;        call fillin_dev
;   else
;        set pseudo flag
;        call fillin_dev
;        call call_query_fsd
;   endif
;   call freetempbuffer
;
        ASSUME DS:NOTHING, ES:NOTHING, SS:TASKAREA
Procedure q_ord_dev,NEAR
LocalVar q_odev_type,WORD                   ; 0 = pseudo, 1 = resident
LocalVar q_odev_pbuf,DWORD                  ; holder for temp buffer@
LocalVar q_odev_stkfrm,WORD                 ; stack frame ptr
LocalVar q_odev_devfsd,DWORD                ; local copy of devfsd

;       PMONLY
         CALLFAR GetTempBuffer              ; ES:DI -> temp buffer

    EnterProc <0,es,di,bx>                  ; init LocalVar

         SaveReg <es, di>                   ; save offset of buf@

         call ord_FindCharDevEntry          ; DS:SI -> deventry record
         jc   q_odev7                       ; CY = error no more items

; see if pseudo or resident device

         xor  ax,ax
         test ds:[si].DES_Type,DEVTYPE_PSEUDO
         jnz  q_odev0                            ; NZ = pseudo bit set
         inc  ax                            ; AX =  1 (resident)

q_odev0: mov  q_odev_type,ax                ; type = 0 if pseudo, 1 if resident
         les  di,q_odev_pbuf                ; ES:DI -> temp buffer

         lea    dx,q_odev_devfsd            ; SS:DX -> DEVFSD
         call fillin_dev                    ; fill in data buffer for device
         jc   q_odev2                       ; CY = error, AX = code

         cmp  q_odev_type,RES_CHAR
         je   q_odev2                       ; if resident, then don't call FSD

         push   q_odev_stkfrm
         call call_query_fsd                ; call the FSD
         pop    bx

q_odev2: RestoreReg <di, es>                ; restore offset of buf@
         pushf
         CALLFAR FreeTempBuffer             ; free it
         popf
         LeaveProc
         ret

q_odev7: mov  ax,ERROR_NO_MORE_ITEMS
         stc
         jmp  SHORT q_odev2

q_odev9: mov  ax,ERROR_INVALID_PATH
         stc
         jmp  SHORT q_odev2

EndProc  q_ord_dev


;***     q_ord_drv - ordinal drive query
;
;   FUNCTION:   If we are querying a drive by ordinal, we need to index
;               into the list of possible drives to find the appropriate one.
;               we then get the CDS for it, and from the CDS, extract the
;               necessary information.  If the drive is local, we must get
;               the VPB from the DPB from the CDS and reset the currency by
;               doing a ValidateCDS.  The VPB will then be current, and point
;               to the FSC for that drive.
;
;   ENTRY:  SS:BX  -->   parameter packet (see FSQAttachStruc)
;
;   EXIT:  NC = successful
;             CX = length to store into user's buffer length
;          CY = error
;             AX = error code
;
;   CALLS     ord_FindDrive, fillin_drv, call_query_fsd, gettempbuffer
;             freetempbuffer
;
;   USES: AX, CX, DX, DS, SI, FLAGS
;
;   ALGORITHM:
;
;   call gettempbuffer and save ptr
;   call ord_FindDrive
;   call fillin_drv
;   if NC,
;        call call_query_fsd
;   call freetempbuffer
;
        ASSUME DS:NOTHING, ES:NOTHING, SS:TASKAREA
Procedure q_ord_drv,NEAR
LocalVar q_odrv_pbuf,DWORD              ; holder for temp buf @
LocalVar q_odrv_stkfrm,WORD             ; stack frame ptr

;       PMONLY
        CALLFAR GetTempBuffer           ; ES:DI -> temp buffer

        EnterProc <es,di,bx>            ; init LocalVar

        SaveReg <es, di>                ; save offset of buf@

        call ord_FindDrive              ; AL = drive#
        jc   q_odrv4                    ; CY = drive not found/valid
        mov     si,TASKAREA:CurrTCB             ; (DI) = current TCB     ;### PTR B720732
        lds     si,TASKAREA:[si].TCBThisCDS     ; (ES:DI) = CDS pointer  ;### PTR B720732
;### PTR B720732 lds  si,[ThisCDS]              ; DS:SI -> temporary CDS

        call fillin_drv                 ; fill in data buffer for drive

; fillin_drv returns:
; NC if REMOTE DRIVE and no error
;   ES:DI -> user's buffer.cbFSAData
;   CX:AX -> CDSd
;   DS:SI -> FSC
; CY and AX = error code for REMOTE/LOCAL DRIVES and error
; CY and AX = 0 and CX = length of data buffer used for LOCAL DRV with no error

         jnc  q_odrv0                       ; NC = call FSD

         or   ax,ax                         ; any error code?
         jz   q_odrv2                       ; no, leave (NC)
         stc
         jmp  SHORT q_odrv2                 ; error, leave w/CY

q_odrv0: push   q_odrv_stkfrm               ; top of stack = stack frame
        call call_query_fsd                ; call the FSD
        pop    bx

q_odrv2: RestoreReg <di, es>
         pushf
         CALLFAR FreeTempBuffer             ; release buffer
         popf
         LeaveProc
         ret

q_odrv4: mov  ax,ERROR_NO_MORE_ITEMS
         jmp  SHORT q_odrv2

EndProc  q_ord_drv

;***     call_query_fsd - push parms and call FSD for query
;
;   FUNCTION: this routine merely pushes the parameters for the call to
;             FS_ATTACH for the query operation.  It also figures
;             out the length of data buffer left for the FSD, and then after
;             calling the FSD, calculates the amount of data buffer used by
;             both the kernel and FSD combined and returns that for later.
;
;   IMPORTANT NOTE: This routine makes sure that a device name of '\DEV\PRN'
;               is never seen by the FSD (the FSD will see \DEV\LPT1 in this
;               case).  To do this, we have a 10-byte localvar (10 bytes=
;               len(\dev\lpt1)), into which is copied the device name.
;               We then compare, starting at offset 5 into the localvar (this
;               skips over the \dev\ part), the device name to 'PRN<null>'.
;               If this comparison succeeds, we change the PRN<null> to
;               LPT1<null> and pass the address of the localvar to the FSD
;               as the device name.  Otherwise we pass the address of the
;               device name, which has previously been stored in the user's
;               data buffer.
;
;   ENTRY: top of stack = parameter frame pointer
;          DS:SI -> FSC
;          CX:AX -> if drive CDS dependent else if device devfsd
;          BX:DX -> if drive vpbfsd else if device 0
;          ES:DI -> user's buffer.cbFSAData
;
;
;   EXIT: NC - successful
;             CX = length of user buffer used, to load via thop on return
;         CY - error
;             AX - error code
;
;   Calls:    FuWord, FS_ATTACH
;
;   USES: AX, BX, CX, DX, DS, ES, DI, FLAGS
;
        ASSUME DS:NOTHING, ES:NOTHING, SS:TASKAREA
Procedure call_query_fsd,NEAR
ArgVar  cqf_stackptr,WORD                   ; offset of parms on stack

LocalVar cqf_fsc,DWORD                      ; FSC holder
LocalVar cqf_len,WORD                       ; data length holder
LocalVar cqf_len1,WORD                      ; data length holder 1
LocalVar cqf_buf,DWORD                      ; user buffer address
LocalVar cqf_cds,DWORD                      ; CDS/devfsd pointer
LocalVar cqf_vpb,DWORD                      ; VPB pointer
LocalVar cqf_lpt1name,10                    ; space for '\DEV\LPT1'

;       PMONLY
        EnterProc <ds,si,0,0,es,di,cx,ax,bx,dx> ; FSC = DS:SI
                                            ; len = 0
                                            ; user buf = ES:DI
                                            ; CDS sel = CX

        mov     bx,cqf_stackptr             ; (SS:BX) -> parms

; the length that we pass the FSD is the total length(passed in from user)
; minus the length of data taken up by the first 5 fields that have been filled
; in already. the FSD then fills in rgFSAData and cbFSAData with the length of
; data filled in in rgFSAData.
; the FSD also updates the variable cbDataLen with the length of data
; used in the data buffer by the FSD.

         sub  di,TASKAREA:[bx].FSQA_pDataBuffer.Offst   ; DI = part used

         SaveReg <ax,bx>
         mov  si,TASKAREA:[bx].FSQA_cbDataLength.Offst  ;
         mov  bx,TASKAREA:[bx].FSQA_cbDataLength.Segmt  ; BX:SI -> buflen
         CALLFAR FuWord                                 ; AX = buffer length
         mov  cx,ax
         RestoreReg <bx,ax>
         LJC  cqf_6

         sub  cx,di                    ; CX = total length - part already used
         LJC  cqf_5                    ; CY = error buffer overflow

         mov  cqf_len,cx
         mov  cqf_len1,cx

         mov  dx,FSD_QUERY
         push dx                       ; DX = QUERY

         mov  si,TASKAREA:[bx].FSQA_pDataBuffer.Offst
         mov  bx,TASKAREA:[bx].FSQA_pDataBuffer.Segmt ; BX:SI -> user data buf
         add  si,FIRST_2_FIELDS_LEN                   ; BX:SI -> szName
         mov  ax,ss
         mov  es,ax
         lea  di,cqf_lpt1name         ; SS:DI, ES:DI -> temp space for name
         mov  cx,10                   ; copy 10 bytes
         SaveReg <bx,di,si>
         CALLFAR FuBuffz
         RestoreReg <si,di,bx>        ; SS:DI, ES:DI -> temp name
                                      ; BX:SI -> szName (user data buff)

         cmp  word ptr cqf_lpt1name+5,'RP'  ; is name 'PRN' ??
         jne  cqf00
         cmp  word ptr cqf_lpt1name+7,'N'
         jne  cqf00
         mov  word ptr cqf_lpt1name+5,'PL'  ; yes, change to 'LPT1' and pass
         mov  word ptr cqf_lpt1name+7,'1T'  ; pointer to this new name to FSD
         mov  byte ptr cqf_lpt1name+9,0
         mov  si,di
         mov  bx,ss                    ; bx:si -> '\DEV\LPT1'

cqf00:   push bx
         push si                       ; push ptr to name on stack

         push   cqf_vpb.Segmt
         push   cqf_vpb.Offst           ; pass pointer to vpbfsd or 0

         push   cqf_cds.Segmt
         push   cqf_cds.Offst           ; pass pointer to cdfsd or devfsd

         push cqf_buf.Segmt
         push cqf_buf.Offst            ; user's buffer.cbFSAData@ on stack

         lea  di,cqf_len               ; DI = offset of data length

         push ss
         push di                       ; @ of length of usable user buffer

         les  di,cqf_fsc

; FSD can trash AX, BX, CX, DX, ES

         call es:[di].FS_ATTACH

         or   ax,ax
         stc
         jnz  cqf_0                    ; AX != 0 if error
         mov  ax,cqf_len1              ; AX = length remaining before call
         sub  ax,cqf_len               ; AX = length remaining after call

         DosContext ds,cx
         mov  cx,users_buflen          ; CX = initial buf length
         sub  cx,ax                    ; CX = total space used

         clc

cqf_0:   LeaveProc
         ret

cqf_5:   mov  ax,ERROR_BUFFER_OVERFLOW
         jmp  cqf_0

cqf_6:   mov  ax,cx                    ; AX = CX = error code
         jmp  cqf_0

EndProc  call_query_fsd

;***     ord_FindCharDevEntry - find a character device based on ordinal value
;
;   FUNCTION: This routine calls Rmp_FindN to find the Nth busy record in the
;             RMP segment, where 'N' is the input ordinal. If there are less
;             than 'N' busy records then CY is returned.
;
;   ENTRY:    AX - ordinal number of entry to find
;
;   EXIT:     NC - successful
;                  DS:SI -> start of found record
;             CY - error
;
;   CALLS:    Rmp_Req, Rmp_FindN
;
;   USES: AX, CX, DS, SI, ES, DI, FLAGS

        ASSUME DS:NOTHING, ES:NOTHING, SS:TASKAREA
Procedure ord_FindCharDevEntry,NEAR

;       PMONLY TASKTIME
        SaveReg <bx,dx>
        DosContext <es>,di
        DosContext <ds>,di
        mov     di,offset DOSGROUP:CharDevRMPRec ; (ES:DI) = handle to RMP seg
        push    ax
        mov     dl,1
        CALLFAR Rmp_Wait                    ; Wait for RMP semaphore
        pop     ax
        dec     ax                          ; Rmp_FindN is zero-based
        mov     dx, size rbheadr_s          ; Start search at first record
        xor     cx,cx                       ; Match all records; BX irrelevant
        CALLFAR Rmp_FindN
        mov     si,di
        pushf
        push    es
        pop     ds                          ; (DS:SI) = ordinalth entry
        POPAF   ax                          ; restore CY status
        RestoreReg <dx,bx>
        ret

EndProc  ord_FindCharDevEntry


;***     ord_FindDrive - find a drive based on ordinal value
;
;   FUNCTION: This routine will call GetVisDrv for a drive.  If the drive is
;             valid, it will check to see if the index of the drive into the
;             drive table is equal to the ordinal number requested.  If so,
;             then we return with the information returned from GetVisDrv.
;             If not, we increment the drive #, and call GetVisDrv to check
;             on the next drive.  We continue until we have gone through all
;             drives(error), or until we find the ordinal drive requested.
;
;   ENTRY:    AX - ordinal number of entry to find
;
;   EXIT:     NC - successful
;                  AL = 1 based drive# (A = 1)
;                  DS:SI -> ThisCDS
;                  [ThisCDS] = DS:SI
;             CY - error bad drive
;
;   CALLS:    GetVisDrv
;
;   USES: AX, CX, FLAGS
;
;   ALGORITHM:
;
;   start with drive #1
;   while drive # <= LastDrive in system
;        call getvisdrv
;        if NC (valid)
;             if ordinal-- = 0
;                  break
;        endif
;        drive# ++
;   endwhile
;   increment drive# to be 1 based
;
        ASSUME DS:NOTHING, ES:NOTHING, SS:TASKAREA
Procedure ord_FindDrive,NEAR

;       PMONLY TASKTIME
        SaveReg <bx>

        SaveReg <ds>
        DosContext      ds
        mov     bl,[uchLastDrive]
        inc     bl                          ; bl = 1 based last drive in system
        RestoreReg      <ds>

         mov  cx,ax                         ; CX = ordinal
         xor  ax,ax                         ; AX = drive #
ofd_0:   inc  ax
         cmp  al,bl
         ja   ofd_5                         ; can't find drive
         SaveReg <ax,bx>
         CALLFAR GetVisDrv                  ; AL = 0 based drive#
         RestoreReg <bx,ax>
         jc   ofd_0                         ; CY = not found, try next one
         loop ofd_0                         ; CX--, go get next one

; we will fall through here if the ordinal'ith one has been reached

ofd_3:   RestoreReg <bx>
         ret

ofd_5:   stc
         jmp  SHORT ofd_3

EndProc  ord_FindDrive

;***     load_2_fields  - load up 2 fields in user's buffer
;
;   FUNCTION: Loads cbFSDName and szFSDName in the user's buffer
;
;   ENTRY: DS:SI -> string to move into buffer (src)
;          BX:DI -> user's buffer.cbFSDName    (dst)
;          AX = length of user's buffer remaining
;
;   EXIT:  CY - error
;             AX = error code
;          NC - successful
;             BX:DI -> user's buffer.cbFSAData
;             CX = updated remaining length of buffer
;
;   USES: AX, BX, CX, DS, SI, DI, FLAGS
;
;   CALLS: DStrLen, SuWord, SuBuff
;

        ASSUME DS:NOTHING, ES:NOTHING, SS:TASKAREA
Procedure load_2_fields,NEAR
LocalVar l2f_buflen,WORD                    ; remaining buflength

;       PMONLY TASKTIME
         EnterProc <ax>

         SaveReg <bx>

         CALLFAR DStrLen                    ; CX = length(FSDName) + NULL

         SaveReg <cx>
         add  cx,LENGTH_WORD_FIELD          ; CX = length of stuff to put in
         cmp  ax,cx                         ; AX = length left(NC if some left)
         RestoreReg <cx>
         jc   l2f_1                         ; CY = error buffer overflow

         mov  ax,cx
         dec  ax                            ; AX = length(FSDName)

; Move the length(FSDName) field into the buffer.cbFSDName

         CALLFAR SuWord                     ; AX into BX:DI
         jc   l2f_0

; Move the FSDName field into the buffer.szFSDName

         push   cx
         CALLFAR SuBuff                     ; DS:SI into BX:DI for CX length
         pop    ax                          ; (AX) = length(FSDName) + NULL
         add    ax,LENGTH_WORD_FIELD        ; CX = length of buffer used
         mov    cx,l2f_buflen
         sub    cx,ax                       ; (CX) = remaining buflen

l2f_0:   RestoreReg <bx>
         LeaveProc
         ret                                ; BX:DI -> user's buffer.cbFSAData

l2f_1:   mov  ax,ERROR_BUFFER_OVERFLOW
         jmp  SHORT l2f_0

EndProc  load_2_fields

;***     load_3_fields  - load up first 3 fields in user's buffer
;
;   FUNCTION: loads iType, cbName and szName in the user's buffer
;
;   ENTRY: AX - word to move into buffer       (src)
;          DS:SI -> string to move into buffer (src)
;          BX:DI -> user's buffer.iType        (dst)
;          DX -> user's stack frame
;
;   EXIT:  CY - error
;             AX = error code
;          NC - successful
;             AX = length of user's buffer remaining
;
;   USES: AX, BX, CX, DI, DS, SI, FLAGS
;
;   CALLS: DStrLen, SuWord, SuBuff, FuWord
;

        ASSUME DS:NOTHING, ES:NOTHING, SS:TASKAREA
Procedure load_3_fields,NEAR
LocalVar l3f_kstrptr,DWORD                  ; holder for Kernel String Ptr
LocalVar l3f_usrbufptr,DWORD                ; holder for User Buffer Ptr
LocalVar l3f_cbname,WORD                    ; holder for cbname
LocalVar l3f_kstrlen,WORD                   ; holder for Kernel String length
LocalVar l3f_buf_left,WORD                  ; holder for buflen remaining

;       PMONLY TASKTIME
        EnterProc <ds,si,bx,di,ax>

         SaveReg <bx>

         CALLFAR DStrLen                    ; CX = length(devicename) + NULL
         mov  l3f_kstrlen,cx

         mov  si,dx
         mov  bx,TASKAREA:[si].FSQA_cbDataLength.Segmt
         mov  si,TASKAREA:[si].FSQA_cbDataLength.Offst  ; BX:SI -> buflen
         CALLFAR FuWord                     ; AX = buffer length
         jc   l3f_0                         ; protection error
         DosContext ds,di
         mov  users_buflen,ax               ; save for later
         add  cx,LENGTH_2WORD_FIELD         ; CX = length of stuff to put in
         sub  ax,cx                         ; AX = length left(NC if some left)
         mov  l3f_buf_left,ax               ; store it away

         jc   l3f_1                         ; CY = buffer overflow
         mov  di,l3f_usrbufptr.Offst
         mov  bx,l3f_usrbufptr.Segmt        ; BX:DI -> cbName field
         mov  ax,l3f_cbname
         CALLFAR SuWord                     ; BX:DI -> cbName field
         jc   l3f_0                         ; protection error

         mov  cx,l3f_kstrlen
         mov  ax,cx
         dec  ax                            ; AX = length(devicename) - NULL

; Move the length(devicename) field into the buffer.cbName

         CALLFAR SuWord                     ; AX into BX:DI
         jc   l3f_0                         ; protection error

; Move the devicename field into the buffer.szName

         lds  si,l3f_kstrptr
         CALLFAR SuBuff                     ; DS:SI into BX:DI for CX bytes

         mov  ax,l3f_buf_left               ; AX = length remaining in buffer

l3f_0:   RestoreReg <bx>
         LeaveProc
         ret

l3f_1:   mov  ax,ERROR_BUFFER_OVERFLOW
         jmp  SHORT l3f_0

EndProc  load_3_fields

;***     fill_fields_0  - load up 3 fields in user's buffer with NULLs
;
;   FUNCTION: Loads cbFSDName with 0, szFSDName with NULL and cbFSAData with 0
;
;   ENTRY: BX:DI -> user's buffer.cbFSDName    (dst)
;          AX = length of user's buffer remaining
;
;   EXIT:  CY - error
;             AX = error code
;          NC - successful
;             BX:DI -> user's buffer.cbFSAData
;             CX = length of user's data buffer used(loaded by THOP on exit)
;
;   USES: AX, BX, CX, DS, SI, DI, FLAGS
;
;   CALLS:  SuWord, SuByte
;

        ASSUME DS:NOTHING, ES:NOTHING, SS:TASKAREA
Procedure fill_fields_0,NEAR

;       PMONLY TASKTIME
         SaveReg <bx>

         mov  cx,LENGTH_0_FIELDS            ; CX = length of stuff to put in
         sub  ax,cx                         ; AX = length left(NC if some left)
         jc   ff0_1                         ; CY = error buffer overflow
         DosContext ds,cx
         mov  cx,users_buflen               ; CX = initial buf size
         sub  cx,ax                         ; CX = initial length - length used

; Move the length of FSDName field(0) into the buffer.cbFSDName

         xor  ax,ax                         ; AX = 0
         CALLFAR SuWord                     ; AX into BX:DI
         jc   ff0_0                         ; CY = protection error

; Move the FSDName field(0) into the buffer.szFSDName

         xor  ax,ax                         ; AX = 0
         CALLFAR SuByte                     ; AL into BX:DI
         jc   ff0_0                         ; CY = protection error

; Move the cbFSAData field(0) into the buffer.cbFSAData

         xor  ax,ax                         ; AX = 0
         CALLFAR SuWord                     ; AX into BX:DI

ff0_0:   RestoreReg <bx>
         ret                                ; BX:DI -> user's buffer.cbFSAData

ff0_1:   mov  ax,ERROR_BUFFER_OVERFLOW
         jmp  SHORT ff0_0

EndProc  fill_fields_0

;***     fill_fields_FAT  - load up 3 fields in user's buffer with FAT data
;
;   FUNCTION: If the volume is certainly a FAT volume then load
;             cbFSDName with 3, szFSDName with "FAT",0 and cbFSAData
;             with 0 in the user's buffer.  If the volume defaulted
;             to FAT, then load cbFSDName with 7, szFSDName with
;             "UNKNOWN",0 and cbFSAData with 0 in the user's buffer.
;
;   ENTRY  BX:DI -> user's buffer.cbFSDName
;          DS:SI -> DPB
;          AX = length of user's buffer remaining
;
;   EXIT:  CY - error
;             AX = error code
;          NC - successful
;             BX:DI -> user's buffer.cbFSAData
;             CX = length of data buffer used(loaded up via thops later)
;
;   USES: AX, BX, DI, DS, ES, FLAGS
;
;   CALLS: SuWord, SuBuff
;
        ASSUME DS:NOTHING, ES:NOTHING, SS:TASKAREA
Procedure fill_fields_FAT,NEAR
;       PMONLY TASKTIME

LocalVar ffF_VPB_fMisc,BYTE                ; misc flags byte from VPB

         SaveReg <bx,bx>
         mov  bx,ds:[si].dpb_hVPB           ; BX = hVPB
         VPBContext es,bx                   ; ES:BX -> VPB
         mov  bl,es:[bx].vpb_fMisc          ; (bl) = VPB Misc flags
         mov  [ffF_VPB_fMisc],bl            ; save the VPB Misc flags
         RestoreReg <bx>

         mov  cx,LENGTH_RESIDENT_FIELDS     ; CX = length of stuff to put in
         test [ffF_VPB_fMisc],VPB_FM_UNKNOWN; Is the volume definitely FAT?
         jz   ffF_10                        ;   yes, cx value is correct

         mov  cx,LEN_UNK_RESIDENT_FIELDS    ; CX = length of stuff to put in

ffF_10:  sub  ax,cx                         ; AX = length remaining
         jc   ffF_50                        ; CY = error buffer overflow
         DosContext ds,cx
         mov  cx,users_buflen               ; CX = initial buf length
         sub  cx,ax                         ; CX = initial - length remaining

         mov  ax,FAT_FSD_name_len-1         ; AX = 3
         test [ffF_VPB_fMisc],VPB_FM_UNKNOWN; Is the volume definitely FAT?
         jz   ffF_20                        ;   yes, ax value is correct

         mov  ax,UNKNOWN_FSD_name_len-1     ; AX = 7

ffF_20:  CALLFAR SuWord                     ; cbFSDName = 0
         jc   ffF_40

         DosContext ds
         mov  si,offset DOSGROUP:FAT_FSD_name ; SI -> "FAT",0
         test [ffF_VPB_fMisc],VPB_FM_UNKNOWN; Is the volume definitely FAT?
         jz   ffF_30                        ;   yes, ax value is correct

         mov  si,offset DOSGROUP:UNKNOWN_FSD_name ; SI -> "UNKNOWN",0

ffF_30:  SaveReg <cx>
         mov  cx,UNKNOWN_FSD_name_len       ; CX = max length of string to copy
         SaveReg <bx>                       ; preserve user's seg/sel
         CALLFAR SuBuffz                    ; store szFSDName
         RestoreReg <bx>
         RestoreReg <cx>                    ; CX = length remaining
         jc   ffF_40

         xor  ax,ax                         ; AX = 0 to move into buffer
         CALLFAR SuWord                     ; cbFSAData = 0
         dec  di
         dec  di                            ; BX:DI -> user's buffer.cbFSAData

ffF_40:  RestoreReg <bx>
         ret

ffF_50:  mov  ax,ERROR_BUFFER_OVERFLOW
         jmp  SHORT ffF_40

EndProc  fill_fields_FAT


;***     update_drv - reset currency for drive, update CDS and return FSC
;
;   FUNCTION: This routine will go out and make sure that the Volume
;             pointed to by the DPB is current.  Using the "updated"
;             hVPB, we can obtain the VPB, and from that, the FSC for
;             the drive in question.
;   NOTE:     This routine addresses LOCAL drives only!!!
;
;   ENTRY: DS:SI -> ThisCDS
;
;   EXIT:  CY - error
;             AX = I/O packet status
;          NC - successful
;             ES:DI -> FSC for drive/volume
;             DS:SI -> DPB for drive/volume
;
;   USES: AX, DI, ES, FLAGS
;
;   CALLS: WhatVolume
;
;   ALGORITHM:
;
;   get DPB from CDS
;   call whatvolume
;   get VPB from hVPB (VPBContext)
;   get FSC from VPB
;
        ASSUME DS:NOTHING, ES:NOTHING, SS:TASKAREA
Procedure update_drv,NEAR
;       PMONLY TASKTIME

         SaveReg <bx>

; The idea here is to first get the DPB from the CDS

         SaveReg <ds,si>
         SaveReg <ds,si>
         RestoreReg <di,es>                 ; ES:DI -> CDS

         lds  si,es:[di].cd_devptr          ; DS:SI -> DPB

         CALLFAR WhatVolume                 ; DS:SI -> DPB w/ hVPB updated
         RestoreReg <di,es>                 ; ES:DI -> CDS
         jc   ldrv_0                        ; CY = error, AX = code

; From the VPB "handle" we can get the VPB, which points to an FSC
; we will update the hVPB in the "copy" of the CDS regardless of whether
; or not it was current/needed currency update.  this is to save the
; time/space to check for whether or not WhatVOlume had to do any "real work"

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

         mov  es:[di].cd_fsi.cdi_hVPB,bx    ; CDS.hVPB = DPB.hVPB

         VPBContext es,bx                   ; ES:BX -> VPB

         les  di,es:[bx].vpb_FSC            ; ES:DI -> FSC for drive/volume

ldrv_0:  RestoreReg <bx>                    ; BX = possible stack frame
         ret

EndProc  update_drv

;***  Path_Check  - checks the user given path name
;
;       ENTRY:        ds:si -> path name to be checked (for drive or pseudo device)
;
;       EXIT:         CY -> error code in AX
;                     NC -> ZR  -> drive
;                           NZ  -> DS:SI -> device name (excluding \DEV\)
;
;       USES:         ds,si,ax (if error)
;
;       This procedure checks the given path by checking for "d:" if not,
;       it assumes it to be a device and calls transpath to canonicalize the
;       path. Now once again it checks the path for '\DEV\' for pseudo
;       device
;
        ASSUME DS:NOTHING,ES:NOTHING,SS:TASKAREA
Procedure  Path_Check,NEAR,LOCAL

         SaveReg        <es,di,bx>
         push   ax

         cmp     byte ptr ds:[si+1],":"
         jnz     pck20                ; Not a "d:"; device so call transpath
         cmp  BYTE PTR ds:[si+2],0          ; NULL terminator
         jnz  pck50                         ; no, error bad drive
;
; DRIVE only ; ZR flag already set and CY cleared
;
pck05:   pop    ax
pck10:   RestoreReg     <bx,di,es>
         ret
;
; It is not a drive. Check for device.
;
pck20:  TaskContext <es>,di
        mov     bx,TASKAREA:CurrTCB                ;  (bx) = current TCB      ;### PTR B720732
        mov     di,TASKAREA:[bx].TCBpOpenBuf       ; (es:di) -> result buffer ;### PTR B720732
;### PTR B720732         mov    di,TASKAREA:[ptda_pOpenBuf] ; (es:di) -> result buffer
        SaveReg <di,bp>
        mov     ax,-1                   ; Don't validate CDS
        mov     bx,NOT (FPATH_JOIN)     ; No need to check for JOIN
                                        ;72417 moved from PTDA to TCB
        SetBP_CurrTCB
        mov     [bp].TCBCurr_Dir_End,ax ; assume no current dir in path
        UnSetBP_CurrTCB
                                        ;72417 end
        push    bx
        mov     bx,TASKAREA:CurrTCB     ; (bx) = current TCB     ;### PTR B720732
        mov     TASKAREA:[bx].TCBThisCDS.Offst,ax     ; No CDS   ;### PTR B720732
;### PTR B720732 mov    [ThisCDS].Offst,ax      ; no CDS yet
        pop     bx
                                        ;72417 moved from PTDA to TCB
        SetBP_CurrTCB
        mov     [bp].TCBcMeta,al        ; initialize meta count
        UnSetBP_CurrTCB
                                        ;72417 end

; defect 57412 change start here, replaces call to canpath

;       CALLFAR canpath       ; expand path    ( replaced by following code )

; set dummy drive in result ( C: )   ES:DI

        mov byte ptr es:[di],  "C"
        mov byte ptr es:[di+1],":"
        mov byte ptr es:[di+2], 0
        add di,2                        ; es:di -> NULL after C:

; canonicalize the path into result buffer, append to C: in es:di
; ds:si -> source buf (no drive)
; es:di -> result buf after C: (where we should copy the canned path)
; es:bx -> backup limit ( NULL after C: )

        mov     bx,di                   ; es:di -> NULL after C: (backup limit)
        mov     ax,di                   ; es:ax -> start of result buf
        add     ax,MAXPATHLEN           ; es:ax -> end of result buf
        SaveReg    <di,bp>
        mov     bp,ax                   ; es:bp -> end of result buf
        CALLFAR Canonicalize
        RestoreReg <bp,di>              ; ThisCDS -> CDS

; defect 57412 change end   here

        RestoreReg <bp,si>              ; SI -> buffer in PTDA
        TaskContext     ds              ;
        jc   pck40                      ;  bad path
                                        ;72417 moved from PTDA to TCB
        SetBP_CurrTCB
        cmp     [bp].TCBcMeta,-1        ; meta chars present?
        UnSetBP_CurrTCB
                                        ;72417 end
        jne     pck50                   ; yes, go return error
        add    si,2                     ; skip "d:" that was put in by canpath
;
; Check for "\DEV\" as prefix for device name, if not, error.
;
         SaveReg        <ds,si>
         DosContext es
         mov    di,offset DOSGROUP:DevPrefix ; ES:DI -> prefix to match
         CALLFAR PathPref               ; ZR = \DEV\ is prefix
         RestoreReg     <si,ds>
         jnz    pck40                   ; no "\DEV\" prefix, error bad path
         add    si,DEV_PREFIX_LEN       ;skip \dev\ also; THIS OP. ALSO
                                        ; SETS FLAG TO NZ AND CLEARS CY
         push   si
         CALLFAR ScanPathChar           ; look for '\' or '/' in pseudo device name
         pop    si
         clc
         jnz    pck05                   ; no path chars - go return

pck40:   mov  ax,ERROR_INVALID_PATH     ; bad path
         jmp    short pck60

pck50:   mov  ax,ERROR_INVALID_DRIVE             ; bad drive
pck60:   stc
         pop    di                              ; throw the saved ax value away
;        jmp  SHORT pck10        ; 57412
         jmp        pck10        ; 57412

EndProc Path_Check

_EndFSCTLGROUP label byte
        PUBLIC  _EndFSCTLGROUP

;***    w_FSCtl - Extendable File System Control Worker
;
;   w_FSCtl allows an FSD to expand it's functionality by offering an IOCtl
;   like interface so that the user can roll their own FSD functions.
;   It is callable in 3 different ways:
;   1) With a file handle as input.  This method allows us to get the SFT
;      entry for the handle given.  From the SFT, we can get the FSC, and
;      call the FSD it points to.
;   2) With a pathname as input.  We have to call TransPath to expand the
;      path for us, and to give us back a pointer to an FSC.  Using this
;      pointer, we can call the appropriate FSD.
;   3) With FSD name as input.  A call to fscFind will return an FSC for the
;      given FSD, and we call the FSD using the pointer in the FSC.
;
;   INPUT:    AX     ===   routing type
;                            1 = handle based
;                            2 = path based
;                            3 = FSD name based
;             CX     ===   file handle
;             SS:BX  -->   parameter packet (see FileSysCtlStruc)
;
;   NOTE: if routetype is 1, the routename MUST be 0, else error
;         if routetype is 2 or 3, handle MUST be -1
;
;   EXIT:     NC -
;               AX = 0 (success)
;             CY -
;                  AX = error_interrupt
;                       error_invalid_category
;                       error_invalid_function
;                       error_interrupt
;                       error_invalid_handle
;                       error_invalid_fsd_name
;                       error_invalid_drive
;
;   CALLS:    handle_route, path_route, fsd_route, call_control_fsd
;             FreeTempBuffer, Take_Kernel_Sem, Clear_Kernel_Sem
;
;   USES: AX, BX, CX, DX, SI, DI, ES, DS, FLAGS
;
;   ALGORITHM:
;
;   if routemethod > max allowable, error
;   if file handle based
;        call handle_route
;        get FSC from SFT
;        call fsd_fsctl
;   endif
;   if path name based
;        call take_kernel_sem
;        call gettempbuffer
;        call path_route
;        call fsd_fsctl
;        call freetempbuffer
;        call clear_kernel_sem
;   endif
;   if fsd name based
;        call fsd_route
;        call fsd_fsctl
;   endif
;   return
;
        ASSUME DS:NOTHING, ES:NOTHING
Procedure w_FSCtl,<FAR16,FARONLY>
LocalVar  fsc_StackFrame,WORD           ; Pointer to parameters on stack
LocalVar  fsc_pPathBuf,DWORD            ; Pointer to temp buffer for path
LocalVar  fsc_pCDSBuf,DWORD             ; Pointer to temp buffer for CDS
LocalVar  fsc_sftptr,DWORD              ; Pointer to sft
LocalVar  fsc_openbuf,PMPATHLEN
LocalVar  fsc_dummy,WORD                ; KLUDGE: see gshare.asm
LocalVar  fsc_dummycds,<SIZE curdir>

;       PMONLY
        EnterProc <bx>              ; $TJMP trashes AX and BX
        push    bx
        mov     bx,TASKAREA:[CurrTCB]                   ;### PTR B720732
        lea     dx,fsc_openbuf
        mov     TASKAREA:[bx].TCBpOpenBuf,dx            ; Initialize TCBpTmpCDS ;### PTR B720732
;### PTR B720732        mov     TASKAREA:[ptda_pOpenBuf],dx ; Initialize ptda_pOpenBuf
        lea     dx,fsc_dummycds
        mov     TASKAREA:[bx].TCBpTmpCDS.Offst,dx       ; Initialize TCBpTmpCDS ;### PTR B720732
;### PTR B720732        mov     TASKAREA:[ptda_pCDS],dx ; Initialize ptda_pCDS


        mov     bx,fsc_StackFrame       ; SS:BX -> user arguments on stack
        cmp     TASKAREA:[bx].FSC_iFunction, FSCTL_GET_NEXT_ROUTE_NAME
        LJE     fsc_getroute

        pop     bx                                      ;### PTR B720732

        CALLFAR $TJMP                   ; Jump through table (by what is in AL)
        db      MAXFSCTLROUTETYPE       ; maximum route type allowed
        dw      fsc_102                 ; Error case
        dw      fsc_102                 ; 0 is an error
        dw      NMPIPESWAPCODE:fsc_handlebased
    .errnz   FSCTL_HANDLEROUTE-1
        dw      NMPIPESWAPCODE:fsc_pathbased
    .errnz   FSCTL_PATHROUTE-2
        dw      NMPIPESWAPCODE:fsc_FSDbased
    .errnz   FSCTL_FSDROUTE-3

;**
; The routemethod is file handle based
;
fsc_handlebased:
        mov     bx,fsc_StackFrame       ; SS:BX -> user arguments on stack
        call    handle_route            ; ES:DI -> FSC
                                        ; DS:SI -> SFT (LOCKED)
        LJC     fsc_100                 ; CY = error
        mov     fsc_sftptr.Offst,si     ;
        mov     fsc_sftptr.Segmt,DS     ; save the sft ptr for later use
        mov     ax,FSCTL_HANDLEROUTE    ; AX = 1
        call    FSD_FSCtl               ; route the request
        les     di,fsc_sftptr           ;
        CALLFAR SFUnlock                ;
        jmp     fsc_100


;**
; The routemethod is path name based
;
fsc_pathbased:
        mov     bx,fsc_StackFrame       ; SS:BX -> user arguments on stack
        cmp     TASKAREA:[bx].FSC_szRouteName.Segmt,0 ; is seg ptr non-zero?
        LJE     fsc_120             ; no, go fault user process
        lds     si,TASKAREA:[bx].FSC_szRouteName   ; DS:SI -> RouteName

;
; Take the kernel semaphore so that we can call TransPath.
;
        CALLFAR Take_Kernel_Sem     ; get kernel semaphore
        LJC     fsc_104             ; CY = abnormal wakeup(interrupt)

        mov     bx,fsc_StackFrame   ; SS:BX -> user arguments on stack
        call    path_route          ; ES:DI -> FSC
        LJC     fsc_15              ; CY = error bad path
;
; 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.
;
        SaveReg <ES,DI>             ; Save pointer to FSC
        CALLFAR PathIncVPBRef
;
; In order to improve performance, we try to not hold the kernel semaphore
; when we call the FSD. This means that we need to copy the relevant data
; from the PTDA into temp buffers, and pass pointers to the temp buffers
; to the FSD in the ArgDat structure. Once the data has been copied, we can
; free the kernel semaphore and allow other threads from this process to
; do other I/O.
;
        CALLFAR GetTempBuffer       ; ES:DI -> temp buffer for path
        ASSUME ES:NOTHING
        mov     fsc_pPathBuf.Offst,di
        mov     fsc_pPathBuf.Segmt,es
        TaskContext ds
                                    ;72417 moved from PTDA to TCB
        mov     si,CurrTCB
        mov     si,TASKAREA:[si].TCBWFP_Start ; DS:SI -> well-formed path
                                    ;72417 end
        CALLFAR FStrCpy             ; Copy WFP into temp buffer

        mov     si,TASKAREA:CurrTCB             ; (SI) = current TCB     ;### PTR B720732
        lds     si,TASKAREA:[si].TCBThisCDS     ; (DS:SI) = CDS pointer  ;### PTR B720732
;### PTR B720732 lds    si,TASKAREA:[ThisCDS]   ; DS:SI -> CDS
;### PTR B720732        SegAssert <SS>,<DS>             ; assert that ThisCDS was in PTDA
;### PTR B720732        cmp     si,-1                   ; Is there a CDS?
;### PTR B720732        jne     fsc_10                  ; yes, alloc temp buffer and copy

;### PTR B720732        mov     fsc_pCDSBuf.Offst,-1    ; indicate no CDS
;### PTR B720732        mov     fsc_pCDSBuf.Segmt,0
;### PTR B720732        mov     bx,CurrTCB
;### PTR B720732        mov     TASKAREA:[bx].TCBThisCDS.Offst,-1
;### PTR B720732        mov     TASKAREA:[bx].TCBThisCDS.Segmt,0        ; gp fault if used
;### PTR B720732        jmp     fsc_13a                 ; skip allocation and copy

;### PTR B720732 fsc_10:        CALLFAR GetTempBuffer           ; ES:DI -> temp buffer for CDS
;### PTR B720732        mov     fsc_pCDSBuf.Offst,di
;### PTR B720732        mov     fsc_pCDSBuf.Segmt,es
;### PTR B720732        mov     bx,CurrTCB
;### PTR B720732        mov     TASKAREA:[bx].TCBThisCDS.Offst,di
;### PTR B720732        mov     TASKAREA:[bx].TCBThisCDS.Segmt,es   ; Store pointer to CDS in TCBThisCDS
;### PTR B720732;
;### PTR B720732; Copy the CDS into the temp buffer. In order to avoid copying the whole
;### PTR B720732; CDS each time (most likely the cdi_text field will be short), we do the
;### PTR B720732; copy in pieces.
;### PTR B720732;
;### PTR B720732; NOTE: This code assumes that cdi_text is the last thing in the CDS.
;### PTR B720732;
;### PTR B720732.errnz  (size curdir - (cd_fsi + cdi_text + PMDIRSTRLEN))

;### PTR B720732        mov     cx,(size curdir) - PMDIRSTRLEN
;### PTR B720732        shr     cx,1
;### PTR B720732        rep     movsw
;### PTR B720732        jnc     fsc_13
;### PTR B720732        movsb
;### PTR B720732 fsc_13: CALLFAR FStrCpy                    ; Copy directory string
;### PTR B720732;
;### PTR B720732; We now have a copy of the path and of the CDS. We now determine what the
;### PTR B720732; value of Curr_Dir_End is for the path in our temp buffer, and pass that
;### PTR B720732; in to FSD_FsCtl.
;### PTR B720732;
;### PTR B720732 fsc_13a:
                                    ;72417 moved from PTDA to TCB
        SetBP_CurrTCB
        mov     cx,TASKAREA:[bp].TCBCurr_Dir_End
        UnSetBP_CurrTCB
        cmp     cx,-1
        je      fsc_14              ; No need to adjust it
        SetBP_CurrTCB
        sub     cx,TASKAREA:[bp].TCBWFP_Start
        UnSetBP_CurrTCB
                                    ;72417 end

        add     cx,fsc_pPathBuf.Offst   ; CX is offset into temp buffer of current dir end
;
; Clear the kernel semaphore before calling the FSD.
;
fsc_14: CALLFAR Clear_Kernel_Sem    ; release kernel semaphore
        mov     ax,FSCTL_PATHROUTE  ; AX = 2
        lds     si,fsc_pPathBuf     ; DS:SI -> path string to pass to FSD
        ASSUME DS:NOTHING
        mov     bx,fsc_StackFrame   ; SS:BX -> user arguments on stack
        RestoreReg <DI,ES>          ; ES:DI -> FSC
        call    FSD_FSCtl           ; route the request

        pushf                       ; Save any error indication
;### PTR B720732        les     di,fsc_pCDSBuf
;### PTR B720732        cmp     di,-1               ; was CDS temp buffer allocated?
;### PTR B720732        je      fsc_14a             ; no, skip freeing the buffer
;### PTR B720732        CALLFAR FreeTempBuffer      ; Free temp buffer used for CDS
;### PTR B720732fsc_14a:
        les     di,fsc_pPathBuf
        CALLFAR FreeTempBuffer      ; Free temp buffer used for path
        CALLFAR PathDecVPBRef       ; Remove path referent
        popf                        ; Restore any error indication
        jmp     fsc_100             ; leave

;
; Got an error from path_route. Free the kernel semaphore and return.
;
fsc_15: CALLFAR Clear_Kernel_Sem    ; release kernel semaphore
        stc
        jmp     fsc_100             ; Return the error


;**
; The routemethod is FSD name based
;
fsc_FSDbased:
        mov     bx,fsc_StackFrame   ; SS:BX -> user arguments on stack
        cmp     TASKAREA:[bx].FSC_szRouteName.Segmt,0 ; is seg ptr non-zero?
        je      fsc_120             ; no, go fault user process
        lds     si,TASKAREA:[bx].FSC_szRouteName   ; DS:SI -> FSD name
        mov     bx,fsc_StackFrame   ; SS:BX -> user arguments on stack
        call    fsd_route           ; ES:DI -> FSC
        jc      fsc_100             ; CY = error, AX = code

        mov     ax,FSCTL_FSDROUTE   ; AX = 3
        call    FSD_FSCtl           ; route the request
        jmp     fsc_100             ; leave

; Return a list of route names
fsc_getroute:
        mov     cx,TASKAREA:[bx].FSC_cbDataLength
        cmp     cx,6                ; Make sure there is enough room for
                                    ;  at least the route name handle
                                    ;  and a 1 character name.
        jl      fsc_107             ; if the data length is insufficient,
                                    ;  return invalid parm
        mov     dx,cx
        lds     si,TASKAREA:[bx].FSC_pDataArea
        mov     bx,ds               ; bx:si -> transfer buffer
        SaveReg <BX,SI,DX>          ; Save pointer to buffer
        mov     dh,ACCTYPE_RW       ; transfer type
        CALLFAR ProbeBuff           ; if we can't access the user buffer
                                    ;  for the size of cx, the caller will
                                    ;  trap
        RestoreReg <DX,SI,BX>       ; Restore pointer to buffer
        LJC     fsc_107             ; if the data pointer is invalid, return
                                    ;   invalid parameter

; (DS:SI) -> pointer to data area where list is to be put
                                    ; Save the current data buffer pointer
                                    ;  in pPathBuf
        mov     fsc_pPathBuf.Offst,si
        mov     fsc_pPathBuf.Segmt,bx
        mov     ds,bx

        MOV     DI,GSEL GDT_FSC
        MOV     ES,DI               ; ES = GDT_FSC

        mov     di,word ptr ds:[si] ; Get handle of FSC to continue
                                    ; from out of the user data area
                                    ; They must initialize it to zero
                                    ; for the first call.


; fscNext will be called to get the next fsc name

;  The name found will be copied into the user's data buffer

        CALLFAR fscNext

        LJC     fsc_109             ; No more FSCs to examine,
                                    ;  go return No more files

        mov     word ptr ds:[si],di ; Put route name handle in buffer


        lds     SI,ES:[DI].FS_NAME  ; ES:DI -> current FSD name

        sub     dx,4                ; Subtract the size of the route
                                    ;  name from the user buffer length
                                    ;  (we know there is enough room
                                    ;  because we checked it above)

;       (DS:SI) -> Registered FSD name

        CALLFAR StrLen              ; Get the length of the name in cx

        sub     dx,cx               ; Subtract it from the buffer length

        LJC     fsc_108             ; Error on underflow

        les     di,fsc_pPathBuf
        add     di, 4               ; Move the buffer pointer past the
                                    ;  route name handle

                                    ; DS:SI is the source
                                    ; ES:DI is the destination
        CALLFAR FStrCpy

        CLC                         ; Clear possible carry from FStrCpy

        mov     bx,fsc_StackFrame   ; SS:BX -> user arguments on stack
                                    ; Set the returned data length
        mov     cx,TASKAREA:[bx].FSC_cbDataLength
        sub     cx,dx               ; Should never be negative or we would
                                    ;  have exited the loop above with
                                    ;  ERROR_INSUFFICIENT_BUFFER
        mov     dx,cx
        les     di,TASKAREA:[bx].FSC_pDataLenOut
        mov     bx,es               ; bx:si -> transfer buffer
        mov     dh,ACCTYPE_RW       ; transfer type
        mov     cx,2                ; need room for a word
        CALLFAR ProbeBuff           ; If buffer invalid cause user process
                                    ;  to die
        LJC     fsc_107             ; if the pointer is invalid, return
                                    ;   invalid parameter
        mov     es:[di],dx          ; put length of data returned in
                                    ; FSC_pDataLenOut
        xor     ax,ax


;### PTR B720732 fsc_100:mov    SS:[ptda_pOpenBuf],0; Clear ptda_pOpenBuf
;### PTR B720732        mov     SS:[ptda_pCDS],0    ; Clear ptda_pCDS
fsc_100:LeaveProc
        ret

fsc_102:mov     ax,ERROR_INVALID_LEVEL
        jmp     short fsc_106

fsc_104:mov     ax,ERROR_INTERRUPT
fsc_106:stc
        jmp     short fsc_100

fsc_107:mov     ax,ERROR_INVALID_PARAMETER
        stc
        jmp     short fsc_100
fsc_108:mov     ax,ERROR_INSUFFICIENT_BUFFER
        jmp     short fsc_100
fsc_109:
        mov     ax, ERROR_NO_MORE_FILES
        jmp     short fsc_100

; if we get here, the request was path or FSD based and the RouteName
; pointer had a selector == zero.  this is treated as a
; null selector by getusrbufnz.  cause the user process to die.

fsc_120: xor    bx,bx
        mov     si,bx               ; bx:si -> transfer buffer
        mov     cx,1                ; transfer length
        mov     dh,ACCTYPE_RW       ; transfer type
        CALLFAR ProbeBuff           ; cause user process to die
        jmp     short fsc_100

EndProc  w_FSCtl

;***    handle_route - set up for call to FSD for file handle based FSCtl
;
;   FUNCTION: this interface will take a file handle as input, and turn
;             it into a locked SFT entry by calling SFFromHandleLock.
;             THE SFT WILL BE LOCKED.  IT IS THE CALLER'S RESPONSIBILITY TO
;             UNLOCK THE SFT.
;
;   INPUT:    CX     ===   file handle
;             SS:BX  -->   stack frame (see FileSysCtlStruc)
;
;   EXIT:     NC - success
;                  ES:DI -> FSC
;                  DS:SI -> SFT (locked)
;             CY - error
;                  AX = ERROR CODE
;
;   CALLS:    SFFromHandle
;
;   USES: AX, ES, SI, DI, FLAGS
;
;   ALGORITHM:
;
;   if RouteName != NULL, then
;      return error
;   endif
;   call SFFromHandleLock
;   if CY, then
;        return error
;
        ASSUME DS:NOTHING, ES:NOTHING
Procedure handle_route,NEAR,local
;       PMONLY

        test TASKAREA:[bx].FSC_szRouteName.Segmt,-1
        jnz  hr_50
        test TASKAREA:[bx].FSC_szRouteName.Offst,-1
        jnz  hr_50                     ; error, RouteName present

        SaveReg <bx>
        mov  bx,cx                     ; BX = file handle

        CALLFAR SFFromHandleLock        ; ES:DI -> SFT (locked)

        RestoreReg <bx>
        jc   hr_52                     ; CY = bad handle

        SaveReg <es>
        mov  si,di
        les  di,es:[si].sf_FSC         ; ES:DI -> FSC
        RestoreReg <ds>        ; DS:SI -> SFT

hr_10:  ret

hr_50:  mov  ax,ERROR_INVALID_PARAMETER
        stc
        ret

hr_52:  mov  ax,ERROR_INVALID_HANDLE
        ret

EndProc  handle_route

;***    path_route - set up for call to FSD for path name based FSCtl
;
;   FUNCTION: this interface will take a path name as input, and fully
;             expand it, as well as getting an FSC pointer; all from
;             TransPath.  It will then return the FSC pointer to the
;             main worker.
;
;   INPUT:    CX     ===   -1, else error
;             DS:SI  ===   path name (RouteName)
;             SS:BX  -->   stack frame (see FileSysCtlStruc)
;
;   EXIT:     NC - success
;               ES:DI -> FSC
;               [ThisCDS] -> CDS
;               [WFP_Start] -> beginning of buffer (original ES:DI)
;               [Curr_Dir_End] -> end of curdir in path
;               CX = offset of gettempbuffer
;             CY - error
;                  AX = ERROR CODE
;
;   CALLS:    TransPath, FStrCpy
;
;   USES: AX, CX, DX, ES, DI, DS, SI, FLAGS
;
;   ALGORITHM:
;
;   if CX != -1, then
;        return error
;   save temp buffer @ on stack
;   call transpath
;   if CY, then
;        return error
;   ES:DI = FSC returned from TransPath
;
        ASSUME DS:NOTHING, ES:NOTHING
Procedure path_route,NEAR
;       PMONLY

         stc
         inc  cx
         jnz  pr_50                    ; file handle != -1, error

         SaveReg <bx>
         mov    di,TASKAREA:CurrTCB                                        ;### PTR B720732
         mov    di,TASKAREA:[di].TCBpOpenBuf ; DI -> buffer to temp buffer ;### PTR B720732
;### PTR B720732         mov    di,TASKAREA:[ptda_pOpenBuf] ; DI -> buffer to temp buffer
;
; We have been given a path. If it is "d:", then we need to append
; a "\" to it otherwise TransPath will barf.
;
         cmp    byte ptr ds:[si+1],":"
         jnz    pr_2                  ; Not a "d:", so continue.
         cmp    byte ptr ds:[si+2],0
         jnz    pr_2                  ; Not just a "d:". Continue.
         mov    word ptr [si+2],005Ch   ; "d:" -> "d:\" so transpath accepts it

pr_2:    SaveReg <bp>

         CALLFAR TransPath             ; expand path/return FSC
                                       ; TCBThisFSC = FSC
                                       ; ThisCDS -> CDS
         RestoreReg <bp>
         jc   pr_54                    ; bad path

         TaskContext es
         mov  di,CurrTCB               ; ES:DI -> TCB
         les  di,es:[di].TCBThisFSC    ; ES:DI -> FSC

pr_10:   RestoreReg <bx>
         ret

pr_50:   mov  ax,ERROR_INVALID_PARAMETER
         ret                           ; don't need to do the LeaveProc here

pr_54:   mov  ax,ERROR_INVALID_PATH
         jmp  SHORT pr_10

EndProc  path_route


;***    fsd_route - set up for call to FSD for FSD name based FSCtl
;
;   FUNCTION: this interface will take an FSD name as input.  With one
;             swift call to findFSC, we can get the FSC pointer that
;             corresponds to the FSD.  All we need do, is return it to
;             the main worker.
;
;   INPUT:    CX     ===   handle (MUST BE -1)
;             DS:SI  ===   FSD name (RouteName)
;             SS:BX  -->   stack frame (see FileSysCtlStruc)
;
;   EXIT:     NC - success
;                  ES:DI -> FSC
;             CY - error
;                  AX = ERROR CODE
;
;   CALLS:    findFSC
;
;   USES: AX, CX, DI, ES, FLAGS
;
;   ALGORITHM:
;
;   if CX != -1, then
;        return error
;   call fscFind
;   if CY, then
;        return error
;
        ASSUME DS:NOTHING, ES:NOTHING
Procedure fsd_route,NEAR
;       PMONLY

        stc
        inc  cx
        jnz  fr_52                     ; file handle != -1, error

        CALLFAR fscFind        ; ES:DI -> FSC
        jc   fr_50                     ; CY = bad FSD name

fr_10:  ret

fr_50:  mov  ax,ERROR_INVALID_FSD_NAME
        ret
fr_52:  mov  ax,ERROR_INVALID_PARAMETER
        ret                            ; don't need to do the LeaveProc here

EndProc  fsd_route


;***    call_control_fsd - call the FS_FSCTL entry point
;
;   FUNCTION  This merely pushes all the necessary parameters for a
;             call to FS_FSCTL in the appropriate FSD.
;
;   INPUT     SS:BX -> stack frame parms
;             DS:SI -> ArgDat "union"
;             ES:DI -> FSC
;             DX    -> temp buffer if Path Route (preserved)
;
;   EXIT:     CX = 0
;             NC if successful
;             CY if error
;                AX = error code if error
;
;   CALLS     FS_FSCTL
;
;   USES  AX, BX, CX, ES
;
        ASSUME DS:NOTHING, ES:NOTHING, SS:TASKAREA
Procedure call_control_fsd,NEAR
;       PMONLY

        SaveReg <dx>                             ; preserve possible buf offset

        push ds
        push si                          ; ArgDat union

        push TASKAREA:[bx].FSC_iRouteMethod      ; ArgType

        push TASKAREA:[bx].FSC_iFunction         ; Function

        push TASKAREA:[bx].FSC_pParmList.Segmt
        push TASKAREA:[bx].FSC_pParmList.Offst   ; pParmList

        push TASKAREA:[bx].FSC_cbParmLength      ; lenParm

        push TASKAREA:[bx].FSC_pParmLenOut.Segmt ;
        push TASKAREA:[bx].FSC_pParmLenOut.Offst ; pParmOut

        push TASKAREA:[bx].FSC_pDataArea.Segmt
        push TASKAREA:[bx].FSC_pDataArea.Offst   ; pDataArea

        push TASKAREA:[bx].FSC_cbDataLength      ; lenData

        push TASKAREA:[bx].FSC_pDataLenOut.Segmt ;
        push TASKAREA:[bx].FSC_pDataLenOut.Offst ; pDataLenOut

; FSD can trash AX, BX, CX, DX, ES

        call ES:[DI].FS_FSCTL                    ; call the FSD

        xor  cx,cx
        cmp  cx,ax                               ; CY if AX != 0

        RestoreReg <dx>                  ; restore possible buf offset

        ret

EndProc call_control_fsd


;***    FSD_FSCtl - Extendable File System Control Router
;
;   FSD_FSCtl will route the DOSFSCTL request to the appropriate FSD
;   (FAT, LOCAL, REMOTE) using the FSC pointer passed in.
;
;   INPUT:    ES:DI -> FSC
;             SS:BX -> stack frame parms
;             AX = 1 if handle based
;                       DS:SI -> SFT
;                  2 if pathname based
;                       DS:SI -> path
;                       CX  = iCurr_Dir_End to pass to FSD
;                  3 if FSDname based
;
;   EXIT:     NC -
;                  AX = 0 (success)
;             CY -
;                  AX = error code
;
;   CALLS:    DOS_FSCtl, call_control_fsd
;
;   USES: AX, BX, CX, SI, DI, ES, DS, FLAGS
;
;   ALGORITHM:
;
;   if handle based
;     if FSD
;       set up "union" for FSD call
;     if local(FAT)
;       call DOS_FSCtl
;       return
;   if pathname based
;     if FSD
;       set up "union" for FSD call
;       call call_control_fsd
;     if local(FAT)
;       call DOS_FSCtl
;       return
;   if handle based
;     if FSD
;       set up "union" for FSD call
;     if local(FAT)
;       call DOS_FSCtl
;       return
;   call call_control_fsd
;
        ASSUME DS:NOTHING, ES:NOTHING, SS:TASKAREA
Procedure FSD_FSCtl,NEAR
LocalVar        ffsc_StackFrame,WORD
LocalVar        ffsc_ArgDat,<size argdat_union>     ; union of values to pass to FSD

;       PMONLY
        EnterProc <bx>                  ; $TJMP trashes AX and BX

        dec     ax                      ; AX = zero-based route method
        CALLFAR $TJMP                   ; Jump through table (by what is in AL)
        db      MAXFSCTLROUTETYPE-1     ; maximum route type allowed
        dw      NMPIPESWAPCODE:ffsc63   ; Error case
        dw      NMPIPESWAPCODE:ffsc10   ; Handle route
        .errnz  FSCTL_HANDLEROUTE-0-1
        dw      NMPIPESWAPCODE:ffsc20   ; Path route
        .errnz  FSCTL_PATHROUTE-1-1
        dw      NMPIPESWAPCODE:ffsc30   ; FSD route
        .errnz  FSCTL_FSDROUTE-2-1

;*************** HANDLE BASED FSCTL ***************************************

; We are here because we are doing a handle based FSCtl.
; Set up the parameters on the stack for the call to the file system and
; then call the router.
;
; Load SFT stuff into "union" structure for passing to the FSD

ffsc10: lea     bx,ds:[si].sf_fsi
        mov     ffsc_ArgDat.au_pfsi.Offst,bx
        mov     ffsc_ArgDat.au_pfsi.Segmt,ds    ; SFT independent portion

        lea     bx,ds:[si].sf_fsd
        mov     ffsc_ArgDat.au_pfsd.Offst,bx
        mov     ffsc_ArgDat.au_pfsd.Segmt,ds    ; SFT dependent portion
        jmp     short ffsc50                    ; Route the call to the file system


;*************** PATHNAME BASED FSCTL *************************************
;
; [TCBThisCDS] -> CDS
; DS:SI -> path
; DS:CX -> end of curdir in path
; [TCBThishVPB] = hVPB
; ES:DI -> FSC
;
; Load CDS stuff into "union" structure for passing to the FSD
;
ffsc20: mov     ffsc_ArgDat.au_ppath.Offst,si
        mov     ffsc_ArgDat.au_ppath.Segmt,ds
        mov     ffsc_ArgDat.au_icde,cx          ; iCurDirEnd

        mov     si,TASKAREA:[CurrTCB]
        mov     ax,TASKAREA:[si].TCBThisCDS.Segmt ; could be -1
        mov     si,TASKAREA:[si].TCBThisCDS.Offst ; ax:SI -> CDS in temp buffer

        lea     bx,[si].cd_fsi
        mov     ffsc_ArgDat.au_pfsi.Offst,bx
        mov     ffsc_ArgDat.au_pfsi.Segmt,ax    ; CDS independent portion

        lea     bx,[si].cd_fsd
        mov     ffsc_ArgDat.au_pfsd.Offst,bx
        mov     ffsc_ArgDat.au_pfsd.Segmt,ax    ; CDS dependent portion

        jmp     SHORT ffsc50                    ; call the file system


;**************** FSDNAME BASED FSCTL *************************************
;
; We are here because we are doing an FSDname based FSCtl.
; Set up the parameters on the stack to call the file system and then
; call the router.
;
ffsc30: xor     si,si
        mov     ds,si                           ; DS:SI -> NULL
        jmp     SHORT ffsc60                    ; Call the file system


;** Route the call to the correct file system.
;   ES:DI -> FSC
;
ffsc50: lea     si,ffsc_ArgDat                  ; DS:SI -> argdata "union"
        TaskContext ds,,noassume

;
; DS:SI -> argdata union
;
ffsc60: mov     bx,ffsc_StackFrame              ; SS:BX -> user arguments
        cmp     di,FSC_LOCAL                    ; what file system?
        jb      ffsc65                          ; Call FSD.
;
; Fall through here to call FAT FS.
;
        call    DOS_FSCtl                       ; call the FAT routine
        jmp     short ffsc70                    ; Return to caller

ffsc63: mov     ax,ERROR_NOT_SUPPORTED
        STC
        jmp     short ffsc70                    ; Return error to caller

ffsc65: call    call_control_fsd                ; Call FSD
ffsc70: LeaveProc                               ; Return to caller
        ret

EndProc FSD_FSCtl


;***    DOS_FSCtl - Extendable File System Control FAT FileSystem Support
;
;   DOS_FSCtl will return ERROR_NOT_SUPPORTED to the caller.
;
;   INPUT:    SS:BX -> stack frame parms
;
;   EXIT:     NC - no error
;             CY - error
;                  AX = ERROR_NOT_SUPPORTED
;
;   CALLS:    none
;
;   USES:     AX, BX, DI, DS, ES, FLAGS
;
;   NOTE: we fill in the lenDataInOUt and LenParmInOut to proper values
;       regardless of ERROR_BUFFER_OVERFLOW error.
;
        ASSUME DS:NOTHING, ES:NOTHING, SS:TASKAREA
Procedure DOS_FSCtl,HYBRID
;        PMONLY

        cmp     TASKAREA:[bx].FSC_iFunction,FSCTL_MAXEASIZE ; asking for max ea size?
        mov     ax,ERROR_NOT_SUPPORTED
        stc
        jne     dfc20

; return max EA size and EA list size

        cmp     TASKAREA:[bx].FSC_cbDataLength,size EASizeBuf   ; data buffer large enough?
        mov     ax,ERROR_BUFFER_OVERFLOW
        jb      dfc05                                           ; yes, return error (carry set)
        push    bx
        mov     di,TASKAREA:[bx].FSC_pDataArea.Offst
        mov     bx,TASKAREA:[bx].FSC_pDataArea.Segmt    ; (BX:DI) -> user data area
        mov     ax,MAXEASIZE
.errnz easb_MaxEaSize
        push    bx
        CALLFAR SuWord
        pop     bx
        jc      dfc40           ; error, go return

CpuMode 386
        mov     eax,MAXEALISTSIZE
CpuMode reset

.errnz (easb_MaxEaListSize - 2)
        CALLFAR SuDWord
        pop     bx              ; get back ptr to fsctl argframe
        jc      dfc20
; initialise the DataLenInOut and ParamLenInOut properly

dfc05:  pushf                   ; save flags and error code
        push    ax              ; we may need later

        push    bx              ; AND Save back the argframe ptr again

        mov     di,TASKAREA:[bx].FSC_pDataLenOut.Offst
        mov     bx,TASKAREA:[bx].FSC_pDataLenOut.Segmt
CpuMode 386
        mov     eax,size EASizeBuf      ; fill in the size of data
CpuMode reset
        CALLFAR SuDWord
        jc      dfc30
        pop     bx              ; get back ptr to fsctl argframe
        push    bx              ; and save it again
        mov     di,TASKAREA:[bx].FSC_pParmLenOut.Offst
        mov     bx,TASKAREA:[bx].FSC_pParmLenOut.Segmt
CpuMode 386
        xor     eax,eax         ; fill in size of parm out (i.e. 0)
CpuMode reset
        CALLFAR SuDWord
        jc      dfc30
        pop     bx
        pop     ax
        popf
dfc20:  ret

dfc30:  pop     bx              ; restore argframe - but we don't need it
        pop     bx              ; we don't need previous error code also
dfc40:  pop     bx              ; we don't need previous flags either
        jmp     short dfc20


EndProc DOS_FSCtl

NMPIPESWAPCODE     ENDS

END
