;       SCCSID = @(#)util.asm   6.11 92/02/20
SCCSID  equ     "@(#)util.asm   6.11 92/02/20"
TITLE   UTIL - Handle utilities
NAME    UTIL

;**     util.asm - Handle Related Utility Subroutines
;
;       bugbug 4-feb-88 peterwu - give this file a better name
;       We should rename this file to show it's related to file handles and
;       SFT.
;       1/6/92  b731094 SFB clear pbvdbfil in sft
;
;       Global          Local
;
;       pJFNFromHandle
;       SFFromHandle
;       SFFromHandleLock
;       SFFromSFN
;       SFFromSFNNoRefChk
;       SFWaitIdle
;       SFLock
;       SFUnlock
;       JFNFindFree
;       ScanJFN
;       CheckSFTRef
;       SFLinkFree
;       SFNAlloc
;       GetSFT
;       GrowSFT
;       DoGrowSFT
;                       GrowSFTLastSeg
;       InitSFE
;       doshlp_RedirDev
;                       dhrd_GetSFT
;       SFTInit
;
;
;       Structure of SFT
;
;       Large model array of SFT entries:
;         GDT_SFT:0 = gdt selector to first SFT segment, which contains
;               entries 0 to SFNPERSEG-1
;         GDT_SFT:2 = gdt selector to second SFT segment, which contains
;               entries SFNPERSEG to 2*SFNPERSEG-1
;            ...
;         GDT_SFT:[CALLSFN] = total number of SFT entries
;
;         All SFT segments except the last one contains SFNPERSEG sft
;         entries. CALLSFN contains the total (free + used) number of sft
;         entries in all the SFT segments. CALLSFN <= MAXSFN.
;
;       Structure of Free list:
;           The start of the free list is SFT_FreeHead. The subsequent free
;           sfts can be found by chasing down the sf_chain field. The last
;           free sft contains 0 in sf_chain.
;
; ***** 62773 25 Feb 93 kam Check SFT reference count on JFN in real mode
;Defect 105267 11/17/94 ChangeTeam - Roll defect 89305 into r207 sparse tree.
;                                    Added code to SFUnlock to free SFT for
;                                    unnamed pipe after all opens are closed
;                                    and all threads have unlocked.
;Defect 72417  11/27/95 MLM        - mark JFN entry as busy until
;                                    the SFN is filled in.  Since
;                                    we no longer serialize opens
;                                    we must prevent a JFN from being
;                                    allocated twice.
;Defect 150371 04/01/96 DRB        - System Anchor Block support

.xlist
include dosseg.inc      ; standard segment definitions
.xcref
include basemaca.inc
include osmaca.inc
include fnofs.inc
include oldmaca.inc
include mi.inc
include dossymc.inc
include defdbug.inc
include sft.inc
include error.inc
include tcbdata.inc
include vmowner.inc
include protmode.inc
include seldesc.inc
include virtsym.inc
include devhdr.inc
include proc.inc
include fsc.inc
include shutdown.inc
include secure2.inc                                                     ;@SEC
.cref
.list
.sall

        ModName , FNO_UTIL_ASM

        ReferGlobal     SFT_FreeHead,DWORD      ; head of SFT free list
        ReferGlobal     ProtOnlySystem,BYTE     ; !0 if prot-only system

        ReferTask       ProcType,BYTE
        ReferTask       CurrentPDB,WORD         ; current PDB segment
        ReferTask       JFN_Length,WORD         ; Length of JFN table
        ReferTask       JFN_pTable,DWORD        ; pointer to JFN table
        ReferTask       JFN_Flg_Ptr,DWORD       ; pointer to per file
                                                ;       descriptor flags
        ReferTask       JFN_Max,WORD            ; current max. JFN no
        ReferTask       CurrTCB,WORD            ; Current thread control block

        ReferGDT        GDT_SFT,WORD

; BUGBUG philipl - reference danno BUGBUG from 25-Nov-88 - 09-Sep-1989
;       extrn           p3xJFN:near
;       EXTRNFAR        RMDevCheck
;       ReferGDT        GDT_3xBoxPTDA,WORD
;       extrn           Scan3xJFN:NEAR

        extrn           ShutdownCheck:near


        EXTRNFAR        SegAlloc
        EXTRNFAR        SegRealloc
        EXTRNFAR        SegFree
        EXTRNFAR        ProcBlock
        EXTRNFAR        ProcRun
        EXTRNFAR        GDTFree
        EXTRNFAR        SegMapVirt
;;ifdef VMSTRICT
;;      EXTRNFAR        SysVirtRealloc
;;endif
        EXTRN32         _SELConvertToLaddr

DATA    segment

        PUBLIC  _SFNAllocCtr, SFNAllocCtr
_SFNAllocCtr    label   DWORD   ; D150371
SFNAllocCtr     dd      0

        public  _SFT_GrowSem, SFT_GrowSem
_SFT_GrowSem    label   BYTE    ; D150371
SFT_GrowSem     db      0       ; Semaphore for GrowSFT

SFT_LOCK        EQU     1       ; 1 = locked
SFT_WANT        EQU     2       ; 2 = want

DATA    ends

        FARCODE HIGH2
HIGH2CODE SEGMENT
    ASSUME  SS:TASKAREA


BREAK   <pJFNFromHandle - return pointer to JFN table entry>

;**     pJFNFromHandle - produce JFN pointer from handle number
;
;       pJFNFromHandle takes a handle number and returns a pointer to the
;       handle table entry (JFN) for that handle number.  Handles are
;       per-task; as per convention, pJFNFromHandle operates on the current
;       task's handle space.  As a side-effect, a copy of the per-handle
;       flags is copied to the TCB.
;
;       ENTRY   (BX) = handle number
;       EXIT    'C' clear if OK
;                 (ES:DI) = address of handle table entry (JFN)
;                 (AX) preserved
;               'C' set if error
;                 (AX) = error code
;       USES    AX, ES, DI, Flags
;
;       ALGORITHM
;
;       if (handle > JFN_Length)
;         return ERROR_INVALID_HANDLE
;       copy JFN_Flg_Ptr[handle] to TCBJfnFlag
;       es:di = &JFN_pTable[handle]

Procedure   pJFNFromHandle,<HYBRID,FAR16>               ;@SEC
    ASSUME      DS:NOTHING,ES:NOTHING,SS:TASKAREA

        cmp     bx,[JFN_Length]         ; is handle greater than allocated?
        jae     pjfn1                   ; yes, go return error

        push    ax
        push    bx
        les     di,[JFN_Flg_Ptr]        ; (es:di) -> JFN_Flags vector
        mov     al,es:[di+bx]           ; (al) = Flags for JFN
        mov     bx,[CurrTCB]
        mov     TASKAREA:[bx].TCBJfnFlag,al ; copy JFN flags into TCB
        pop     bx
        pop     ax
        les     di,[JFN_pTable]         ; (es:di) -> JFN vector
        add     di,bx                   ; adjust for word table
        add     di,bx                   ; (es:di) -> JFN
;;      clc                             ; carry is already clear from above
        ret


pjfn1:  DBPRT   40h,1,<Illegal JFN $x\n>,<BX>
        MOV     AX,ERROR_INVALID_HANDLE ; appropriate error
        STC                             ; signal error
        return                          ; go back

EndProc pJFNFromHandle

BREAK <SFFromHandle - return pointer (or error) to SF entry from handle>

;**     SFFromHandle - Given a handle, get JFN and then index into SF table
;
;       ENTRY   BX has handle
;
;       EXIT    'C'
;                 'Z'
;                   ax has error code
;                   di = preserved
;                 'NZ'
;                   es:di -> SFT, which has no VPB, because of BeginFormat error
;               'NC'
;                   es:di -> SFT entry
;                   ax = preserved
;
;       USES    ax,di,es,flags
;
;       ALGORITHM
;
;       call pJFNFromHandle to get pointer to handle
;       if (handle contains SFN_NULL)
;         return ERROR_INVALID_HANDLE
;       call SFFromSFN
;       if (error)
;         return ERROR_INVALID_HANDLE
;       return pointer to SFT entry

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

        call    pJFNFromHandle          ; get jfn pointer
        jc      sffh20                  ; bad handle, go report error
        cmp     word ptr es:[di],SFN_NULL ; unused handle
        jz      sffh10                  ; yes, bad handle, go complain
        SaveReg <BX>                    ; save handle
        mov     bx,ES:[DI]              ; get SFN
        call    SFFromSFN               ; get real SF spot
        RestoreReg <BX>         ; restore handle
        jc      sffh30                  ; error, go complain
        ret                             ; return success

sffh10: mov     ax,ERROR_INVALID_HANDLE ; appropriate error

sffh20: cmp     ax,ax                   ; set 'Z' flag
        stc
        ret                             ; report error

sffh30: mov     ax,ERROR_INVALID_HANDLE ; appropriate error
        ret

EndProc SFFromHandle

;***    SFFromHandleLock -- return locked SFT given a handle
;
;       This procedure returns a pointer to the SFT associated with a
;       handle.  The SFT will be locked.  This routine is provided to give
;       a non-preemptable fetch-lock operation for SFTs, and should be
;       used from preemptable code.
;
;       ENTRY:  bx = handle
;
;       EXIT:   NC: es:di -> sft (locked)
;               CY: ax = error code
;
;       USES:   ax,bx,dx,di,es,flags
;
Procedure SFFromHandleLock,HYBRID
    ASSUME      DS:NOTHING,ES:NOTHING,SS:TASKAREA

        call    SFFromHandle
        jc      sfhlx
        call    SFLock
        jc      sfhl10
sfhlx:  ret

sfhl10: call    SFUnlock
        stc
        mov     ax,ERROR_INVALID_HANDLE
        jmp     short sfhlx

EndProc SFFromHandleLock


;****   SFFromSFN - return pointer to SFT entry given SFN
;        (returns error even when sf_ref_count is zero, i.e. nobody is\
;               using this SFT or it is not yet initialised fully)
;
;****   SFFromSFNNoRefChk - return pointer to SFT entry given SFN
;         (SFt FROM SFN NO Ref-count Check )
;         ( sf_ref_count does not matter; used by InitSFE and SFLinkFree)
;         ( used in those places where we need the SFT ptr if there is one;
;               we don't care whether it is used, or initialised)
;**
;       ENTRY   bx = sfn
;       EXIT    if 'NC'
;                 es:di -> sft entry  (es = gdt selector)
;               if 'C'
;                 if 'Z'
;                   error (invalid handle)
;                   or sf_ref_count == 0 (for SFFromSFN only)
;                 else (NZ)
;                   es:di -> sft entry
;                   BeginFormat failed (see devioctl.asm/wdi_BeginFormat)
;                   (BeginFormat error: sft not attached to a drive)
;                   w_Close should be the only user of this condition
;
;       USES    di,es,flags
;
;       ALGORITHM
;
;       if (SFN >= CALLSFN)
;         error exit
;       selector = GDT_SFT:[SFN / SFNPERSEG]
;       offset = (SFN mod SFNPERSEG) * size sf_entry
;       return selector:offset

    assume      ds:nothing,es:nothing,ss:TASKAREA

Procedure SFFromSFN, <near,faronly, far16>
        jmp     short sfs05
EndProc SFFromSFN

Procedure SFFromSFNNoRefChk,<near,faronly, far16>
        push    cx
        mov     cx,1
        jmp     short sfs10             ;

sfs05:
;Entry   SFFromSFN,hybrid
        push    cx
        xor     cx,cx           ; flag- ref count == 0 also gives invalid_handle

; cx = non zero (1) if we have to return error for those sfts
; with ref_count == 0 i.e. sfts that are not referred to by anybody
; or those sfts that are not yet fully initialised

sfs10:
        push    ax
        push    dx

        mov     di,GSEL GDT_SFT
        mov     es,di           ; es = GDT_SFT
        cmp     bx,es:CALLSFN
        jae     sfs20           ; invalid SFN, error exit
    assume es:nothing

; Note: we made SFNPERSEG equal to 512 so we can optimize this procedure to
; do shifts instead of division.
; Note: Changed to division in order to support new field file
;       handle lockID's
;        .errnz  (SFNPERSEG - 512)

;*******************************************************************
; OLD SHIFT-ORIENTED CODE:
;
; locate the SFT segment selector
;         mov     di,bx           ; di = SFN
; do not combine the shr and shl into "shr di,8" because
; we want bit 0 of the result to be 0.
;        shr     di,9            ; di = sfn / SFNPERSEG
;        shl     di,1            ; di = [sfn / SFNPERSEG] * (size word)
;        mov     es,es:[di]      ; es = SFT segment selector
;
;; calculate offset
;; bx = SFN
;; es = SFT segment selector
;
;        mov     di,bx           ; di = SFN
;        and     di,SFNPERSEG-1  ; di = SFN mod SFNPERSEG
;        imul    di,di,size sf_entry ; di = (SFN mod SFNPERSEG) * size sf_entry
;        add     di,size sft     ; Shutdown flags in SFT struc at begin of seg
;                                ; clears possible carry set by imul.
;*******************************************************************
; NEW DIVISION-ORIENTED CODE:
;
; locate the SFT segment selector
        push    bx              ; save SFN
        mov     ax,bx           ; ax = SFN
        xor     dx,dx           ; dx = 0
        mov     bx,SFNPERSEG
        div     bx              ; ax = SFN / SFNPERSEG ; dx = SFN mod SFNPERSEG
        pop     bx
        shl     ax,1            ; ax = [SFN / SFNPERSEG] * (size word)
        mov     di,ax           ; di = ax
        mov     es,es:[di]      ; es = SFT segment selector

; calculate offset
; dx = (SFN mod SFNPERSEG) from previous div op
; es = SFT segment selector

        imul    di,dx,size sf_entry ; di = (SFN mod SFNPERSEG) * size sf_entry
        add     di,size sft     ; Shutdown flags in SFT struc at begin of seg
                                ; clears possible carry set by imul.
;*******************************************************************

; check to see if beginformat failed.  If so, we must return 'C' & 'NZ'

        test    es:[di].sf_flags2,SF_BEGINFORMAT_FAILED
        jnz     sfs30                       ; failed, return 'NZ' & 'C'
        or      cx, es:[di].sf_ref_count        ; ZR set if ref_count == 0
                                                ; and entry is SFFromSFN
        jz      sfs30
        pop     dx
        pop     ax
        pop     cx
        ret

sfs20:  cmp     ax,ax                       ; sets 'Z' (bad handle)

sfs30:  stc
        pop     dx
        pop     ax
        pop     cx
        ret
EndProc SFFromSFNNoRefChk


;***    SFWaitIdle - Wait for all operations on SFT entry to clear.
;
;       SFWaitIdle loops doing a block while there are still any other
;       threads holding the sf_entry or waiting on it.
;       (Called by w_Close for last close processing case, in which
;        close needs to wait till it's the only operation.)
;
;       Pipes don't need and can't have serialization at the SFT
;       level because the pipe close has to get to the ClosePipe
;       code to wake up a blocked read or write that have no
;       more writers or readers.  Pipes have their own read/write
;       serialization and read/write operation in progress code.
;
;       WARNING Caller must NOT have the SFT locked.
;               Caller's process must be only process with a reference
;               to the SFT, or we may starve.
;               Don't call this procedure from the 3xBox.
;
;       ENTRY   (ES:DI) -> SFT entry.
;
;       EXIT    The SFT entry is Idle, a "last close" is safe.
;
;       EFFECTS blocks (context switch)
;
;       USES    ax, bx, cx, dx, flags
;
Procedure SFWaitIdle,near
        assume  ds:nothing, es:nothing

        cmp     ProcType,PT_REALMODE            ;#53754
        je      sfwi2                           ; 3xbox, go return

        test    es:[di].sf_flags,SF_PIPE        ; we need no serialization for
        jnz     sfwi2                           ;  pipes.

;       There might be 0 waiters, but one thread actually using the SFT entry,
;       we call SFLock to wait for any such thread to clear.

sfwi1:  call    SFLock                          ; Wait for current op. to clear
;#50116 Don't check for carry here as it may be an sft for a char device
;#50116 and we wish to see if the usercnt is greater than zero so the sft
;#50116 will not vanish for an active thread.
        call    SFUnlock                        ; Release SFT, run waiters

;       At this point, no other thread is holding the SFT entry, so sf_usercnt
;       is the number of threads waiting to use the SFT of a file (or is the
;       number of threads that are currently using the SFT of a char device).

        cmp     es:[di].sf_usercnt,0            ; Any waiters or users?
        jz      sfwi2                           ; None. the SFT entry is idle

;       There are either threads waiting to use the SFT (for a file), or else
;       there are threads using the SFT (for a char device). We block here and
;       wait for them to run.

        or      es:[di].sf_flags,SF_BLOCKED     ; tell owner we're blocked
        push    di
        mov     ax,es
        mov     bx,di                           ; (ax:bx) = block key = sf addr
        mov     di,-1
        mov     cx,di                           ; (di:cx) = never time out
        mov     dh,1                            ; (dh) = NON interruptable
        CALLFAR ProcBlock
        pop     di
        jmp     short sfwi1                     ; Go try again

sfwi2:  clc                                     ; #B725277 Clear cy no error
        ret                                     ; #B725277 return


EndProc   SFWaitIdle


;***    SFLock - Lock SFT entry.
;
;       SFLock seizes the SFT entry for use by this thread.  Since there's
;       only 1 3xbox thread, and 3xbox sf_entries are never shared with pmode,
;       this routine doesn't do anything if called for the 3xBox.
;
;       For non-3xBox case, we either grab the sf_entry, or wait our turn
;       for it.  SFUnlock should be called to release a seized sf_entry.
;       For char devices, sf_usercnt is incremented to keep track of how many
;       active operations are currently occurring on the char device.
;
;       Pipes don't need and can't have serialization at the SFT
;       level because the pipe close has to get to the ClosePipe
;       code to wake up a blocked read or write that have no
;       more writers or readers.  Pipes have their own read/write
;       serialization and read/write operation in progress code.
;
;       ENTRY   (ES:DI) -> SFT entry.
;
;       EXIT    We own the SFT entry.
;               CY -> SFT vanished under us (no more references to this SFT)
;               NC -> SFT is valid
;
;       EFFECTS blocks (context switch)
;
;       USES    ax, bx, dx, flags
;
;       WARNING!!!
;
;           Callers of this routine need to ensure that the SFT is indeed
;           valid when we return. This is because there is a window where
;           the SFT may have been marked clean by FSD_Close, but there are
;           still threads blocked in this routine. When they get the SFT,
;           it may not be valid even though the close operation has not been
;           completed.
;           Callers should check for CY, and call SFUnlock if
;           the CY flag is set. An error should then be returned.
;
;
Procedure SFLock,<near,far16,faronly>
        assume  ds:nothing, es:nothing

ifdef SECURITY
;@SEC   Allow Security to skip SFT lock  on current SFN because it was
;       already locked by the current thread.
        push    bx
        mov     bx, CurrTCB                                               ;@SEC
        xor     ax,ax                                                     ;@SEC
        mov     al, TASKAREA:[bx].TCBSecFlags                             ;@SEC
        test    ax, TCBSEC_NOLOCKSFT                                      ;@SEC
        pop     bx
        jz      sflk5                                                     ;@SEC
        ret                                                               ;@SEC

sflk5:                                                                    ;@SEC
endif

        test    es:[sft_inshutdown],StopSFLocks
        jne     sflk4

Entry   SFLockNoSD,hybrid
        cmp     ProcType,PT_REALMODE
        je      sflk2                           ; 3xbox, go return

        test    es:[di].sf_flags,DEVID_DEVICE+SF_PIPE
        jz      sflk1                           ; Treat files differently
        inc     es:[di].sf_usercnt              ; Increment number of users
        jmp     short sflk2

sflk1:  test    es:[di].sf_flags,SF_INUSE
        jnz     sflk3                           ; busy, go blk, wait our turn

        or      es:[di].sf_flags,SF_INUSE       ; mark SFT entry as ours
sflk2:  cmp     es:[di].sf_ref_count,1          ; are there any references ?
        ret

sflk3:  inc     es:[di].sf_usercnt              ; add ourselves to waiting list
        or      es:[di].sf_flags,SF_BLOCKED     ; tell owner we're blocked

        push    di
        push    cx
        push    bx
        mov     ax,es
        mov     bx,di                           ; (ax:bx) = blk key = sf addr
        mov     di,-1
        mov     cx,di                           ; (di:cx) = never time out
        mov     dh,1                            ; (dh) = NON interruptible
        CALLFAR ProcBlock
        pop     bx
        pop     cx
        pop     di

        dec     es:[di].sf_usercnt               ; be optimistic if we        ;### B731968
        test    es:[sft_inshutdown], StopSFLocks ; are not in shutdown        ;### B731968
        jz      sflk1                            ; go try again               ;### B731968
;       jmp     short sflk1                     ; go try again                ;### B731968

sflk4:  call    ShutdownCheck
        jmp     short SFLockNoSD

EndProc   SFLock


;***    SFUnlock - Unlock SFT entry.
;
;       SFUnlock releases a locked SFT entry.  Any waiting threads will be
;       run. For char devices, the thread that will perform the last operation
;       is run when the value of sf_usercount becomes zero. This last thread
;       will do a final CLOSE on the char device.
;       If called for the 3xBox, this routine simply returns.
;
;       Pipes don't need and can't have serialization at the SFT
;       level because the pipe close has to get to the ClosePipe
;       code to wake up a blocked read or write that have no
;       more writers or readers.  Pipes have their own read/write
;       serialization and read/write operation in progress code.
;
;       ENTRY   (ES:DI) -> SFT entry.
;
;       EXIT    SFT entry up for grabs.
;
;       USES    none (including flags)
;
Procedure SFUnlock,<near,far16,faronly>
        assume  ds:nothing, es:nothing

        pushf
        push    ax
        push    bx

ifdef SECURITY
;@SEC   Allow Security Helpers to skip SFT unlock  on current SFN if it was
;       already locked by the current thread (us)
        mov     bx, CurrTCB                                               ;@SEC
        xor     ax,ax                                                     ;@SEC
        mov     al, TASKAREA:[bx].TCBSecFlags                             ;@SEC
        test    ax, TCBSEC_NOLOCKSFT                                      ;@SEC
        jnz     sfuk2                           ; skip out if set         ;@SEC
endif
        cmp     ProcType,PT_REALMODE
        je      sfuk2                           ; 3xbox, go return

        test    es:[di].sf_flags,DEVID_DEVICE+SF_PIPE
        jz      sfuk0                           ; Treat files differently
        dec     es:[di].sf_usercnt              ; Decrement number of users
        jnz     sfuk2                           ; This is not the last one
; For Unnamed Pipes, do the following.  (We assume if SFT is already    ;89305
; freed, sfi_hVPB structure is set to zero.)                            ;89305
        test    es:[di].sf_flags, SF_PIPE       ; Jump if not pipe      ;89305
        jz      sfuk0                                                   ;89305
        test    es:[di].sf_flags, SF_NMPIPE     ; Jump if named pipe    ;89305
        jnz     sfuk0                                                   ;89305
        cmp     es:[di].sf_ref_count,0          ; Jump if ref not zero  ;89305
        jnz     sfuk0                           ;                       ;89305
        cmp     es:[di].sf_fsi.sfi_hVPB,0       ; Jump if already freed ;89305
        jz      sfuk0                           ;                       ;89305
        mov     es:[di].sf_fsi.sfi_hVPB,0       ;                       ;89305
        call    SFLinkFree                      ; Put SFT on free list  ;89305
        jmp     sfuk2                           ;                       ;89305

;   If this is an SFT for a char device:
;       We were done using the SFT. If there is a thread blocked, waiting
;       for all activity to stop on the SFT, we start it up so that the
;       "final" CLOSE can be done on the char device.
;
;   If this is an SFT for a file:
;       We check to see if there is any thread waiting for the SFT. If there
;       is, we do a ProcRun to start it up.
;
sfuk0:  test    es:[di].sf_flags,SF_BLOCKED
        jz      sfuk1                           ; No one waiting, return

        mov     ax,es
        mov     bx,di                           ; (ax:bx) = block key = sf addr
        CALLFAR ProcRun


sfuk1:  and     es:[di].sf_flags,not (SF_INUSE+SF_BLOCKED)  ; clear semaphore
sfuk2:  pop     bx
        pop     ax
        popf
        ret

EndProc   SFUnlock


BREAK <JFNFindFree - return a JFN pointer if one is free>

;**     JFNFindFree - Find a free JFN entry
;
;       JFNFindFree scans through the current task's JFN (handle) table to
;       find a free entry.  It returns the index of the free entry and
;       the address of the table entry.  Note that the entry is NOT marked
;       in use, it's only located.
;
;       ENTRY   none
;       EXIT    'C' clear if OK
;                 (BX) = new handle code
;                 (ES:DI) = address of JFN slot
;                 (AX) = preserved
;               'C' set if error
;                 (AX) = error_too_many_open_files
;       USES    AX, BX, ES, DI, FLAGS

Procedure   JFNFindFree,hybrid
    ASSUME      DS:NOTHING,ES:NOTHING,SS:TASKAREA
        SaveReg <ax,cx>                 ; preserve caller's cx
        mov     ax,SFN_NULL             ; ax = search value
        call    ScanJFN                 ; bx = handle, es:di -> jfn cell
        RestoreReg <cx,ax>
        jc      jfnf20                  ; not found, go error exit

;### b726426   Pull out this test as it breaks applications
;ifdef   FSSTRICT
;       SaveReg <es,di>                 ; preserve caller's cx
;       les     di,[JFN_Flg_Ptr]        ; pointer to JFN flags
;       cmp     byte ptr es:[di+bx],-1  ; find out if we are in process of
;                                       ; closing
;       je      jfnie1
;
;       RestoreReg <di,es>
;endif
        mov     word ptr es:[di], SFN_BUSY
                                        ;72417
                                        ; mark JFN entry as busy until
                                        ; the SFN is filled in.  Since
                                        ; we no longer serialize opens
                                        ; we must prevent a JFN from being
                                        ; allocated twice.

        cmp     JFN_Max,bx              ; Keep track of the highest JFN used
        jae     jfn10                   ;
        mov     JFN_Max,bx              ;
        clc
jfn10:  ret

jfnf20: mov     ax,ERROR_TOO_MANY_OPEN_FILES
        ret

;jfnie1: InternalError <JFNFindFree: in process of releasing JFN>

EndProc JFNFindFree


;***    ScanJFN - scan JFN table for a given SFN
;
;       ENTRY   ax = SFN to search for
;       EXIT    if carry clear - success
;                 bx = handle number
;                 es:di -> address of handle
;               if carry set - not found
;       USES    ax,bx,cx,di,es,flags

Procedure ScanJFN,hybrid
    assume ds:nothing,es:nothing
        les     di,[JFN_pTable]         ; (es:di) -> JFN table
        mov     cx,[JFN_Length]         ; (cx) = length of JFN table
        mov     bx,cx                   ; (bx) = length of JFN table
        cld                             ; search forward
        repne   scasw                   ; search
        stc                             ; assume no match
        jne     sjfnx                   ; no match, return carry

; found SFN
; es:di -> JFN cell after the one we want
        dec     di
        dec     di                      ; (es:di) -> JFN cell we want
        sub     bx,cx                   ; (bx) = handle+1, CARRY=clear
        dec     bx                      ; (bx) = handle
                                        ; exit with carry cleared
sjfnx:  ret
EndProc ScanJFN


;***    CheckSFTRef - check for references to SFT for process
;
;       This routine determines whether a process has any references to an
;       SFT.  We can't just the reference count because it will only
;       be zero if all processes have removed references to it.  We look
;       through the JFN looking for the SFN.  FCBs aren't inherited and
;       don't have entries in the JFN, so we use their ref count.
;       As an optimization, we have a flag that indicates that the JFN
;       shouldn't be checked because the SFT was just created.
;        (This procedure is called only by MFTClose right now. This is
;       called to find out whether the same handle is being referred to by
;       the process more than once - e.g. dupped handles )
;
;       ENTRY   (ES:DI) -> SFT
;                BX     -> real handle ( -1 indicates no handle to look for)
;
;       EXIT    CY - no references
;               NC - references
;       USES    ax,bx,cx,flags

Procedure CheckSFTRef,NEAR
    assume ds:nothing,es:nothing

        test    es:[di].sf_flags,SF_FCB ; FCB?
        jnz     csr4                    ; yes, go check ref count

        cmp     bx,-1                   ; no handle to check ?
        je      csr5                    ; then return (no references)
        test    es:[di].sf_flags,SF_NOJFN   ; skip JFN check?
        jnz      csr5                    ; yes, go return

;*************** 62773 ********************************************************

        cmp     ProcType,PT_REALMODE            ; 3xbox ?
        je      csr4                            ; if so, check reference count

;*************** 62773 ********************************************************

        SaveReg <es,di>

;;;
;;; BUGBUG - philipl this should be removed 09-Sep-1989
;;; ; real mode;
;;; ; on entry bx = real handle (JFN) which can be found in PDB_JFN table
;;; ; if the handle is duplicated.
;;;         mov     ax,bx                   ;
;;;         SaveReg <ds>
;;;         call    Scan3xJFN               ; CY if no other ref in current proc
;;;         RestoreReg      <ds>
;;;         jmp     short csr2              ;
;;;
; prot mode; look for a dup in JFN table with SFN

csr1:   mov     ax,es:[di].sf_fsi.sfi_selfsfn   ; (AX) = sfn
        call    ScanJFN                 ; any references to SFT?

csr2:   RestoreReg <di,es>
csr3:   ret                             ; carry set appropriately

csr4:   test    es:[di].sf_ref_count,0ffffh ; any more references?
        jnz     csr3                    ; yes, return NC
csr5:   stc                             ; no, return CY
        ret

EndProc CheckSFTRef



;***    SFLinkFree - Put possibly free SFT on the free list, if actually free
;
;       SFLinkFree examines the SFT pointed to by ES:DI to see if it's free,
;       which is indicated by sf_ref_count = 0.  If the SFT is not free,
;       we simply return.  If it is free, we make it the new head of the
;       free list, so that SFNAlloc can find it quickly.
;
;       If the SFT entry is from FCB sft, we simply return.
;
;       ENTRY   ES:DI  -> SFT entry
;
;       EXIT    if SFT.sf_ref_count != 0 -> nothing
;               if SFT.sf_ref_count == 0
;                   SFT on free list, which will destroy sf_chain.
;
;       USES    - none including flags -
;
;       WARNING ** FCB SFTs are now marked by FCB bit set in sf_flags          **
;
;       WARNING ** SFT.sf_chain will be destroyed, therefore, do not call     **
;               ** SFLinkFree until AFTER calling cleanup routines in sharer. **
;
Procedure SFLinkFree,<HYBRID, far16>
    ASSUME  ds:nothing,es:nothing

        pushf

        test    ES:[di].sf_flags,SF_FCB ; FCB SFT?
        jnz     sflf3                   ; yes FCB SFT, go return

ifdef   FSSTRICT
; verify sft pointer by calling SFFromSFN with selfsfn, then compare pointers
        SaveReg <ax,bx,dx>

        mov     ax,es
        mov     dx,di                   ; ax:dx = es:di -> sft
        mov     bx,es:[di].sf_fsi.sfi_selfsfn ; bx = sfn
        call    SFFromSFNNoRefChk               ; es:di -> sft
        jnc     sflf0                 ; 'NC' = ok, go on
        jz      sflfie1                 ; 'Z' = bad SFN, go panic

;   We have a valid SFT pointer, but if 'C' & 'NZ' were returned, the
;   SFT was the result of a BeginFormat IOCtl failure.  Since we are just
;   returning the SFT, we let it continue here

sflf0:  mov     bx,es
        cmp     ax,bx
        jne     sflfie1                 ; go panic if selector not the same
        cmp     dx,di
        jne     sflfie1                 ; go panic if offset not the same

        RestoreReg <dx,bx,ax>
endif

        cmp     ES:[di].sf_ref_count,0  ; is reference count actually 0?
        jnz     sflf3                   ; no, go return having done nothing

ifdef   FSSTRICT
; Check if the the sft entry has been removed from the MFT chain.

        CpuMode 386
        cmp     ES:[di].sf_MFT,SF_MFT_NULL
        CpuMode reset

        jne     sflfie2                 ; go panic if sf_MFT is not zero.
endif

ifdef   FSSTRICT
; make sure sft entry is not already on free list by scanning free list for
; the same sfn
        SaveReg <ax,bx,ds,si>

        mov     bx,es:[di].sf_fsi.sfi_selfsfn ; bx = selfsfn
        DosContext <ds>
        lds     si,[SFT_FreeHead]       ; ds:si -> free list
    assume ds:nothing

sflf1:  mov     ax,ds
        or      ax,si                   ; end of free list?
        jz      sflf2                   ;   yes, go free sft entry

        cmp     ds:[si].sf_fsi.sfi_selfsfn,bx ; is this same as given sfn?
        je      sflfie3                 ;   yes, go panic

        lds     si,ds:[si].sf_chain     ; ds:si -> next sft
        jmp     sflf1

sflf2:  RestoreReg <si,ds,bx,ax>
endif

; Free SFT entry, not from FCB set, so stick it on the free list

        SaveReg <ds,ax>
        DosContext DS,AX

        or      ES:[di].sf_flags2,sff2_isfree   ; mark the SFT as free
        mov     ax,DS:[SFT_FreeHead.Offst]
        mov     ES:[di].sf_chain.Offst,ax
        mov     ax,DS:[SFT_FreeHead.Segmt]
        mov     ES:[di].sf_chain.Segmt,ax       ; SFT on free list
        mov     DS:[SFT_FreeHead.Offst],di
        mov     DS:[SFT_FreeHead.Segmt],es      ; Free list head updated

        RestoreReg <ax,ds>

sflf3:  popf
        ret

IFDEF FSSTRICT
sflfie1:        InternalError <SFLinkFree: bad pointer to SFT entry>
sflfie2:        InternalError <SFLinkFree: sf_MFT non-zero>
sflfie3:        InternalError <SFLinkFree: SFE already in free list>
ENDIF

EndProc   SFLinkFree


;***    SFNAlloc - allocates a SF table entry
;
;       SFNAlloc allocates a free SFT entry.
;
;       ENTRY   NONE
;       EXIT    'C' clear if OK
;                   (BX) = SFN
;                   (ES:DI) = address of SFT
;                   Since SFT was free, sf_ref_count will = 0
;                   sf_fsi.sfi_mode init to 0
;                   sf_flags init to 0
;                   free list updated
;                'C' set if error
;                   (AX) = error code
;       USES    BX, ES, DI, FLAGS
;               also AX, if error
;
;       Change history:
;       ---------------
;       69817  LRN  06/10/93 Must save CX around call to _SELConvertToLaddr

Procedure SFNAlloc,<hybrid, far16>
    ASSUME      DS:NOTHING,ES:NOTHING,SS:TASKAREA

        push    ds
        DosContext DS,BX

sfnf1:  cmp     [SFT_FreeHead.Segmt],0
        LJZ     sfnf2                   ; no free sft entries, go get some more
        les     di,[SFT_FreeHead]       ; (es:di) -> SFT

        mov     bx,es:[di].sf_chain.Offst
        mov     [SFT_FreeHead.Offst],bx
        mov     bx,es:[di].sf_chain.Segmt
        mov     [SFT_FreeHead.Segmt],bx ; update free list pointer

;       Fill in SFT

        push    bp
        mov     bp,CurrTCB
        mov     bx,[bp].TCBUser_ID
        MOV     ES:[DI.sf_fsi.sfi_UID],bx
        mov     bx,[bp].TCBProc_ID
        MOV     ES:[DI.sf_fsi.sfi_PID],bx
        pop     bp
        MOV     bx,CurrentPDB
        MOV     ES:[DI.sf_fsi.sfi_PDB],bx

        CpuMode 386
        mov     es:[di.sf_MFT],SF_MFT_NULL      ; no MFT assigned
        CpuMode reset

        xor     bx,bx                           ; bx = 0; clear carry
        mov     es:[di].sf_fsi.pPVDBFil.LowWord,bx                     ;b731094
        mov     es:[di].sf_fsi.pPVDBFil.HighWord,bx                    ;b731904
        mov     es:[di].sf_fsi.sfi_mode,bx
        mov     es:[di].sf_fsi.sfi_mode2,bx
        mov     es:[di].sf_flags,bx
        mov     es:[di].sf_flags2,bx
        mov     es:[di].sf_usercnt,bx
        mov     es:[di].sf_fsi.sfi_tstamp,bl    ; clear time stamp flags
        mov     es:[di].sf_plock,bx

; Set the sf_devptr to zero so that we can detect if someone tries to do
; a DevIOCtl to the handle.

        mov     es:[di].sf_devptr.Segmt,bx
        mov     es:[di].sf_devptr.Offst,bx

; initialize Lock-ID for protected file-handles
        mov     es:[di].sf_LockID.LowWord, bx
        mov     es:[di].sf_LockID.HighWord, bx

; increment global SFNAllocCtr for Lock-ID

sfnf1a:
        add     [SFNAllocCtr].LowWord, 1
        adc     [SFNAllocCtr].HighWord, bx   ; bx must = 0
        jc      sfnf1a               ; wonder if this jump will ever occur

; install an invalid FSC to cause protection fault if used before properly
; initialized

        mov     es:[di].sf_FSC.Offst,bx         ; Offst = 0
        mov     es:[di].sf_FSC.Segmt,FSC_BAD    ; segment = invalid selector

        mov     bx,es:[di].sf_fsi.sfi_selfsfn   ; bx = sfn
        pop     ds                      ; restore caller's ds

CpuMode 386
        SaveReg <ax>
        SaveReg <ds,si,di,es,bx,dx,cx>          ; 69817 added cx save
        mov     ax,es
        movzx   eax,ax
        FlatContext <ds,es>
        CCALL32  _SELConvertToLaddr,<eax>
        RestoreReg <cx,dx,bx,es,di,si,ds>       ; 69817 added cx restore
IFDEF FSSTRICT
        cmp     eax,-1                          ; error ?
        jne     short sfnf0
        InternalError   <SFNAlloc: invalid sft address?>
ENDIF
sfnf0:
        mov     es:[di].sf_fsd.sfdFAT_pSFT,eax
        mov     es:[di].sf_fsd.sfdFAT_LastFATSec,0
        RestoreReg <ax>
CpuMode reset
        clc

        ret                             ; SUCCESS EXIT

;       If we come here, it means the free list is empty.  First, try to
;       grow the SFT.  If that works, use one of the new entries.  If we
;       can't grow the SFT, we return an error.

sfnf2:  SaveReg <ax,cx,dx,si,ds>        ; Preserve callers regs accross GrowSFT
        mov     ax,SFTINCR              ; ax = size to grow
        call    GrowSFT                 ; CF = 0/1 -> success/failure
        RestoreReg <ds,si,dx,cx,ax>
        LJNC    sfnf1                   ; GrowSFT succeeded, go allocate SFT

;       Grow failed, tell user we can't open any more files.

        MOV     AX,ERROR_TOO_MANY_OPEN_FILES
        pop     ds
        ret                             ; FAILURE EXIT

EndProc SFNAlloc

;***    GetSFT - allocates a SF table entry and initialises
;
;       GetSFT allocates a SFT and initialises some of the fields
;
;       ENTRY   ax = sfi_mode to be filled in
;               TCBThishVPB
;               PathNameType
;
;       EXIT    'C' clear if OK
;                   (BX) = SFN
;                   (ES:DI) = address of SFT
;                   sf_ref_count = 1
;                   sf_flags = No_JFN (allocated yet)_
;                   TCBThisSFT set to this SFT
;                   sfi_hVPB filled in
;                'C' set if error
;                   (AX) = error code
;       USES    AX, BX, ES, DI, FLAGS
;

Procedure GetSFT,HYBRID

    ASSUME DS:NOTHING, ES:NOTHING, SS:TASKAREA
;
; get a free SFT and fill it in.  set up TCBThisSFT
;
        call    SFNAlloc                ; (ES:DI) -> free sft, (BX) = SFN
        jc      gs10                    ; error, go return
        mov     es:[di].sf_ref_count,1  ;
        mov     es:[di].sf_fsi.sfi_mode,ax
        mov     es:[di].sf_flags,SF_NOJFN ; not a device, no JFN alloced
        SaveReg <ds,si>
        TaskContext <ds>,si
        mov     si,CurrTCB
        mov     TASKAREA:[si].TCBThisSFT.Offst,di
        mov     TASKAREA:[si].TCBThisSFT.Segmt,es
        mov     ax,TASKAREA:[si].TCBThishVPB
        mov     es:[di].sf_fsi.sfi_hVPB,ax
        RestoreReg      <si,ds>
gs10:   ret

EndProc GetSFT

;***    GrowSFT - attempt to grow the SFT
;
;       GrowSFT is called when there are no more free SFTs, it attempts to
;       allocate memory to hold some more.  It does not actually allocate the
;       SFT (mark it in use), SFNAlloc does that.
;
;       ENTRY   ax = number of entries to grow
;
;       EXIT    Carry Clear
;                       Grow succeeded or partially succeeded
;               Carry Set
;                       Grow failed
;
;       USES    ax, bx, cx, dx, si, di, ds, es, flags
;
Procedure GrowSFT,hybrid
    ASSUME  ds:nothing,es:nothing

        LocalVar        gsf_Grow,WORD

        PMONLY

        AssertNoBuf
        EnterProc       <ax>
        DosContext ds
        test    SFT_GrowSem,SFT_LOCK    ; Is another thread already growing?
        jz      gsf1                    ; No, we'll do it.

;       Some other thread is doing grow work.  Sleep until that thread is
;       done, then report success.  (Note, we don't care about spurious
;       wakeups, we'll just go loop through the calling code and come right
;       back here.)

gsf0:   or      SFT_GrowSem,SFT_WANT    ; tell world to wake us up
        mov     ax,ds
        mov     bx,offset DOSGROUP:SFT_GrowSem ; (ax:bx) = lock key
        mov     di,-1
        mov     cx,di                   ; (di:cx) = never time out
        mov     dh,1                    ; (dh) = NON interruptable
        CALLFAR ProcBlock
        test    SFT_GrowSem,SFT_LOCK    ; check for spurious wakeups
        jnz     gsf0
;;      clc                             ; test instruction cleared carry
        jmp     gsf10

gsf1:   or      SFT_GrowSem,SFT_LOCK    ; set the semaphore

        mov     ax,gsf_Grow
        call    DoGrowSFT
    assume ds:nothing

gsf8:   pushf
        DosContext <ds>
        test    SFT_GrowSem,SFT_WANT    ; any thread waiting?
        jz      gsf9                    ; no, go exit
        mov     ax,ds
        mov     bx,offset DOSGROUP:SFT_GrowSem ; (ax:bx) = lock key
        CALLFAR ProcRun                 ; wake up waiters
gsf9:   mov     SFT_GrowSem,0           ; clear lock and waiting flags
        popf

gsf10:  LeaveProc
        ret                             ; ** EXIT **
EndProc   GrowSFT


;**     DoGrowSFT - grow SFT to hold more SFT entries
;
;       If we run out of memory after some SFT are added, we'll return
;       carry set but SFT may still be expanded by some amount. In either
;       case CALLSFN will reflect the current size of SFT.
;
;       ENTRY   ax = number of entries to grow
;
;       EXIT    carry clear = success or partially success
;               carry set = failed
;
;       USES    ax,bx,cx,dx,si,di,ds,es,flags
;
;       ALGORITHM
;
;       size remaining = min(request size, MAXSFN - CALLSFN)
;       do {
;         size remaining = grow last SFT segment(size remaining)
;         if (carry)
;           error exit
;       } while (size remaining > 0)

Procedure DoGrowSFT,hybrid
    assume ds:nothing,es:nothing,ss:nothing
        mov     bx,MAXSFN       ; bx = maximum number of SFN allowed
        push    GSEL GDT_SFT
        pop     ds              ; ds = GDT_SFT
        sub     bx,ds:CALLSFN   ; bx = MAXSFN - cAllSFN
        jb      dgsie1          ; more SFT entries than possible, go panic

        stc                     ; assume error
        je      dgsx            ; bx = 0 (can't grow), exit with carry set

; calculate real growth size = min(ax,bx)
; ax = requested growth size
; bx = maximum growth size allowed
        cmp     ax,bx           ; ax is smaller?
        jb      dgs10           ;   YES, minimum is in ax

        mov     ax,bx           ; ax = minimum

; ax = entries left to add

dgs10:  push    ds:CALLSFN      ; [tos] = cAllSFN before growing

; ax = entries remaining to grow
; [tos] = CALLSFN before growth attempt

; ax=entries left = GrowSFTLastSeg(ax=number of entries to grow)
dgs20:  call    GrowSFTLastSeg  ; ax = entries remaining
    assume ds:nothing
        jc      dgs30           ; error, go exit
        or      ax,ax           ; Done?
        jnz     dgs20           ;   NO, loop until done

        pop     ax              ; throw away original CALLSFN
        ret                     ; total success, exit with carry cleared

; error occured, figure out whether we have grown the SFT by some amount or
; not. If we did, then return carry clear; otherwise return carry set.
dgs30:  pop     ax              ; ax = original CALLSFN
        push    GSEL GDT_SFT
        pop     ds
        cmp     ax,ds:CALLSFN   ; set carry if SFT have grown
        cmc                     ; set carry if SFT didn't grow
dgsx:   ret

dgsie1: InternalError <DoGrowSFT: bad CALLSFN>

EndProc DoGrowSFT


;**     GrowSFTLastSeg - expand last SFT segment to specified size
;
;       Grow the last SFT segment to hold AX more sf_entries. If AX is
;       more than can be added to the last SFT segment, then the last SFT
;       segment will be expanded to hold SFNPERSEG entries, and upon
;       exit AX = AX - entries added. If AX <= what can be added to the
;       last segment, then the last SFT segment is expanded to hold AX more
;       new sf_entries, and upon exit AX = 0.
;
;       CALLSFN and free list are also updated if operation is
;       successful.
;
;       Here the last segment is defined as the segment where SFN =
;       CALLSFN will be if it exists (cAllSFN = last valid SFN + 1).
;       Therefore, the last segment may or may not already be
;       allocated.
;
;       ENTRY   ax = number of new entries to add
;
;       EXIT    if carry clear - success
;                 ax = number of new entries left if they didn't all
;                      fit into the last SFT segment
;               if carry set - error
;
;       USES    ax,bx,cx,dx,si,di,ds,es,flags
;
;       ALGORITHM
;
;       if (last SFT segment doesn't exist)
;         allocate last SFT segment
;       if (ax more entries can fit in last segment) {
;         segrealloc last segment to hold ax more entries
;         ax = 0
;       } else {
;         segrealloc last segment to hold SFNPERSEG entries
;         ax = ax - number of entries added
;       }

Procedure GrowSFTLastSeg,near,local
    assume ds:nothing,es:nothing,ss:nothing

        LocalVar gsls_cGrow,WORD        ; #entries left to grow
        LocalVar gsls_cLast,WORD        ; #entries already in last segment
        LocalVar gsls_cLastNew,WORD     ; #entries to be in last segment
        LocalVar gsls_iLastSeg,WORD     ; index to last segment selector

; Determine if the last SFT segment exists.

        EnterProc       <ax>            ; cGrow = entries to add
        or      ax,ax                   ; zero growth request?
        LJZ     gslsx                   ;   yes, exit with carry cleared

        push    GSEL GDT_SFT
        pop     es                      ; es = GDT_SFT
        mov     ax,es:CALLSFN           ; ax = cAllSFN = last valid SFN+1
        xor     dx,dx                   ; dx:ax = dito
        mov     bx,SFNPERSEG            ; bx = SFNPERSEG
        div     bx                      ; ax = CALLSFN / SFNPERSEG
                                        ; dx = CALLSFN mod SFNPERSEG
        add     ax,ax                   ; GDT_SFT:ax -> last segment selector
        mov     gsls_iLastSeg,ax        ; GDT_SFT:iLastSeg -> dito
        mov     gsls_cLast,dx           ; cLast = #entries in last segment
        or      dx,dx                   ; Does last SFT segment exist?
        jnz     gsls10                  ;   Yes, go grow it

; last SFT segment does not exist, create it so we can grow it later

IFDEF FSSTRICT                          ; verify last segment doesn't exist
; GDT_SFT:ax -> last segment selector
        mov     di,ax                   ; es:di -> last segment selector
        test    word ptr es:[di],-1     ; last segment exist?
        jz      gsls5                   ;   no, continue

        InternalError <GrowSFTLastSeg: last seg exist>
gsls5:
ENDIF

; Since SegAlloc won't let us allocate a segment with size 0, we might
; as well allocate one of size sf_entry + the initial shutdown flags word.
; Actually the size here doesn't matter much, all we want is a segment we
; can SegRealloc later.
        mov     bx,(size sf_entry)+(size sft)   ; bx = size sf_entry +
                                                ; size sft. (for shutdown)
        xor     ax,ax                   ; ax:bx = size
        mov     si,HF_ZEROINIT          ; si = hf flags
        mov     cx,(D_DATA0 shl 8) + SA_GDT
        mov     di,FSSFTOWNER
; bx=selector = SegAlloc(ax:bx=size si=hflags cx=flags di=owner)
        CALLFAR SegAlloc
        jc      gslsx                   ; can't allocate, exit w/ carry set

; bx = last segment selector

; save last segment selector
        mov     di,gsls_iLastSeg        ; GDT_SFT:si -> last segment selector

; initialize shutdown flags word
        mov     es,bx                   ; (es) - last segment selector
    assume es:nothing
        mov     es:[sft_inshutdown],NotShutdown  ; set normal status

        push    GSEL GDT_SFT
        pop     es                      ; es:si -> last segment selector slot
    assume es:nothing
        mov     es:[di],bx              ; save last SFT segment selector

; Now we know for sure last SFT segment exist. Find out how large to
; SegRealloc it to.
gsls10: mov     ax,gsls_cLast           ; ax = #entries in last segment
        add     ax,gsls_cGrow           ; ax = #entries to fit in last segment
        cmp     ax,SFNPERSEG            ; would it fit?
        jbe     gsls20                  ;   Yes, go grow it

        mov     ax,SFNPERSEG            ; only this many will fit

; ax = #entries to put in last segment
gsls20: mov     gsls_cLastNew,ax        ; save #entries to put in last segment
        mov     bx,size sf_entry        ; bx = size sf_entry
        mul     bx                      ; dx:ax = new size of last segment - 2
        add     ax,size sft             ; add space for sft structure (used
        adc     dx,0                    ; by shutdown)
; beware dx:ax may be 1:0000h = 64K even
        mov     bx,ax
        mov     ax,dx                   ; ax:bx = new size of last segment
        mov     di,gsls_iLastSeg        ; GDT_SFT:di -> last segment selector
        push    GSEL GDT_SFT
        pop     es                      ; es:di -> dito
    assume es:nothing
        mov     di,es:[di]              ; di = last segment selector
; SegRealloc(ax:bx=size di=selector)
        CALLFAR SegRealloc
        jc      gsls30                  ; no memory, go clean up and exit

; update CALLSFN, cGrow, and put new entries on free list
        mov     cx,gsls_cLastNew        ; cx = new #entries in last segment
        sub     cx,gsls_cLast           ; cx = #entries added to last segment
        sub     gsls_cGrow,cx           ; update cGrow
        push    GSEL GDT_SFT
        pop     ds
        mov     bx,ds:CALLSFN           ; bx = SFN of first new entry
        add     ds:CALLSFN,cx           ; update cAllSFN

; bx = SFN
; cx = number of new entries added to last segment

; InitSFE(bx=sfn, cx=count)
        call    InitSFE
        mov     ax,gsls_cGrow           ; ax = entries left to add
        clc
        jmp     gslsx                   ; exit with carry cleared

; clean up: free last segment if we just allocated it, then set carry
; and exit
gsls30: mov     ax,gsls_cLast           ; ax = #entries in last segment
        or      ax,ax                   ; is last segment empty?
        jnz     gsls40                  ;   No, go set carry and exit
        mov     di,gsls_iLastSeg        ; GDT_SFT:di -> last segment selector
        push    GSEL GDT_SFT
        pop     es                      ; es:di -> dito
    assume es:nothing
        mov     ax,es:[di]              ; ax = last segment selector
        CALLFAR SegFree                 ; free the segment
gsls40: stc

gslsx:  LeaveProc
        ret
EndProc GrowSFTLastSeg


;**     InitSFE - put virgin SFT entry on free list
;
;       Similar to SFLinkFree, but deals with SFT entries that have
;       never been initialized. If cx is too large, the procedure will
;       return when it reaches 64K. CALLSFN must be updated to make
;       SFN valid before calling InitSFE.
;
;       ENTRY   bx     = starting SFN number to assign to free entries
;               cx     = number of SFT entries to be freed
;
;       EXIT    if carry clear - success
;               if carry set - error
;
;       USES    ax,bx,cx,si,di,ds,es,flags

Procedure InitSFE,hybrid
    assume ds:nothing;es:nothing;ss:nothing

; SFFromSFNNoRefChk(bx=sfn)
        call    SFFromSFNNoRefChk
        jc      fsex            ; invalid SFN, exit with carry set

; bx = SFN
; cx = SFT entries to free
; es:di -> first sft entry to free

        jcxz    fsex            ; zero request, exit with carry cleared

        DosContext <ds>
        mov     ax,SFT_FreeHead.Segmt
        mov     si,SFT_FreeHead.Offst   ; ax:si -> free list

; ax:si -> free list
; bx = SFN
; cx = SFT entries remaining
fse10:  mov     es:[di].sf_chain.Segmt,ax
        mov     es:[di].sf_chain.Offst,si ; link in free list
        mov     es:[di].sf_fsi.sfi_selfsfn,bx
        mov     es:[di].sf_flags2,sff2_isfree
        mov     ax,es
        mov     si,di                   ; ax:si -> free list
        inc     bx                      ; next SFN
        add     di,size sf_entry        ; next SFT entry
        jc      fse20                   ; reach end of segment
        loop    fse10                   ;   loop until done

fse20:  mov     SFT_FreeHead.Segmt,ax
        mov     SFT_FreeHead.Offst,si   ; update free list
fsex:   ret
EndProc InitSFE


;***    doshlp_RedirDev - redirector helper for redirection of PRN/AUX
;
;       This subroutine will return a pointer to the preopened
;       SFT for the PRN or AUX device in the 3.x box and a pointer
;       to the shadow copy of the flags maintained by the 3xbox.
;       For future expansion to multiple 3.x boxes, the call will
;       return an arbitrary number of pointers to SFTs/RMDevInfo flags pairs.
;
;       Note that the redirector will check the pointer to the
;       shadow copy of the flags for NULL, thus allowing for the
;       future if the shadow flags are no longer needed.
;
;       See RMDevCheck for more info.
;
;       Each buffer element is 2 far pointers.
;
;       NOTE: when this procedure is called, SS is not necessarily the
;             3xbox's PTDA.
;
;       ENTRY   (DL) =  0 - "AUX", handle 3 in 3.x box
;                       1 - "PRN", handle 4 in 3.x box
;               (DH) =  number of DWORDs times 2 in result buffer
;               (DS:SI) = buffer for SFT pointers
;
;       EXIT    Carry set if buffer too small
;               Carry clear if all pointers fit in buffer
;                       (DH) = number of SFT pointers in buffer
;
;       USES    AX, BX, DX, DI, ES, Flags
;

Procedure       doshlp_RedirDev,FARONLY
    ASSUME  ds:nothing,es:nothing

        SaveReg <ds,si,cx>
        xor     cl,cl                   ; (cl) = number of SFTs found

        DosContext es
        test    ProtOnlySystem,-1       ; 3.x box exist?

if 1 ; BUGBUG danno 25-Nov-1988
        jmp     dhrd8
else ; BUGBUG danno 25-Nov-1988
     ; temporarily disabled for MVDM

        jnz     dhrd8                   ; no, we're done
        call    dhrd_GetSFT             ; get the pointer if it exists
    assume      es:nothing
        jc      dhrd8                   ;   Not device

        cmp     dh,cl                   ; enough room in array?
        jb      dhrdx                   ;   No, CF is set, exit

        mov     ds:[si.Offst],di
        mov     ds:[si.Segmt],es

;*  Try to find the 3x box private copy of the flags
        SaveReg <ds,si,cx,dx>           ; remember buffer, count and type
        mov     bl,dl
        xor     bh,bh
        add     bx,3                    ; (bx) = JFN for AUX or PRN
        CALLFAR RMDevCheck              ; Try to get offset of RMDevInfo entry
        mov     bx, si                  ; we need SI as our buff index
        RestoreReg <dx,cx,si,ds>        ; Get back count, type, and buffer
        jc      dhrdx                   ; Carry set means handle invalid

;*  We ignore ZF return from RMDevCheck, since dhrd_GetSFT guarantees that
;   we have a device.
;
;   (bx) = offset of RMDevInfo entry
;
;BUGBUG 26-Aug-1987 bws Assume RMDevInfo is in DOSGROUP

        lea     bx,[bx].rmdi_flags      ; (DOSGROUP:bx) -> shadow flags
        add     si,4                    ; Skip past the SFT pointer
        mov     ds:[si.Offst],bx
        mov     ds:[si.Segmt],DOSGROUP
        inc     cl                      ; Another valid entry

endif ; BUGBUG danno 25-Nov-1988

dhrd8:  clc

dhrdx:  mov     dh,cl                   ; (dh) = number of SFTs found
        RestoreReg <cx,si,ds>
        ret
EndProc         doshlp_RedirDev


if 0 ; BUGBUG danno 25-Nov-1988
     ; temporarily disabled for MVDM

;***    dhrd_GetSFT - get a 3.x box SFT pointer
;
;       This subroutine will return a pointer to the preopened
;       SFT for the PRN or AUX device in the 3.x box.
;
;       NOTE: when this procedure is called, SS is not necessarily the
;             3xbox's PTDA. Therefore we can't call SFFromHandle because
;             it uses the handle in the current context (PTDA). p3xJFN is
;             ok.
;
;       ENTRY   (DL) =  0 - "AUX", handle 3 in 3.x box
;                       1 - "PRN", handle 4 in 3.x box
;
;       EXIT    Carry set if no corresponding SFT
;               Carry clear if matching SFT found
;                       (ES:DI) = pointer to SFT
;
;       USES    AX, BX, DI, ES, Flags

Procedure       dhrd_GetSFT,NEAR,local
    ASSUME  ds:nothing,es:nothing,ss:nothing

        SaveReg <ds,si,cx,dx>

        mov     bl,dl
        xor     bh,bh
        add     bx,3                    ; (bx) = fake JFN for AUX or PRN
        call    p3xJFN                  ; bx = real JFN
ifdef FSSTRICT
        jc      dhrdie                  ; what?! illegal handle
endif

;       (AX) = selector from p3xJFN

        push    bx
        CALLFAR GDTFree                 ; free up temporary selector
        pop     bx

; simulate SFFromHandle here
; bx = real handle (could be 0ffh, which means STDPRN/STDAUX was closed)

        cmp     bl,SFN_NULL             ; free handle?
        jae     dhrd95                  ; yes, return error

        mov     ax,GSEL GDT_3xBoxPTDA
        mov     ds,ax                   ; ds = 3xbox's ptda
        les     di,ds:[JFN_pTable]      ; es:di -> real 3xbox jfn table
        add     bx,bx                   ; adjust offset for word table
        mov     bx,es:[di+bx]           ; bx = real SFN

        cmp     bx,SFN_NULL             ; free SFN?
        je      dhrd95                  ; yes, return error indication

        call    SFFromSFN               ; es:di=sft (bx=sfn)
        jc      dhrd95
        test    es:[di.sf_flags],DEVID_DEVICE
        jz      dhrd95                  ; not a device, we don't care about it

        or      dl,dl                   ; looking for AUX or PRN?
        je      dhrd51                  ; aux
        mov     ax,'RP'                 ; (ax:bx) = "PRN" + <nul>
        mov     bx,' N' and 0ffh        ; bx = N + <nul>
        jmp     short dhrd52
dhrd51:
        mov     ax,'UA'                 ; (ax:bx) = "AUX" + <nul>
        mov     bx,' X' and 0ffh        ; bx = X + <nul>
dhrd52:
        cmp     word ptr es:[di].sf_fsd.sfdFAT_name,ax     ; name match?
        jne     dhrd95                          ; no
        cmp     word ptr es:[di].sf_fsd.sfdFAT_name+2,bx
        jne     dhrd95                          ; no

;;      clc                             ; compare above cleared carry
        jmp     short dhrdxx            ; all tests passed, return okay

dhrd95:                                 ; return error indication
        stc
dhrdxx:
        RestoreReg <dx,cx,si,ds>
        ret

ifdef FSSTRICT
dhrdie: InternalError <dhrd_GetSFT: bad handle>
endif
EndProc         dhrd_GetSFT


endif ; BUGBUG danno 25-Nov-1988

HIGH2CODE ENDS

        FARCODE INIT
INITCODE segment

;**     SFTInit - set up SFT data structures
;
;       ENTRY   none
;
;       EXIT    carry clear - success
;               carry set - failed
;
;       USES    ax,bx,cx,dx,si,di,ds,es,flags

Procedure SFTInit
    assume ds:nothing,es:nothing,ss:nothing

; allocate a GDT segment to hold the SFT segments index
        mov     bx,sizeGDTSFT           ; bx = size of segment
        xor     ax,ax                   ; ax:bx = size of segment
        mov     si,HF_ZEROINIT          ; si = hf flags
        mov     cx,(D_DATA0 shl 8) + SA_GDT+SA_ALLOCATED+SA_SPECIFIC
        mov     di,FSSFTOWNER
        push    GSEL GDT_SFT            ; pass to SegAlloc
        CALLFAR SegAlloc

        jnc     sfti5                   ; success, continue
        Panic <'SFTInit: SegAlloc'>
sfti5:  pop     es

; we assume SFT_FreeHead are all zero (statically initialized)
IFDEF FSSTRICT
        DosContext <ds>
        mov     ax,SFT_FreeHead.Segmt
        or      ax,SFT_FreeHead.Offst
    assume ds:nothing
        jz      sfti10          ; assumption valid, continue

        Panic <'SFTInit: CALLSFN not 0'>
sfti10:
ENDIF

; Now we have a valid SFT of size zero, grow it to default size
        mov     ax,SF_DEFAULT_NUMBER
        CALLFAR DoGrowSFT

IFDEF FSSTRICT
        jnc     sftix           ; grow succesful, return carry cleared

sftiie2:Panic <'SFTInit: DoGrowSFT'>
ELSE
; if DoGrowSFT fail, we'll exit with carry set automatically
ENDIF

sftix:  ret
EndProc SFTInit

INITCODE ends
        END
