;       SCCSID = @(#)rom.asm    6.15 92/03/27
TITLE   ROM - Miscellaneous routines
NAME    ROM

SCCSID equ  "@(#)rom.asm        6.15 92/03/27"
;
;       IBM/Microsoft Confidential
;
;       Copyright (c) IBM Corporation 1987, 1989
;       Copyright (c) Microsoft Corporation 1987, 1989
;
;       All Rights Reserved
;
;   Misc Low level routines for doing simple FCB computations, Cache
;   reads and writes, I/O optimization, and FAT allocation/deallocation
;
;       Global          Local
;
;       SKPCLP
;       FNDCLUS
;       BufSec
;       BufRd
;       BigBufRd
;       BufWrt
;                       CMSkip
;                       CMSize
;                       CMAlloc
;                       CMCheckFail
;       CMAttendSwapper
;                       CMFill
;                       CMLock
;                       CMUnlock
;       CMKill
;       CMShrink
;       NextSec
;       Optimize
;       FigRec
;       Allocate
;       Release
;       RelBlks
;       AllocateEAVL
;       ReleaseVL
;       GetEOF
;
;
;   NOTE ON SWAPPER INTERACTION:
;
;       Because the swapper always writes full sectors, BufWrt will never
;       be called to do swapper output.  However, BufRd will be called to
;       do swapper data reads, so BufRd uses GetBuffrBP to cause Bypass
;       reads to be used were appropriate.
;
;  Change History:
;
;  106104 D.Azzarito 4/11/95  Fix cm_iValid immdeiately if we are
;                             shrinking the Swapper.
;  106225 ChangeTeam 1/16/95  FlushBuf called in AllocateEA would cause Current
;                             Dircetory Structure corruption when attempting
;                             to lock a drive. Conditional Flushing buffers
;                             in AllocateEA and preRelease routines.
;

INCL_NOREFERS   equ     1       ; for kmode.inc

.xlist
include fnofs.inc
include dosseg.inc
.xcref
include basemaca.inc
include osmaca.inc
include oldmaca.inc
include defdbug.inc
include buffer.inc
include dossymc.inc
include filemode.inc
include error.inc
include tkexport.inc
include mft.inc
include osconst.inc
include vmowner.inc
include pgexport.inc
include vmexport.inc
include mi.inc
include sft.inc
include protmode.inc
include seldesc.inc
include virtsym.inc
include fsh.inc
include vpb.inc
include dpb.inc
include devsym.inc
include kmode.inc
include sched.inc
include filemacs.inc
include infoseg.inc
include mmpha.inc
.cref
.list
        ModName rom,FNO_ROM_ASM

        ReferTask   CurrTCB,WORD        ; Current task control block

        ReferGDT    GDT_VPB,WORD

;       ReferFlat   _smHeap,DWORD          Defect# 24845
        ReferFlat   _pgCommitSem,BYTE
        ReferFlat   _SMSwapperSem,BYTE

        EXTRN       CheckExtent:NEAR
        EXTRN       DecVPBRef:NEAR
        EXTRN       FuBuff:NEAR
        EXTRN       GetBuffrBP:NEAR
        EXTRN       IncVPBRef:NEAR
        EXTRN       IsEOF:NEAR
        EXTRN       Pack:NEAR
        EXTRN       PlaceBufLRU:NEAR
        EXTRN       PlaceBufMRU:NEAR
        EXTRN       ProbeBuff:NEAR
        EXTRN       RlseBuffr:NEAR
        EXTRN       SuBuff:NEAR
        EXTRN       UnPack:NEAR
        EXTRN       DRead:NEAR
ifdef FSSTRICT
        EXTRN       checkfat:NEAR
endif
        EXTRN       UseCache:NEAR
        EXTRN       Init_VPB_Cache:NEAR    ; #86745


        EXTRN32         _KSEMRelease
        EXTRN32         _KSEMRequestExclusive
        EXTRN32         _KSEMRequestShared
        EXTRN32         _KSEMRequestMutex
        EXTRN32         _KSEMReleaseMutex
        EXTRN32         _TKSleep
        EXTRN32         _VMAllocKHB
        EXTRN32         _VMReallocKHB
        EXTRN32         _VMFreeKHB
        EXTRN32          AllocCluster
        EXTRN32          FreeCluster
        EXTRN32         _BufFlushFATDir

        EXTRN32         _VMRequestSems          ;; 77961
        EXTRN32         _VMReleaseSems          ;;

        EXTRNFAR    UnPack
        EXTRNFAR    UnPack
        EXTRNFAR    WaitVPB
        EXTRNFAR    WakeVPB
        EXTRNFAR    GetBuffrB
        EXTRNFAR    BufWrite

        ReferGlobal _flGlobalActions,DWORD

        EXTRNFAR    FlushBuf
        EXTRNFAR    Volume_Alloc_Lock
        EXTRNFAR    Volume_Alloc_Unlock
        EXTRNFAR    GetMRUBufSec
        EXTRNFAR    RlseBuffr

        CpuMode 386     ; enable 32 bit constants


;*  Initial size and growth delta for cluster map
;
;   We choose these numbers such that a cluster map is always allocated as
;   a multiple of the minimum memory block size.
;
;   minimum allocation: Minimum size memory block
;   growth allocation:  2*Minimum size memory block
;
;   If minimum size memory block is 32 bytes, then
;
;       initial clusters = (32-4)/2 = 14
;
;                     File Size Mapped by
;   Bytes per Cluster   Initial     Grow
;   -----------------   -------     ----
;   0.5K                    7K       32K
;   2.0K                   14K      128K    <- usual hard disk configuration
;   4.0K                   28K      256K

ALLOC_GRAN_BYTES equ    32      ; MemMgr allocation granularity (bytes)
                                ; This MUST be a power of 2!

CM_HEAD_BYTES   equ     (size ClusMap-size cm_map)
CM_HEAD_WORDS   equ     (CM_HEAD_BYTES/2)

CM_MIN_ORD      equ     ((ALLOC_GRAN_BYTES-CM_HEAD_BYTES)/2)-1  ; zero based
CM_GROW_BYTES   equ     (2*ALLOC_GRAN_BYTES)
CM_GROW_WORDS   equ     (CM_GROW_BYTES/2)
CM_MAX_ORD      equ     (0FFFFh - 2)

;*      CM_MINCLUS is the minimum number of clusters in a file before we
;       decide to go to the expense of allocating a cluster map.  Making this
;       number small means that we will have lots of small heap objects lying
;       about.

CM_MINCLUS  EQU 32


;* Kernel heap to allocate non-swapfile cluster maps from
;
; The cluster map for the swap file must allways be resident
; so it gets allocated from _smHeap. Defect# 24845 fix requires that
; the swapfile cluster map comes from the resident heap and not
; from the swapper's resident heap (_smHeap).  The problem is that
; when the swapper's cluster map needs tobe grown, could cause an allocation
; tobe attempted which could cause the swapper's disk frame bit map to
; grow, which fails if both reside in the swapper's heap.

CMAP_HEAP               EQU     VM_HKH_PUB_SWAPRW





;*  Variables for supporting swap file cluster map allocation/growth
;
;   Even though they are only used in PROT mode, we place them in DOSGROUP
;   to avoid segment loads in Exit_Kmode.

DATA    segment
CMSwap_sft      dd      0       ; SFT of file
CMSwap_iAlloc   dw      0       ; Desired largest ordinal
DATA    ends


        FARCODE HIGH2
HIGH2CODE SEGMENT
        ASSUME  SS:TASKAREA

Break   <FNDCLUS -- Skip over allocation units>

;***    FNDCLUS - Skip over the clusters for the file from the
;                 current position
;
;       ENTRY   CX = No. of clusters to skip
;               ES:BP points to VPB
;               [ThisSFT] point to SFT
;
;       EXIT    'NC' if success
;                   BX = Last cluster skipped to
;                   CX = No. of clusters remaining (0 unless EOF)
;                   DX = Position of last cluster
;               'C' if error (currently user FAILed to I 24)
;
;       USES    BX, CX, DX, DI, SI, Flags
;
;       EFFECTS Switches Context
;
Procedure   FNDCLUS,faronly

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

        chkvselhd es,bp
;;      Assert      ISDPB,<ES,BP>,"FndCLus"
        mov     di,CurrTCB
        les     di,TASKAREA:[di].TCBThisSFT
        Assert  ISSFT,<ES,DI>,"FndClus"
        MOV     BX,ES:[DI.sf_fsd.sfdFAT_lstclus]    ; BX = last cluster of file
        MOV     DX,ES:[DI.sf_fsd.sfdFAT_cluspos]    ; DX = current pos in file
        OR      BX,BX
        JZ      SHORT fc30              ; Carry is clear
;
; If our current position within the file is before the position we want to
; go to, then we "optimize" by scanning forwards from the current position.
; If not, we start from the beginning of the file and scan forwards.
; We NEVER scan backwards.
;
        SUB     CX,DX
        JNB     SHORT fc10              ; Scan from current position
        ADD     CX,DX
        XOR     DX,DX
        MOV     BX,ES:[DI.sf_fsd.sfdFAT_firFILEclus]    ; Start from beginning
fc10:   VPBContext es,bp
        JCXZ    SHORT fc20      ; No clusters to skip. Return.

;*  (bx)    =  cluster to start at
;   (cx)    =  number of clusters to skip
;   (dx)    =  ordinal of cluster in file
;   (es:bp) -> VPB
;
        call    SKPCLP
fc20:   ret

;
; There are no clusters allocated to this file. Return this information.
; Carry is clear.
;
fc30:   VPBContext es,bp
        DEC     DX
        add     cx,1            ; INC cx and set carry if CX overflow
        RET
EndProc FNDCLUS


;***    SKPCLP - skip cluster position
;
;       ENTRY   (bx)    =  start cluster
;               (cx)    =  number of clusters to skip
;               (dx)    =  ordinal of start cluster in file
;               (es:bp) -> VPB
;               TCBThisSFT -> SFT -> MFT
;
;       EXIT    'NC'if success
;                   (bx) = Last cluster skipped to
;                   (cx) = No. of clusters remaining (0 unless EOF)
;                   (dx) = Position of last cluster
;               'C' if failure
;                   (ax) = error code
;
;       USES    esi,edi,flags
;       USES    ax ONLY if carry set
;
;       EFFECTS Switches context
;
;       PSEUDO CODE
;
;           // clusterData represents return information.  This includes
;           // CF set and AX = error code for failures from CMSkip/CMFill.
;
;           KSEMRequestShared
;           if fail
;               goto Skip
;           state = shared
;           goto Top
;
;       GetExcl:
;           KSEMRelease
;           state = free
;           KSEMRequestExclusive
;           if fail
;               goto Skip
;           state = exclusive
;
;       Top:
;           pCMap = mft.pCMap
;
;           if (pCMap) {                        // cmap already allocated
;
;               if (want <= valid) {
;                   clusterData = pCMap[want]   // in cmap, just get it
;                   goto Done
;               }
;
;               if (state != exclusive)         // need exclusive for grow
;                   goto GetExcl
;
;               if (want > alloc) {
;                   CMAlloc()                   // no room in cmap, realloc
;                   if (error) {
;                       TMF_CMapFailed = TRUE;
;                       goto Skip
;                   }
;               }
;
;               clusterData = CMFill()          // fill in to wanted value
;
;           } else {                            // cmap NOT allocated
;
;               if (want < min) {
;       Skip:       clusterData = CMSkip()      // not big enough to alloc cmap
;                   goto Done
;               }
;
;               if (state != exclusive)         // need exclusive for alloc
;                   goto GetExcl
;
;               CMAlloc()                       // alloc initial cmap
;               if (error) {
;                   TMF_CMapFailed = TRUE;
;                   goto Skip
;               }
;               clusterData = CMFill()          // fill in initial cmap
;           }
;
;       Done:
;           if (state != free)
;               KSEMRelease                     // release only if not free
;           return (clusterData)

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

LocalVar        sc_hvpb,DWORD
LocalVar        sc_semstate,WORD
LocalVar        sc_firstclus,WORD

SC_SEM_FREE     EQU     0               ; free
SC_SEM_SHARE    EQU     1               ; shared
SC_SEM_EXCL     EQU     2               ; exclusive

        mov     si,bp                   ; (es:si) -> vpb - can't use bp!

        EnterProc <es,si,SC_SEM_FREE>

        SaveReg <ds>
        push    ax                      ; Save caller's ax

        mov     ax,dx                   ; (ax) = current ordinal
        add     ax,cx                   ; (ax) = desired ordinal
        jc      scSkip                  ;   Ordinal wraps, handle slow way
;BUGBUG 11-10-87 bryand - can this ever happen?????

        mov     di,CurrTCB              ; (di) = Current TCB
        lds     di,TASKAREA:[di].TCBThisSFT     ; (ds:di) -> SFT
    assume  ds:nothing
        mov     si,ds:[di].sf_fsd.sfdFAT_firFILEclus ; (si) = first cluster
        mov     sc_firstclus,si         ; save first file cluster

        mov     edi,ds:[di].sf_MFT
        FlatContext <ds,es>,,noassume   ; (ds:edi) -> MFT, ds = es = FlatContext

        call    CMRequestShared         ; get shared lock
        jc      SHORT scFail            ; Could not wait for cluster map
        mov     sc_semstate,SC_SEM_SHARE
        jmp     short scTop

scGetx: call    CMRelease               ; release shared lock
        mov     sc_semstate, SC_SEM_FREE
        call    CMRequestExclusive      ; get exclusive lock
        jc      SHORT scFail            ; Could not wait for cluster map
        mov     sc_semstate,SC_SEM_EXCL


; CMapSem is owned either shared or exclusive

scTop:  mov     esi,ds:[edi].mft_pCMap  ; (esi) -> cluster map

        or      esi,esi                 ; Cluster map allocated yet?
        jz      SHORT scFirst           ;   NO, go allocate it

        cmp     ax,ds:[esi].cm_iValid   ; Is ordinal already in map?
        jbe     SHORT sc70              ;   YES, do quick thing

        cmp     sc_semstate,SC_SEM_EXCL
        jne     scGetx                  ; go get exclusive sem

        cmp     ax,ds:[esi].cm_iAlloc   ; Does map have room?
        jbe     SHORT scFill            ;   YES, fill it in
        jmp     SHORT scAlloc           ;   NO, go allocate it

; first time allocation

scFirst:
        cmp     ax,CM_MINCLUS           ; is file long enough yet?
        jb      SHORT scSkip            ;   no, go do it manually

        cmp     sc_semstate,SC_SEM_EXCL
        jne     scGetx                  ; go get exclusive sem

;       (ax)    =  ordinal of cluster in file
;       (bx)    =  start cluster
;       (cx)    =  number of clusters to skip
;       (dx)    =  ordinal of start cluster in file
;       (esi)   -> cluster map
;       (edi)   -> MFT entry
;
;       We have no cluster map and we need to allocate one.  However, since
;       we may have done some I/O recently, and the beginning cluster (bx)
;       may not be the first cluster in the file.  All the cluster mapping
;       code relies on the array being filled in from ordinal 0. Therefore,
;       we need to adjust our cluster pointers to begin scanning from the
;       start of the file.

        mov     cx,ax                   ; (cx) = number of clusters to skip
        xor     dx,dx                   ; (dx) = ordinal of beginning of file
        mov     bx,sc_firstclus         ; (bx) = first cluster of file

;       We've adjusted cluster pointers so that CMFill will start scanning
;       at the beginning of the file.
;
;       (ax)     =  ordinal of cluster in file
;       (bx)     =  start cluster of file
;       (cx)     =  number of clusters to skip = (ax)
;       (dx)     =  ordinal of start cluster in file = 0
;       (ds:esi) -> cluster map
;       (ds:edi) -> MFT entry

scAlloc:
        call    CMAlloc                 ; Allocate or Grow cluster map
        jc      SHORT scFail            ;   Go fill in map

scFill: SaveReg <es,bp>
        les     bp,sc_hvpb              ; (es:bp) -> vpb
    assume  ds:nothing
        call    CMFill                  ; Fill in map, get data
        RestoreReg <bp,es>
        jmp     SHORT scDone            ; Go to exit



scFail: mov     si,CurrTCB
        or      TASKAREA:[si].TCBMiscFlags,TMF_CMapFailed ; Remember failure

scSkip: SaveReg <di,ds,es,bp>
        les     bp,sc_hvpb              ; (es:bp) -> vpb
    assume  ds:nothing
        call    CMSkip                  ; Find cluster manually
        RestoreReg <bp,es,ds,di>
        jmp     SHORT scDone            ; Go to exit


; value is already in cmap, just get it
; (ds:esi) -> cluster map
sc70:   mov     dx,ax                   ; (dx) = desired ordinal
        movzx   eax,ax                  ; (eax) = ordinal
        mov     bx,ds:[esi].cm_map.[eax*2] ; (bx) = cluster number
        xor     cx,cx                   ; (cx) = 0, found cluster, clear carry

scDone: pop     si                      ; (si) = caller's ax
        jc      SHORT sc90              ; Error, ax = error code
        xchg    ax,si                   ; (ax) = caller's ax

sc90:   mov     si,sc_semstate          ; (si) = semstate
    .errnz SC_SEM_FREE
        or      si,si
        jz      sc99                    ; sem not taken, don't release

;       (ds:edi) -> MFT entry
        call    CMRelease               ; release sem

sc99:   RestoreReg <ds>
    assume  ds:nothing
        les     si,sc_hvpb              ; (es:si) -> vpb
        LeaveProc
        mov     bp,si                   ; (es:bp) -> vpb
        ret

EndProc     SKPCLP


;***    CMSkip - skip to desired cluster
;
;       ENTRY   (bx)    =  cluster to start at
;               (cx)    =  number of clusters to skip
;               (dx)    =  ordinal of cluster in file
;               (es:bp) -> VPB
;               TCBThisSFT -> SFT -> MFT
;
;       EXIT    'NC' if success
;                   (bx) = Last cluster skipped to
;                   (cx) = No. of clusters remaining (0 unless EOF)
;                   (dx) = Position of last cluster
;               'C' if failure
;                   (ax) = error code
;               (ds) = TASKAREA
;
;       USES    bx,cx,dx,di,si,ds,flags
;
;       EFFECTS Switches context
;
;       PSEUDO CODE
;
;       while (countSkip > 0)
;           cluNext = UnPack(cluCurrent);
;           if (error)
;               return (C+error)
;           else if (cluNext >= EOF)
;               break;
;           else
;               cluCurrent = cluNext;
;               countSkip--;
;               ordCurrent++;
;       return (cluCurrent, countSkip, ordCurrent);

Procedure   CMSkip,hybrid
    assume  ds:nothing,es:nothing

LocalVar ClusterStart,WORD
LocalVar cClusters,WORD
LocalVar ClusterStartOrd,WORD
LocalVar hVPB,WORD

        mov     ax,bp
        EnterProc <bx,cx,dx,ax>

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

        mov     di,CurrTCB              ; (di) = Current TCB
        lds     di,TASKAREA:[di].TCBThisSFT     ; (ds:di) -> SFT
    assume  ds:nothing
        mov     edi,ds:[di].sf_MFT
        FlatContext <ds,es>,,noassume   ; (ds:edi) -> MFT, ds = es = FlatContext

        mov     esi,ds:[edi].mft_pCMap  ; (esi) -> cluster map

        or      esi,esi                 ; Cluster map allocated yet?
        jz      SHORT cmsk1             ;   NO, go skip

        mov     ax,dx                   ; (ax) = current ordinal
        add     ax,cx                   ; (ax) = desired ordinal
        jc      SHORT cmsk1             ;   Ordinal wraps, handle slow way
;BUGBUG 11-10-87 bryand - can this ever happen?????

        cmp     dx,ds:[esi].cm_iValid   ; Is start ordinal filled in yet ?
        jae     SHORT cmsk1             ;   NO, go do slow way

        cmp     ax,ds:[esi].cm_iValid   ; Is end ordinal already in map?
        jbe     SHORT cmsk1             ;   YES, go do slow way

        or      dx,dx                   ; Is it first ordinal ?
        jz      SHORT cmsk1             ;   YES, go do slow way

        sub     dx,ds:[esi].cm_iValid   ; (dx) = number of ordinals between
        neg     dx                      ;        ivalid and starting ordinal

        or      dx,dx                   ; Any clusters to skip in map ?
        jz      SHORT cmsk1             ;   NO, go do slow way

        sub     cx,dx                   ; (cx) = # of clusters to skipped so far
        jz      SHORT cmsk1
        jc      SHORT cmsk1

        movzx   edx,ds:[esi].cm_iValid  ; (edi) = starting ordinal
        lea     esi,ds:[esi].cm_map.[edx*2] ; (ds:esi) -> last valid cluster
        mov     bx,ds:[esi]             ; (bx) = cluster to start scan

        mov     ClusterStart,bx
        mov     cClusters,cx
        mov     ClusterStartOrd,dx

cmsk1:  RestoreReg <si,ax,ds,es,di>

        mov     bx,ClusterStart         ; (bx) = starting cluster
        mov     cx,cClusters            ; (cx) = count of clusters
        mov     dx,ClusterStartOrd      ; (dx) = starting cluster ordinal

        SaveReg <bp>
        mov     bp,hVPB

        TaskContext <ds>,,noassume

cmsk10: CALLFAR UnPack                  ; di=next cluster = Unpack (bx=cluster)
        jc      SHORT cmskx             ;   UnPack failed

        cmp     di,es:[bp].vpb_fsd.vpdFAT_minEOF    ; at EOF?
        jae     SHORT cmsk20            ;   Yes, exit loop

        mov     bx,di                   ; (bx) = next cluster
        inc     dx                      ; (dx) = ordinal of cluster in file
        loop    cmsk10

cmsk20: clc                             ; success

cmskx:  RestoreReg <bp>
        LeaveProc
        ret
EndProc     CMSkip


;***    CMSize - compute desired size of cluster map segment
;
;       Given a desired cluster ordinal, we generate a size for the cluster
;       map.  The size computed will provide space to store at least
;       CM_MIN_ORD ordinals, and any space after that will be a multiple
;       of CM_GROW_WORDS words.
;
;       The following relations hold:
;
;           ordAlloc =  CM_MIN_ORD + N * CM_GROW_ORDINALS;
;           ordAlloc >= ordDesired
;
;       ENTRY   (ax)  = desired cluster ordinal
;
;       EXIT    (eax) = size for VMAllocKHB/VMReallocKHB (up to 20000)
;               (dx)  = ordAlloc
;
;       USES    eax,bx,dx,flags
;
;       WARNING The maximum file cluster ordinal is FFFD!
;
;       PSEUDO CODE
;
;       hBytes = (size ClusMap header)          ; # of bytes in header
;       hWords = (size ClusMap header)/2        ; # of words in header
;
;       words = ordMin + hWords                 ; min words needed - 1
;       if (ordDesire > ordMin)
;           words = ordDesire + hWords          ; words needed - 1
;           words = (words OR (GrowW-1))        ; mask on low bits
;                                               ; = total words rounded up - 1
;
;       ordAlloc = words - hWords               ; zero based # alloc
;       size = (words + 1) * (size word)        ; size in bytes
;       return (size,ordAlloc);

Procedure   CMSize
    assume  ds:nothing

        mov     bx,CM_MIN_ORD+CM_HEAD_WORDS     ; (bx) = nwords min - 1
        cmp     ax,CM_MIN_ORD
        jbe     SHORT cms10                     ; desired ordinal <= min

        mov     bx,ax                   ; (bx) = desired ordinal
        add     bx,CM_HEAD_WORDS        ; (bx) = words needed - 1
        or      bx,CM_GROW_WORDS-1      ; (bx) = mask on low bits
                                        ;      = total words rounded up - 1

cms10:  mov     dx,bx                   ; (dx) = total words - 1
        sub     dx,CM_HEAD_WORDS        ; (dx) = ordAlloc
        movzx   eax,bx                  ; (ebx) = total words - 1
        inc     eax                     ; (eax) = total words
        shl     eax,1                   ; (eax) = total bytes

        ret
EndProc     CMSize


;***    CMAlloc - Allocate/Reallocate and initialize cluster map
;
;       Allocate a cluster map, put cluster 0 in map, set iValid and iAlloc
;       ordinals.  If this call is on behalf of the swapper, or if we have
;       already failed an allocation during this system call, don't try to
;       allocate.
;
;       ENTRY   (ax)     =  desired cluster ordinal
;               (ds:esi) =  pCMap (0 => pCMap not allocated)
;               (ds:edi) -> MFT Entry
;               ds = es = Flat
;               TCBThisSFT -> SFT -> MFT
;
;       EXIT    'NC' if success
;                   (ds:esi) -> (re)alloced cluster map
;                   (ds:edi) -> MFT Entry
;               'C' if failure
;                   (ds:esi) =  mft_pCMap
;                   (ds:edi) -> MFT Entry
;
;       USES    flags
;
;       EFFECTS Switches context
;
;       PSEUDO CODE
;
;       CMCheckFail (); /* see if we should try alloc */
;       if (C)
;           return (C);
;       else
;           <size,ordAlloc> = CMSize (ordDesire);
;           if (pCMap == 0)
;               pCMap = VMAllocKHB (size);
;               if (error)
;                   return (C);
;               pCMap.cm_map[0] = SFT->sf_firclus;
;               pCMap.cm_iValid = 0;            /* only know first cluster */
;           else
;               VMReallocKHB (pCMap, size);
;               if (error)
;                   return (C);
;               pCMap.cm_iValid = min(pCMap.cm_iValid,ordAlloc)
;
;           pCMap.cm_iAlloc = ordAlloc;
;           MFT.pCMap = pCMap
;           return (NC,pCMap);

Procedure   CMAlloc
    assume  ds:FLAT,es:nothing

LocalVar        cma_pCMap,DWORD         ; 32 bit flat pointer to cluster map
LocalVar        cma_iReqOrd,WORD        ; callers ax = requested ordinal
LocalVar        cma_pMFT,DWORD          ; 32 bit flaat pointer to MFT

        EnterProc <esi,ax,edi>

        SaveReg <eax,bx,ecx,edx>
;@116104  save regs before alloc check
        call    CMCheckFail             ; Can we try to alloc?
        jc      cmax                    ;   NO, fail

        call    CMSize                  ; (eax) = size,  (dx) = end_ordinal

        SaveReg <eax,dx>                ; (eax) = size,  (dx) = end_ordinal

        lea     edx,cma_pCMap           ; (ss:edx) -> localvar cma_pCMap
        SSToDS  edx                     ; (ds:edx) -> localvar cma_pCMap

        mov     ecx,CMAP_HEAP                   ; (ecx) = normal cmap heap
        test    [edi].mft_flags,mft_pagerheap   ; resizing swap file?
        jz      SHORT cma10                     ;   NO,  use normal heap
; start Defect# 24845
;       mov     ecx,_smHeap                     ; (ecx) = swap cmap heap
        mov     ecx, VM_HKH_PUB_RESRW           ; (ecx) = swap cmap heap
; end Defect# 24845

cma10:  or      esi,esi                         ; pCMap allocated yet?
        jnz     SHORT cma20                     ;   yes, go realloc

; allocate the cluster map

        CCALL32 _VMAllocKHB,<ecx,eax,FSCMOWNER,0,edx>

    assume  ds:nothing,es:nothing

        or      eax,eax
        RestoreReg <dx,eax>             ; (dx) = end_ordinal, (eax) = size
        stc                             ; Indicate failure
        jnz     SHORT cma99             ; alloc failed

; alloc succeeded; fill in iValid and cluster map
        mov     esi,cma_pCMap           ; (ds:esi) -> cluster map
        mov     ds:[esi].cm_iValid,0    ; Only first cluster is known
        mov     di,CurrTCB
        SaveReg <es>
        les     di,ss:[di].TCBThisSFT   ; (es:di) -> SFT
    assume  es:nothing
        mov     di,es:[di].sf_fsd.sfdFAT_firFILEclus ; (di) = first cluster
        RestoreReg <es>
        mov     ds:[esi].cm_map,di      ; Store first cluster in map
        jmp     SHORT cma90


; (eax)    =  size
; (ecx)    =  heap
; (ds:esi) -> already allocated CMap
; (ds:edx) -> localvar cma_pCMap -> already allocated CMap
;
; cluster map already allocated; realloc it

cma20:  CCALL32 _VMReallocKHB,<ecx,edx,eax,0>

        mov     esi,cma_pCMap           ; (ds:esi) -> realloced cluster map

        or      eax,eax
        RestoreReg <dx,eax>             ; (dx) = end_ordinal, (eax) = size
        stc                             ; Indicate failure
        jnz     SHORT cma99             ; realloc failed

        mov     ax,cma_iReqOrd          ; ax = caller requested ord
        cmp     ax,ds:[esi].cm_iValid   ; shrinking below prev iValid?
        jae     cma90                   ;  no, leave current iValid

        mov     ds:[esi].cm_iValid,ax   ;  yes, iValid = caller requested ord

; (dx)     =  end_ordinal
; (ds:esi) -> cluster map

cma90:  mov     ds:[esi].cm_iAlloc,dx   ; Set end cluster ordinal
        mov     edi,cma_pMFT            ; (ds:edi) -> MFT
        mov     ds:[edi].mft_pCMap,esi  ; set mft pointer to cmap
        clc                             ; success

cma99:  RestoreReg <edx,ecx,bx,eax>
        mov     edi,cma_pMFT            ; (ds:edi) -> MFT - for error exit
        LeaveProc

        ret                             ;@116104

; @116104 handle failure from CMCheckFail
; CMCheckFail failed
; If this is a shrink operation, we want to reduce cm_iValid
; so invalid clusters aren't in the map.  It is ok to not
; grow the map, but it is not ok to leave stuff that is being
; truncated in it.
cmax:
        or      esi,esi                 ; pCMap allocated yet?
        jz      cmax1                   ; no, we can leave we since aren't
                                        ; shrinking.

        mov     ax,cma_iReqOrd          ; ax = caller requested ord
        cmp     ax,ds:[esi].cm_iValid   ; shrinking below prev iValid?
        jae     cmax1                   ;  no, leave current iValid
        mov     ds:[esi].cm_iValid,ax   ;  yes, iValid = caller requested ord

cmax1:
        stc
        jmp     short cma99             ; Exit with carry set

;@116104 ends

EndProc     CMAlloc,NoCheck


;***    CMCheckFail - Check if we should fail alloc/realloc before trying
;
;       BUGBUG donh 3/14/89 - Is this code still relevant, since there is
;       no swapper process, but instead many threads that might be swapping?
;
;       Check if we should try to alloc/realloc.  If we are the swapper,
;       or if we have already failed during this system call, return fail.
;
;       If we are the swapper, we save the relevant data and set a flag that
;       is checked in Exit_Kmode (PMONLY versions only).  Some process other
;       than the swapper will see this flag and allocate/realloc the swap
;       file cluster map for us.  Then, when the swapper comes into SKPCLP,
;       the cluster map will be large enough and it will fill it in.
;
;       ENTRY   (ax) = desired cluster ordinal
;               TCBThisSFT -> SFT -> MFT
;
;       EXIT    'NC' if success
;                   may try to alloc/grow cluster map
;               'C' if failure
;                   don't try to alloc/grow cluster map
;
;       USES    flags
;
;       PSEUDO CODE
;
;       if (Swapper)
;           sft = TCBThisSFT;
;           CMSwap_sft = sft;       /* save SFT */
;           CMSwap_iAlloc = ord;    /* save desired ordinal */
;           _flGlobalActions |= KM_FF_ATTENDSWAP;
;                                   /* signal for other threads */
;           return (C);             /* couldn't alloc */
;       else if (TMF_CMapFailed)
;           return (C);
;       else
;           return (NC);

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

        SaveReg <di>
        mov     di,CurrTCB
        test    TASKAREA:[di].TCBfSwapping,SM_TCB_SWAPPING ; Swapping?
        jnz     SHORT cmcf10            ;   YES, set up for AttendSwapper
        test    TASKAREA:[di].TCBMiscFlags,TMF_CMapFailed ; Already failed?
;;;;    clc                             ; Assume success  ; test clears carry!
        jnz     SHORT cmcf90            ;   YES, go return carry
        RestoreReg <di>
        ret

cmcf10: SaveReg <eax,ds>                ; swapping, set up for AttendSwapper
        DosContext <ds>
        or      word ptr _flGlobalActions,KM_FF_ATTENDSWAP ;Signal other threads
        mov     CMSwap_iAlloc,ax                ; Save desired ordinal
        mov     eax,TASKAREA:[di].TCBThisSFT    ; Save SFT ptr
        mov     CMSwap_sft,eax
        RestoreReg <ds,eax>
    assume  ds:nothing

cmcf90: stc                             ; Fail
        RestoreReg <di>
        ret

EndProc     CMCheckFail,NoCheck


;***    CMAttendSwapper - do alloc/realloc of cluster map for swap file
;
;       Get information from CMSwap variables, wait for cluster map to
;       be unlocked, then alloc/grow cluster map.  If CMWait blocks we
;       start again at the top in case someone else got in and changed
;       things.  If we didn't do this, we could end up shrinking the
;       cluster map if another thread got in and grew it before us.
;
;       To avoid growing and shrinking the cluster map of the swap
;       file at the same time, we used to take the page manager overcommit
;       semaphore.  This shouldn't be needed any more since either
;       swap file shrinking is disabled or the MFT is properly serialized.
;
;       To avoid deadlock on the page manager overcommit semaphore,
;       the tcb flag SM_TCB_RESIZING is set during the cluster map
;       allocation.  This is OK because the swapper has already accounted
;       for any cluster map growth by adding a pad to the swap file.
;
;       ENTRY   ds = es = FLAT
;
;       EXIT    TCBThisSFT is trashed.
;
;       USES    none
;
;       EFFECTS Switches context
;
;       PSEUDO CODE
;
;       sft = CMSwap_sft;
;       mft = sft->sf_MFT
;       CMRequestExclusive
;
;       _flGlobalActions &= ~KM_FF_ATTENDSWAP;  /* we are doing grow now */
;       ordDesired = CMSwap_iAlloc;
;       sft = CMSwap_sft;
;       TCBThisSFT = sft;
;       pCMap = mft->pCMap
;       CMAllocSafe (ord, pCMap);
;
;       CMRelease
;       return
;
;  Change history:
;  ---------------
;  71990 Schmitt 11/18/93 - Acquire pgCommitSem
;  77961 Schmitt 01/26/94 - Call VMRequestSems to get kernel res. heap sem
;                            _then_ pgCommitSem
;
Procedure   CMAttendSwapper,far16
    assume  ds:nothing, es:nothing

        pushf
        pushad

        DosContext <ds>
        and     word ptr _flGlobalActions,NOT KM_FF_ATTENDSWAP
                                        ; We are doing the grow now
        lds     di,CMSwap_sft           ; (ds:di) = swap file SFT
    ASSUME      DS:NOTHING

        mov     edi,ds:[di].sf_MFT
        FlatContext <ds>,,noassume      ; (ds:edi) -> MFT entry

        ;; 77961 - Removed old comment/code. Call VMRequestSem to get
        ;;         kernel resident heap sem then pgCommitSem.
        CCALL32 _VMRequestSems < 0 >    ;; 77961 (dummy param)
        or      eax,eax
        jnz     cas10                   ; jump if error

        call    CMRequestExclusive      ; get exclusive lock on cmap

ifdef FSSTRICT
        jnc     cmasok                  ; swapping not set, ok
        InternalError <CMAttendSwapper - SM_TCB_SWAPPING>
cmasok:
endif ; FSSTRICT

        DosContext <ds>
        and     word ptr _flGlobalActions,NOT KM_FF_ATTENDSWAP
                                        ; We are doing the grow now
        mov     ax,CMSwap_iAlloc        ; (ax) = desired ordinal
        mov     edx,CMSwap_sft          ; (edx) = swap file SFT
        mov     bx,CurrTCB
        mov     TASKAREA:[bx].TCBThisSFT,edx ; TCBThisSFT = swap file

        FlatContext <ds>,,noassume      ; (ds:edi) -> MFT entry
        mov     esi,ds:[edi].mft_pCMap  ; (ds:esi) -> cluster map

        call    CMAlloc                 ; Try to allocate/reallocate

        call    CMRelease               ; release lock on cmap

        ;; 77961 - Removed old comment/code.
        CCALL32 _VMReleaseSems < 0 >    ;; 77961 (dummy param)

casx:   popad
        popf
        RETP

;       we jump here if we get error back from taking the overcommit
;       semaphore.  If we do, forget about growing the cluster map for
;       now and exit.  The next thread can do it.

cas10:  DosContext <ds>
        or      word ptr _flGlobalActions,KM_FF_ATTENDSWAP
        jmp     casx

EndProc     CMAttendSwapper


;***    CMFill - Fill cluster map up to requested cluster ordinal
;
;       Fill in cluster values, starting from last valid cluster in map, until
;       the cluster for the desired ordinal has been put in the map.
;
;       We ignore the start cluster passed by the caller, because we need to
;       ensure that the cluster map is filled in completely to the desired
;       ordinal.  It is possible for an application to do I/O at the front of
;       the file, then seek past the end of the file and do a write.  Allocate
;       will allocate clusters for the file, but SKPCLP doesn't get called
;       until we actually start writing.  This would leave a gap of invalid
;       data in the cluster map.
;
;       ENTRY   (ax)    = desired cluster ordinal
;               (bx)    = start cluster
;               (cx)    = number of clusters to skip
;               (dx)    = ordinal of start cluster
;               (es:bp) = VPB
;               (FLAT:esi) -> cluster map
;               (FLAT:esi.cm_iValid) = valid cluster number
;
;       EXIT    'NC' if success
;                   (bx) = Last cluster skipped to
;                   (cx) = No. of clusters remaining (0 unless EOF)
;                   (dx) = Position of last cluster
;                   cluster map filled in through desired ordinal
;               'C' if failure
;                   (ax) = error code
;
;       USES    bx,cx,dx,flags
;
;       EFFECTS Switches context
;
;       PSEUDO CODE
;
;       ordCurrent = cm_iValid;
;       cluCurrent = cm_map[ordCurrent];
;       countSkip = ordDesired - ordCurrent;
;       while (countSkip > 0)
;           cluNext = UnPack(cluCurrent);
;           if (error)
;               return (C+error)
;           else if (cluNext >= EOF)
;               break;
;           else
;               cluCurrent = cluNext;
;               countSkip--;
;               ordCurrent++;
;               cm_map[ordCurrent] = cluCurrent;
;       cm_iValid = ordCurrent;  /* set lasted filled ordinal */
;       return (cluCurrent, countSkip, ordCurrent);

Procedure   CMFill
    assume  ds:nothing,es:nothing

        SaveReg <esi,edi>

        movzx   edx,ds:[esi].cm_iValid  ; (edx) = starting ordinal
        mov     cx,ax                   ; (cx) = desired ordinal
        sub     cx,dx                   ; (cx) = number of clusters to fill in
        lea     esi,ds:[esi].cm_map.[edx*2] ; (ds:esi) -> last valid cluster

        mov     bx,ds:[esi]             ; (bx) = cluster to start scan

;*  (ds:esi) -> last cluster filled in
;   (bx)    =  cluster number
;   (cx)    =  number of clusters still to be filled in
;   (dx)    =  cluster ordinal

cmf10:  SaveReg <esi,ds>
        TaskContext <ds>,,noassume
        CALLFAR UnPack                  ; di=next cluster = Unpack (bx=cluster)
        RestoreReg <ds,esi>
    assume  ds:nothing
        jc      SHORT cmfx              ;   UnPack failed

        cmp     di,es:[bp].vpb_fsd.vpdFAT_minEOF    ; At EOF?
        jae     SHORT cmfx              ;   Yes, go exit
        ; jae => no carry

        mov     bx,di                   ; (bx) = next cluster
        inc     esi
        inc     esi                     ; (ds:esi) -> spot to receive cluster

        mov     ds:[esi],bx             ; Store cluster
        inc     dx                      ; (dx) = cluster ordinal
        loop    cmf10
        clc                             ; Success

cmfx:   RestoreReg <edi,esi>            ; (ds:esi) -> cmap
        mov     ds:[esi].cm_iValid,dx   ; Set last valid cluster ordinal
        ret

EndProc     CMFill


;***    CMRequestShared - get shared lock on mft_CMapSem
;
;       ENTRY   ds:edi -> MFT entry
;               ds = es = FlatContext
;
;       EXIT    if carry
;                   timeout, can't wait because we are swapping
;               else
;                   MFT.mft_CMapSem = share
;
;       USES    flags
;
;       WARNING **** WE MUST BE CAREFUL TO AVOID A SWAPPER DEADLOCK ****
;
;       Consider the following scenario:
;
;           swapper                     some other process
;           -------                     ------------------
;           Signal alloc/grow
;                                       Lock cluster map
;           Swap file activity in
;           order to satisfy request;
;
;       SKPCLP calls CMRequestShared for the swapper I/O request.
;       If we waited for the semaphore, the system would be deadlocked!
;       So, we use a timeout of zero on the KSEMRequest call and
;       return CARRY if we timeout.  SKPCLP sees CF and uses CMSkip
;       instead of the cluster map.  Eventually the grow will succeed, and
;       the swapper will fill in its cluster map.
;
;       PSEUDO CODE
;
;       KSEMRequestShared(MFT.mft_CMapSem);
;       return;

Procedure   CMRequestShared
    assume  ds:nothing,es:nothing

        SaveReg <eax,ecx,edx>

        mov     ecx,-1                          ; (ecx) = -1 = infinite timeout

        push    bp
        mov     bp,[CurrTCB]
        test    [bp].TCBfSwapping,SM_TCB_SWAPPING ; Are we swapping?
        pop     bp
        jz      cmrs50                          ; not swapping, infinite timeout
        xor     ecx,ecx
cmrs50: lea     edx,[edi].mft_CMapKSem          ; (edx) -> mft_CMapKSem
        CCALL32 _KSEMRequestShared,<edx, ecx>

        or      eax,eax
        jz      cmrsx                           ; no error, return no carry

        stc                                     ; error, set carry

cmrsx:  RestoreReg <edx,ecx,eax>
        ret
EndProc     CMRequestShared


;***    CMRequestExclusiveWait - get exclusive lock on mft_CMapKSem
;
;       Same as CRequest ExlusiveWait (below) but waits for the semaphore
;       even if we're swapping.
;
;       ENTRY   ds:edi -> MFT entry
;               ds = es = FlatContext
;
;       EXIT    if carry
;                   Could not lock semaphore
;               else
;                   MFT.mft_CMapKSem = share
;
;       USES    flags
;
;***    CMRequestExclusive - get exclusive lock on mft_CMapKSem
;
;       ENTRY   ds:edi -> MFT entry
;               ds = es = FlatContext
;
;       EXIT    if carry
;                   timeout, can't wait because we are swapping
;               else
;                   MFT.mft_CMapKSem = share
;
;       USES    flags
;
;       WARNING **** WE MUST BE CAREFUL TO AVOID A SWAPPER DEADLOCK ****
;
;       Consider the following scenario:
;
;           swapper                     some other process
;           -------                     ------------------
;           Signal alloc/grow
;                                       Lock cluster map
;           Swap file activity in
;           order to satisfy request;
;
;       SKPCLP calls CMRequestExclusive for the swapper I/O request.
;       If we waited for the semaphore, the system would be deadlocked!
;       So, we use a timeout of zero on the KSEMRequest call and
;       return CARRY if we timeout.  SKPCLP sees CF and uses CMSkip
;       instead of the cluster map.  Eventually the grow will succeed, and
;       the swapper will fill in its cluster map.
;
;       PSEUDO CODE
;
;       KSEMRequestExclusive(MFT.mft_CMapKSem);
;       return;

Procedure   CMRequestExclusiveWait
    assume  ds:nothing,es:nothing

        SaveReg <eax,ecx,edx>
        mov     ecx, -1                         ; infinite timeout
        jmp short cmre50                        ; Get semaphore


Entry   CMRequestExclusive
        SaveReg <eax,ecx,edx>

        mov     ecx,-1                          ; (ecx) = -1 = infinite timeout

        push    bp
        mov     bp,[CurrTCB]
        test    [bp].TCBfSwapping,SM_TCB_SWAPPING ; Are we swapping?
        pop     bp
        jz      cmre50
        xor     ecx,ecx                         ; not swapping, infinite timeout

cmre50: lea     edx,[edi].mft_CMapKSem          ; (edx) -> mft_CMapKSem
        CCALL32 _KSEMRequestExclusive,<edx, ecx>

        or      eax,eax
        jz      cmrex                           ; no error, return no carry

        stc                                     ; error, set carry

cmrex:  RestoreReg <edx,ecx,eax>
        ret
EndProc     CMRequestExclusiveWait, NoCheck


;***    CMRelease - release lock on mft_CMapKSem
;
;       ENTRY   ds:edi -> MFT entry
;               ds = es = FlatContext
;
;       EXIT    none
;
;       USES    flags
;
;       PSEUDO CODE
;
;       KSEMRelease(MFT.mft_CMapKSem);
;       return;

Procedure   CMRelease
    assume  ds:nothing,es:nothing

        SaveReg <eax,ecx,edx>

        lea     edx,[edi].mft_CMapKSem          ; (edx) -> mft_CMapKSem
        CCALL32 _KSEMRelease,<edx>
        clc
        RestoreReg <edx,ecx,eax>
        ret
EndProc     CMRelease


;***    CMKill - free cluster map
;
;       Free cluster map, if present, for specified file.
;
;       ENTRY   (ds:edi) -> MFT
;
;       EXIT    MFT->mft_pCMap = 0
;               cluster map freed
;
;       USES    NONE
;
;***    CMShrink - realloc cluster map for shrinking file
;
;       realloc cluster map, if present, for specified file.
;       This will free the cluster map if the file shrinks
;       below CM_MINCLUS cluters in size.
;
;       ENTRY   ax = ordinal number of new last cluster in file
;               TCBThisSFT->MFT
;
;       EXIT    MFT->mft_pCMap -> realloced cmap;
;               cm_iValid and cm_iAlloc set
;
;       USES    NONE
;
;       PSEUDO CODE
;
; CMKill:
;       size = 0
;       goto top
;
; CMShrink:
;       size = ax
;
; top:  if size < CM_MINCLUS
;           size = 0
;
;       CMRequestExclusive;             get exclusive lock on cmap
;
;       p = MFT->mft_pCMap;
;       if (p != 0)
;           if (size == 0)
;               MFT->mft_pCMap = 0;
;               VMFreeKHB (p);
;           else
;               CMAlloc(p,size)
;       CMRelease;                      release lock on cmap
;       return;

Procedure   CMKill,<hybrid,far16>
    assume  ds:nothing,es:nothing;ss:TASKAREA

        pushf
        pushad
        SaveReg <ds,es>

        xor     ax,ax                   ; (ax) = 0 size
        jmp     SHORT cmk5              ; jump to common code


Entry   CMShrink,hybrid

        pushf
        pushad
        SaveReg <ds,es>

        mov     di,CurrTCB
        lds     di,ss:[di].TCBThisSFT
    assume  ds:nothing
        mov     edi,ds:[di].sf_MFT

cmk5:   FlatContext <ds,es>,bx          ; (ds:edi) -> MFT entry, es = Flat

        cmp     ax,CM_MINCLUS           ; size < min?
        jae     cmk10                   ;   NO, ok

        xor     ax,ax                   ;   YES, (ax) = 0 size

;   We're  freeing the cluster map, if swapping, then nobody else should
;   have the semaphore.
;
        call    CMRequestExclusive
        jmp short cmk20


;   We're shrinking the cluster map, wait for the semaphore to be free.
;
cmk10:  call    CMRequestExclusiveWait
cmk20:
ifdef FSSTRICT
        jnc     cmkok                   ; swapping not set, ok
        InternalError <CMKill/CMShrink - SM_TCB_SWAPPING>
cmkok:
endif ; FSSTRICT

        mov     esi,ds:[edi].mft_pCMap  ; (esi) -> cluster map
        or      esi,esi                 ; Cluster map allocated?
        jz      SHORT cmkx              ;   No, don't free it

        or      ax,ax                   ; size == 0?
        jz      SHORT cmk50             ;   YES, free cmap


;* size != 0 clusters, realloc cluster map

        call    CMAlloc                 ;   NO, do realloc
        jmp     SHORT cmkx              ; done


;* size = 0 clusters, kill cluster map

cmk50:  mov     ecx,CMAP_HEAP                   ; (ecx) = normal cmap heap
        test    [edi].mft_flags,mft_pagerheap   ; resizing swap file?
        jz      SHORT cmk60                     ;   NO,  use normal heap

; start Defect# 24845
;       mov     ecx,_smHeap                     ; (ecx) = swap cmap heap
        mov     ecx, VM_HKH_PUB_RESRW           ; (ecx) = swap cmap heap
; end Defect# 24845

cmk60:  mov     ds:[edi].mft_pCMap,0            ; pCMap = NULL, no cluster map

        CCALL32 _VMFreeKHB,<ecx,esi>

cmkx:   call    CMRelease
        RestoreReg <es,ds>
        popad
        popf
        ret

EndProc     CMKill,NoCheck


Break  <BufSec -- BUFFER A SECTOR AND SET UP A TRANSFER>

;***    BufSec - Buffer a sector and set up a transfer
;
;       Insure specified sector is in buffer, flushing buffer before
;       read if necessary.
;
;       ENTRY   AH = priority of buffer
;               AL = 0 if buffer must be read, 1 if no pre-read needed
;               ES:BP point to VPB
;               SI = 0 - normal file system read
;                  = 1 - direct disk access read
;               DI = 0 for normal I/O (ALWAYS 0 FROM BufWrt)
;                  = ReadBypassAdj for swapper I/O (BufRd ONLY)
;               [ClusNum] = Physical cluster number
;               [SecClusPos] = Sector position of transfer within cluster
;               CX = Byte count of transfer
;
;       EXIT    ES:DI = Pointer to buffer
;               SI = Pointer to transfer address
;               CX = Number of bytes
;               [NextAdd] updated
;               Carry set if error (user FAILed to I 24)
;
;       USES    AX, BX, CX, DX, DI, SI, ES, Flags
;
;       EFFECTS Switches Context
;
Procedure   BufSec,NEAR
        ASSUME  DS:NOTHING,ES:NOTHING

        chkvselhd es,bp
        Assert      ISVPB,<ES,BP>,"BufSec"
        SaveReg <CX>                    ; Save byte count of transfer
        push    bp
        mov     bp,CurrTCB
        mov     [bp].TCBAllowed,ALLOWED_FAIL + ALLOWED_RETRY + ALLOWED_IGNORE

        mov     cx,word ptr [bp].TCBSecPos.HighWord
        mov     dx,word ptr [bp].TCBSecPos.LowWord  ; Assume direct access
        pop     bp

        or      si,si                   ; is access direct?
        jnz     SHORT bufsec1           ; yes, go skip cluster determination

        push    bp
        mov     bp,CurrTCB
        mov     dx,[bp].TCBClusNum
        mov     bl,[bp].TCBSecClusPos
        pop     bp
        FigRec32 BP              ; SI:DX = sector number
        xchg    CX,SI
; CX:DX = sector number

bufsec1:
        call    GetBuffrBP
        jc      SHORT bufsecerr
        mov     di,CurrTCB
        les     di,TASKAREA:[di].TCBCurBuf  ; ES:DI -> current kernel buffer
        Assert  ISCURBUF,<ES,DI>,"BufSec"

ifdef FSSTRICT
        cmp     word ptr es:[di].buf_sector+2,cx
        jne     SHORT ie_bs             ; high sector number for buffer is BAD
        cmp     word ptr es:[di].buf_sector,dx
        jz      SHORT bufsec3           ; low sector number for buffer is OK
ie_bs:  InternalError <BufSec - Invalid buffer from GetBuffrBP>
bufsec3:
endif ; FSSTRICT

        RestoreReg <CX>                 ; CX = byte count of transfer
        push    bp                      ; Save BP of entry
        mov     bp,CurrTCB
        mov     si,[bp].TCBNextAdd
        add     [bp].TCBNextAdd,cx
        Assert  ISCURBUF,<ES,DI>,"BufSec"
        or      es:[di.buf_flags],BUF_ISDATA
        LEA     DI,[DI].BUFINSIZ        ; Point to buffer
        add     di,[bp].TCBBytSecPos
        pop     bp                      ; BP = hVPB (Same as on entry)
        CLC
bufsecx:RET

bufsecerr:
        POP     CX                  ; Remove byte count from stack (preserve CX)
                                    ; j200324 preserve error code in AX
        RET

EndProc BufSec


Break   <BufRd, BufWrt -- PERFORM BUFFERED READ AND WRITE>

;***    BufRd - Transfer characters from a system buffer.
;
;       Do a partial sector read via one of the system buffers
;
;       NOTE    In order to avoid deadlock with the swapper, we can't just
;               call subuff.  Instead, we call subuff if the segment is
;               present.  If it's not present, we release our buffer, and
;               call probebuff, which will pull the segment in.  We then
;               loop around and try the operation again.
;
;       ENTRY   ES:BP points to VPB
;               SI = 0 - normal file system read
;                  = 1 - direct disk access read
;               CX = byte count of transfer
;
;       EXIT    Carry set if error
;                   (AX) = error code
;
;       USES    AX, BX, CX, DX, DI, SI, DS, Flags
;
;       EFFECTS Switches Context
;

bufrframe       struc
bufrf_es        dw      ?
bufrf_bp        dw      ?
bufrf_si        dw      ?
bufrf_TCBNA     dw      ?
bufrframe       ends

Procedure   BufRd,faronly
        ASSUME  DS:NOTHING,ES:NOTHING

        Assert      ISVPB,<ES,BP>,"BufRd"

bufr0:  chkvselhd es,bp

;       Save values we need for retry

        mov     di,CurrTCB

        .errnz  bufrf_es
        push    es

        .errnz  bufrf_bp-bufrf_es-2
        push    bp

        .errnz  bufrf_si-bufrf_bp-2
        push    si

        .errnz  bufrf_TCBNA-bufrf_si-2
        push    ss:[di].TCBNextAdd

        test    ss:[di].TCBfSwapping,SM_TCB_SWAPPING
        mov     di,0                    ; assume normal read, don't trash flags!
        jz      SHORT bufr1             ; not swapping, go do normal read
        mov     DI,ReadBypassAdj        ; are swapping, do swap read
bufr1:  MOV     AX,0
        CALL    BufSec
        JC      SHORT bufrx
        mov     bx,CurrTCB
        mov     bx,TASKAREA:[bx].TCBDMAAdd.Segmt

;       If target is present
;           then do transfer
;       If target is absent
;           then release buffer (to prevent swapper deadlock), force load,
;               and try again.
;       If target causes exception
;           then return error

        LOADAR  ax,bx                   ; AH = access byte
        jnz     SHORT bufrag            ; Access fault, depend on ProbeBuff below
        test    ah,D_PRES               ; Is selector present?
        jz      SHORT bufrag            ; not-present, go do special handling.

        mov     ax,es
        mov     ds,ax
    assume  ds:nothing
        XCHG    DI,SI
        call    SuBuff
    assume  ds:nothing,es:nothing
        jc      SHORT bufrx             ; Invalid user segment

        VPBContext es,bp

        mov     DI,CurrTCB
        LDS     DI,TASKAREA:[DI].TCBCurBuf
        Assert  ISCURBUF,<DS,DI>,"BufRd/EvenRD"
        LEA     BX,[DI.BUFINSIZ]
        SUB     SI,BX                   ; Position in buffer

        chkvselhd es,bp
        push    bp
        mov     bp,[CurrTCB]
        test    [bp].TCBfSwapping,SM_TCB_SWAPPING ; are we swapping?
        pop     bp
        jnz     SHORT bufr2             ; yes swapping, go chuck buffer
        CMP     SI,ES:[bp].vpb_fsi.vpi_cbSector ; Read Last byte?
        JB      SHORT bufr3             ; No, go exit.
bufr2:  call    PlaceBufLRU             ; move to head of "free" list
bufr3:  call    RlseBuffr
        Assert  ISVPB,<ES,BP>,"BufRd/EvenRD"
        CLC
bufrx:  mov     si,sp                   ; clean off es, bp, si, TCBNextAdd
        lea     sp,[si]+(SIZE bufrframe)  ; use lea to preserve flags
        TaskContext     DS
        ASSUME  DS:NOTHING
        RET                             ; EXIT

;       bufrag = bufr again
;       Since we got here, either the user segment isn't accessable, or
;       isn't present.  Release the buffer we hold, restore entry conditions
;       to variables, and call ProbeBuff.  If we got an access error, then
;       ProbeBuff will report it.  If we got a not present fault, ProbeBuff
;       will suck in the segment we want.  Retry or report error depending
;       on result from ProbeBuff.
;
;       (BX:SI) -> transfer address
;       (CX)    =  length

bufrag: call    RlseBuffr               ; release buffer
        mov     dh,1                    ; need read/write segment access
        SaveReg <cx>                    ; ProbeBuff trashes cx
        call    ProbeBuff               ; check access, force presence
        RestoreReg <cx>                 ; CX = byte count
        jc      SHORT bufrx             ; report error

;       Restore for retry

        mov     di,CurrTCB

        .errnz  bufrf_TCBNA-bufrf_si-2
        pop     ss:[di].TCBNextAdd

        .errnz  bufrf_si-bufrf_bp-2
        pop     si

        .errnz  bufrf_bp-bufrf_es-2
        pop     bp

        .errnz  bufrf_es
        pop     es

        jmp     bufr0                   ; go try operation again

EndProc BufRd


;***    BufWrt - Transfer characters into a system buffer.
;
;       Do a partial sector write via one of the system buffers
;
;       NOTE    In order to avoid deadlock with the swapper, we can't just
;               call subuff.  Instead, we call subuff if the segment is
;               present.  If it's not present, we release our buffer, and
;               call probebuff, which will pull the segment in.  We then
;               loop around and try the operation again.
;
;       ENTRY   ES:BP points to VPB
;               SI = 0 - normal file system read
;                  = 1 - direct disk access read
;               CX = byte count of transfer
;
;       EXIT    Carry set if error (currently user FAILed to I 24)
;
;       USES    AX, BX, CX, DX, DI, SI, DS, Flags
;
;       EFFECTS Switches Context
;

bufwframe       struc
bufwf_es        dw      ?
bufwf_bp        dw      ?
bufwf_si        dw      ?
bufwf_TCBSP     dw      ?
bufwf_TCBSP2    dw      ?
bufwf_TCBNA     dw      ?
bufwframe       ends

Procedure   BufWrt,<hybrid,far16>
        ASSUME  DS:NOTHING,ES:NOTHING

        Assert      ISVPB,<ES,BP>,"BufWrt"

bufw0:  chkvselhd es,bp

;       Save values we need for retry

ifdef FSSTRICT
        call    checkfat
endif
        mov     di,CurrTCB

        .errnz  bufwf_es
        push    es

        .errnz  bufwf_bp-bufwf_es-2
        push    bp

        .errnz  bufwf_si-bufwf_bp-2
        push    si

        .errnz  bufwf_TCBSP-bufwf_si-2
        push    word ptr ss:[di].TCBSecPos.LowWord

        .errnz  bufwf_TCBSP2-bufwf_TCBSP-2
        push    word ptr ss:[di].TCBSecPos.HighWord

        .errnz  bufwf_TCBNA-bufwf_TCBSP2-2
        push    ss:[di].TCBNextAdd

        xor     al,al                   ; presume preread
        or      si,si                   ; are we doing direct I/O?
        jnz     SHORT bufw5             ; yes, always preread, go read it

        SaveReg <CX>                    ; Save byte count of transfer
        push    bp
        mov     bp,CurrTCB
        add     word ptr [bp].TCBSecPos.LowWord,1   ; INC does not affect 'C'
        adc     word ptr [bp].TCBSecPos.HighWord,0
; Compare high words of sector to see if we need to pre-read
        mov     ax,word ptr [bp].TCBSecPos.HighWord
        cmp     ax,word ptr [bp].TCBValSec.HighWord ; Has sector been written before?
        MOV     AL,1
        ja      SHORT bufw4             ; Skip pre-read.
        jb      SHORT bufw2             ; Need to pre-read
; High word is the same. Need to look at low words.
        mov     ax,word ptr [bp].TCBSecPos.LowWord
        cmp     ax,word ptr [bp].TCBValSec.LowWord  ; Has sector been written before?
        MOV     AL,1
        JA      SHORT bufw4             ; Skip pre-read if SecPos > ValSec
bufw2:
        XOR     AL,AL
bufw4:
        pop     bp
        RestoreReg <CX>                 ; CX = byte count of transfer
bufw5:  XOR     DI,DI
        CALL    BufSec
        JC      SHORT bufwx
        mov     bx,CurrTCB
        mov     bx,TASKAREA:[bx].TCBDMAAdd.Segmt

;       If target is present
;           then do transfer
;       If target is absent
;           then release buffer (to prevent swapper deadlock), force load,
;               and try again.
;       If target causes exception
;           then return error

        LOADAR  ax,bx                   ; AH = access byte
        jnz     SHORT bufwag            ; Access fault, depend on ProbeBuff below
        test    ah,D_PRES               ; Is selector present?
        jz      SHORT bufwag            ; not-present, go do special handling.

        call    FuBuff
    assume  ds:nothing,es:nothing
        jc      SHORT bufwx             ; Invalid user segment

        VPBContext es,bp
        mov     BX,CurrTCB
        LDS     BX,TASKAREA:[BX].TCBCurBuf
        Assert  ISCURBUF,<DS,BX>,"BufWrt/EvenWrt"
        OR      [BX.buf_flags],BUF_DIRTY
        LEA     SI,[BX.BUFINSIZ]
        SUB     DI,SI                   ; Position in buffer
        MOV     SI,DI
        MOV     DI,BX

        chkvselhd es,bp
        push    bp
        mov     bp,[CurrTCB]
        test    [bp].TCBfSwapping,SM_TCB_SWAPPING ; are we swapping?
        pop     bp
        jnz     SHORT bufw6                     ; yes swapping, go chuck buffer
        CMP     SI,ES:[bp].vpb_fsi.vpi_cbSector ; write last byte?
        JB      SHORT bufw7                     ; No, go exit.
bufw6:  call    PlaceBufLRU             ; move to head of "free" list
bufw7:  call    RlseBuffr
        Assert  ISVPB,<ES,BP>,"BufWrt/EvenRD"
        CLC
bufwx:  mov     si,sp                   ;remove es,bp,si,TCBNextAdd,TCBSecPos
        lea     sp,[si]+(SIZE bufwframe)  ;use lea to preserve flags
        TaskContext     DS
        ASSUME  DS:NOTHING
ifdef FSSTRICT
        pushf                           ; j200324 preserve carry flag
        call    checkfat
        popf
endif
        RET                             ; EXIT

;       bufwag = bufw again
;       Since we got here, either the user segment isn't accessable, or
;       isn't present.  Release the buffer we hold, restore entry conditions
;       to variables, and call ProbeBuff.  If we got an access error, then
;       ProbeBuff will report it.  If we got a not present fault, ProbeBuff
;       will suck in the segment we want.  Retry or report error depending
;       on result from ProbeBuff.
;
;       (BX:SI) -> transfer address
;       (CX)    =  length

bufwag: call    RlseBuffr               ; release buffer
        mov     dh,0                    ; need read segment access
        SaveReg <cx>                    ; ProbeBuff trashes cx
        call    ProbeBuff               ; check access, force presence
        RestoreReg <cx>                 ; CX = byte count
        jc      SHORT bufwx             ; report error

;       Restore for retry

        mov     di,CurrTCB

        .errnz  bufwf_TCBNA-bufwf_TCBSP2-2
        pop     ss:[di].TCBNextAdd

        .errnz  bufwf_TCBSP2-bufwf_TCBSP-2
        pop     word ptr ss:[di].TCBSecPos.HighWord

        .errnz  bufwf_TCBSP-bufwf_si-2
        pop     word ptr ss:[di].TCBSecPos.LowWord

        .errnz  bufwf_si-bufwf_bp-2
        pop     si

        .errnz  bufwf_bp-bufwf_es-2
        pop     bp

        .errnz  bufwf_es
        pop     es

        jmp     bufw0           ; go try operation again

EndProc BufWrt

;***    BigBufRd - Transfer characters from a system big buffer.
;
;       Do a partial 1 - 2 sectors read via one of the system big buffers
;       Algorithm :
;          Figure sectors numbers
;          If not contiguous sectors - fail.
;          Search for the first sector in system buffers
;          If first sector already in system buffer - fail
;          Get Big Buffer
;          If no Big Buffer available - fail.
;          If not integral number of sectors {
;            Get system buffer for the 2nd sector.
;            If it already contains the sector - free big buffer and fail.
;            If can't get unused buffer - free big buffer and fail.
;          }
;          Read integral number of sectors into Big Buffer - save error flag
;          If read error release system buffer, free Big Buffer and fail.
;          If not integral number of sectors {
;            Copy 2nd sector from Big buffer to system buffer.
;            Release system buffer.
;          }
;          Copy data from big buffer to user memory.
;          Free Big Buffer.
;
;       ENTRY   ES:BP points to VPB
;               SI = 0 - normal file system read
;                  = 1 - direct disk access read
;
;       EXIT    Carry set if failed
;
;       USES    AX, BX, CX, DX, DI, SI, DS, Flags
;
;       EFFECTS Switches Context
;

Procedure   BigBufRd,faronly
        ASSUME  DS:NOTHING,ES:NOTHING,SS:TASKAREA

bbr_hVPB equ <bp>

        LocalVar bbr_DirAccess,WORD
        LocalVar bbr_SecPos,DWORD
        LocalVar bbr_LastPos,WORD
        LocalVar bbr_ClusNum,WORD
        LocalVar bbr_SecClusPos,WORD    ; Only low byte is used
        LocalVar bbr_BigBufBit,WORD
        LocalVar bbr_BigBufOff,WORD
        LocalVar bbr_physBufAddr,DWORD

        TaskContext <ds>

        mov     di,CurrTCB

        mov     bl,[di].TCBSecClusPos
        EnterProc <si,[di].TCBSecPos,[di].TCBLastPos,[di].TCBClusNum,bx>

        push    es
        mov     si,[bbr_hVPB]
        les     si,es:[si].vpb_fsi.vpi_pDPB
        test    es:[si].dpb_flags,DPBNONREMOV
        pop     es
        jnz     bbr0
        stc
        jmp     bbrExit
bbr0:

; Check that two contig sectors exist
        mov     si,[bbr_DirAccess]
        push    bp
        mov     bp,[bbr_hVPB]
        CALLFAR NextSec
        pop     bp
        jc      bbrFail
        cmp     [bbr_DirAccess],0       ; is access direct?
        jnz     bbr10                   ; yes, go skip cluster determination
        mov     di,CurrTCB
        mov     dx,[bbr_ClusNum]
        mov     bx,[di].TCBClusNum
        cmp     bx,dx               ; Contiguous sectors (same cluster) ?
        je      bbr5                ; Yes.
        dec     bx
        cmp     bx,dx               ; Contiguous sectors (adjacent clusters) ?
        jne     bbrFail             ; No.
bbr5:
        mov     bx,[bbr_SecClusPos]
        mov     SI,[bbr_hVPB]
        FigRec32 <SI>
        mov     [bbr_SecPos].hiword,si
        mov     [bbr_SecPos].loword,dx
bbr10:
; [SecPos] = Sector number of first sector.
; Check that first sector not already in system buffer.

        BufferContext <ds>

        SearchBuf MRU,[bbr_hVPB],[bbr_SecPos]
        je      bbrFail                 ; First sector already cached

; Get Big Buffer
        mov     ax,ds:[bs_BigBufMap]
        or      ax,ax
        je      bbrFail                 ; No free big buffers.
        mov     di,ds:[bs_BigBufBase]   ; Offset of first buffer
        mov     cx,ds:[bs_MaxSec]       ; Size of sector
        shl     cx,1                    ; cx = size of Big Buffer
        mov     bx,1                    ; First Big Buffer bit
bbr20:  test    bx,ax
        jne     bbr21                   ; Found free big buffer
        add     di,cx
        shl     bx,1
        jmp     bbr20                   ; Loop is finite since ax != 0.
bbr21:
; Found Big Buffer - Mark it used
        mov     [bbr_BigBufBit],bx
        mov     [bbr_BigBufOff],di
;**     mov     cx,bx
        not     bx
        and     ax,bx
        xchg    ax,ds:[bs_BigBufMap]
;**     test    cx,ax
;**     jne     bbrFail                 ; Somebody stole it before we did.

        mov     si,[bbr_hVPB]
        mov     AX,ES:[si].vpb_fsi.vpi_cbSector
        mov     si,TASKAREA:[CurrTCB]
        mov     cx,TASKAREA:[si].TCBBytCnt2
        or      cx,cx
        je      bbr40                   ; One whole sector.
        cmp     cx,ax                   ; Two whole sectors ?
        je      bbr40                   ; Yes.

; Get system buffer for 2nd sector
        push    ecx
        mov     ecx,[bbr_SecPos]
        inc     ecx                     ; ECX = Second sector number.
        SearchBuf MRU,[bbr_hVPB],ECX
        pop     ecx
        je      bbrFFail               ; 2nd sector already cached
        SearchBuf UNUSED
        jne     bbrFFail               ; Unused buffer not found.

        cmp     [di].buf_hVPB,BUF_FREE  ; did buffer become free?
        je      bbr30                   ;   Yes, no need to move it again
        push    bp
        xor     bp,bp
        xchg    bp,[DI].buf_hVPB        ; (BP) = buffer volume, free buffer
        call    DecVPBRef               ; Reduce ref count on VPB
        pop     bp

bbr30:  AND     [DI].buf_flags,BUF_TYPE_0           ; clear out type bits
        MOV     [DI].buf_flags,BUF_BUSY or BUF_ATTEMPTING_READ ; Grab buffer
        MOV     si,TASKAREA:[CurrTCB]               ; (SS:SI) = current TCB
        mov     ax,TASKAREA:[si].TCBNumber          ; (AX) = thread ID
        MOV     [DI].buf_tid,AX                     ; Set thread ID
        OR      TASKAREA:[si].TCBForce_Flg,FF_BUF   ; We own a buffer
        MOV     TASKAREA:[si].TCBCurBuf.Offst,DI    ; Save buffer address
        MOV     TASKAREA:[si].TCBCurBuf.Segmt,DS
        call    PlaceBufMRU                     ; We'll need it for next read.
bbr40:
; Read 1 or 2 sectors into Big Buffer.
        BufferContext <ds>
        mov     bx,[bbr_BigBufOff]      ; DS:BX points to big buffer
        xor     ax,ax                   ; assume normal read
        mov     si,TASKAREA:[CurrTCB]
        test    TASKAREA:[si].TCBfSwapping,SM_TCB_SWAPPING
        jz      bbr50                   ; not swappinc, go do normal read
        mov     al,ReadBypassAdj        ; swapping, so do bypass read
bbr50:  mov     cx,2                    ; Assume 2 sectors read.
        test    TASKAREA:[si].TCBBytCnt2,-1 ; More then one sector
        jnz     bbr52                   ; Yes.
        dec     cx                      ; Only one sector read.
bbr52:  mov     di,[bbr_SecPos].hiword
        mov     dx,[bbr_SecPos].loword  ; DI:DX = First sector number.
        push    bp                      ; Save stack frame
CpuMode 386
        push    eax
        mov     eax,ds:[bs_physBufSeg]  ; Buffer segment physical address
        mov     [bbr_physBufAddr],eax
        movzx   eax,bx                  ; Buffer offset in segment
        add     [bbr_physBufAddr],eax    ; Buffer physical address
        pop     eax
CpuMode Reset
        push    ss
        pop     ds
        lea     bx,[bbr_physBufAddr]    ; ds:bx point to dword phys address
        or      ah,1                    ; Tell DRead we pass physical address
        mov     bp,[bbr_hVPB]
        call    DRead                   ; DRead(al=0 or ReadBypassAdj)
        BufferContext <DS>
        pop     bp                      ; bp = stack frame.
        jc      bbrF2Fail               ; Read error

        mov     si,[bbr_hVPB]
        mov     AX,ES:[si].vpb_fsi.vpi_cbSector
        mov     si,TASKAREA:[CurrTCB]
        mov     cx,TASKAREA:[si].TCBBytCnt2
        jcxz    bbr60                   ; One whole sector.
        cmp     cx,ax                   ; Two whole sectors ?
        je      bbr60                   ; Yes.

; Copy 2nd sector from big buffer to system buffer.
        push    DS
        pop     ES

CpuMode 386
        SaveReg <ecx,esi,edi>

; Set 2nd sector system buffer
        movzx   edi,TASKAREA:[si].TCBCurBuf.Offst
        mov     ecx,[bbr_SecPos]
        inc     ecx                     ; ECX = Second sector number.
        mov     [DI].buf_sector,ECX
        push    bp
        MOV     BP,[bbr_hVPB]           ; (BP) = VPB
        MOV     [DI].buf_hVPB,BP
        call    IncVPBRef
        pop     bp
        and     [di].buf_flags,not BUF_ATTEMPTING_READ
        MOV     [DI].buf_wrtcntinc,0
        add     edi,BUFINSIZ
        movzx   esi,[bbr_BigBufOff]
        cld
        movzx   ecx,ax
        add     esi,ecx                 ; Copy 2nd sector from Big Buffer
        shr     ecx,2
        rep     movsd
        call    RlseBuffr
        RestoreReg <edi,esi,ecx>
CpuMode Reset

bbr60:
; Copy data into user buffer.
        add     cx,ax
        mov     bx,TASKAREA:[si].TCBDMAAdd.Segmt
        mov     di,TASKAREA:[si].TCBNextAdd
        mov     si,[bbr_BigBufOff]
        call    SuBuff
        VPBContext es,,NOCHECK
        jc      bbrFFail
; Free Big Buffer.
        BufferContext <ds>
        mov     ax,[bbr_BigBufBit]
        or      ds:[bs_BigBufMap],ax    ; Free Big Buffer
        TaskContext <ds>
        mov     si,TASKAREA:[CurrTCB]
        mov     TASKAREA:[si].TCBNextAdd,di
        mov     TASKAREA:[si].TCBBytSecPos,0
        clc
        jmp     bbrExit


; Free system buffer (if claimed), free Big Buffer and fail.
; This will work even if we don't have a system buffer (see RlseBuffr)
bbrF2Fail:
        call    RlseBuffr

; Free Big Buffer and fail.
bbrFFail:
        mov     ax,[bbr_BigBufBit]
        BufferContext <ds>
        or      ds:[bs_BigBufMap],ax    ; Free Big Buffer

; Fail - Restore TCB variables
bbrFail:
        TaskContext <ds>
        mov     di,[CurrTCB]

        test    [bbr_DirAccess],-1      ; is access direct?
        stc                             ; Fail indication.
        je      bbrF10                  ; no, restore only cluster info
        mov     ax,[bbr_SecPos].loword
        mov     [di].TCBSecPos.loword,ax
        mov     ax,[bbr_SecPos].hiword
        mov     [di].TCBSecPos.hiword,ax
        jmp     bbrExit
bbrF10:
        mov     ax,[bbr_SecClusPos]
        mov     [di].TCBSecClusPos,al
        mov     ax,[bbr_ClusNum]
        mov     [di].TCBClusNum,ax
        mov     ax,[bbr_LastPos]
        mov     [di].TCBLastPos,ax

bbrExit:
        LeaveProc
        ret

EndProc BigBufRd


HIGH2CODE   ENDS

        FARCODE HIGH4
HIGH4CODE SEGMENT
        ASSUME  SS:TASKAREA

Break   <NextSec -- Compute next sector to read or write>

;***    NextSec - Compute where next sector is
;
;       Compute the address of the next sector to read or write.  If a file
;       system transfer, we step through the fat to find the next sector's
;       address.  If a direct disk transfer, we simply increment the sector
;       number.
;
;       ENTRY   ES:BP point to VPB
;               SI = 0 - normal file system read
;                  = 1 - direct disk access read
;
;       EXIT    'NC'
;                   If file system transfer, will set
;                       SecClusPos
;                       ClusNum
;                       LastPos
;                   If direct disk transfer, will set
;                       SecPos
;
;               'C'
;                   Hit end-of-file on file system transfer
;                   Means nothing for direct access transfer
;
;       USES    AX, BX, DI, SI, Flags
;
;
Procedure   NextSec,<hybrid,far16>
        ASSUME  DS:NOTHING,ES:NOTHING

        Assert      ISVPB,<ES,BP>,"NextSec"
        chkvselhd es,bp
        OR      SI,SI                   ; direct access transfer?
        JZ      SHORT NORNXT            ; no, go do fat comp.

        push    bp
        mov     bp,CurrTCB
        add     word ptr [bp].TCBSecPos.LowWord,1   ; INC does not affect CARRY
        adc     word ptr [bp].TCBSecPos.HighWord,0
        pop     bp
        JMP     SHORT CLRET             ; done

NORNXT:
        push    bp
        mov     bp,CurrTCB
        mov     al,[bp].TCBSecClusPos
        pop     bp
        INC     AL
        CMP     AL,ES:[bp].vpb_fsd.vpdFAT_cluster_mask
        JBE     SHORT SAVPOS
        push    bp
        mov     bp,CurrTCB
        mov     bx,[bp].TCBClusNum
        pop     bp
        call    IsEOF
        JAE     SHORT NONEXT
        call    UnPack
        JC      SHORT NONEXT
        push    bp
        mov     bp,CurrTCB
        mov     [bp].TCBClusNum,di
        inc     [bp].TCBLastPos
        pop     bp
        MOV     AL,0
SAVPOS:
        push    bp
        mov     bp,CurrTCB
        mov     [bp].TCBSecClusPos,al
        pop     bp
CLRET:
        CLC
        RET
NONEXT:
        STC
        RET
EndProc NextSec


Break   <Optimize -- DO A USER DISK REQUEST WELL>

;***    Optimize - allow for maximal length physical transfer
;
;       Optimize determines the maximum number of physical sectors
;       that may be transferred in a single I/O request.  If a transfer
;       crosses a cluster boundary, it will determine if the clusters
;       are contiguous.
;
;       ENTRY   ES:BP point to VPB
;               si == 0 => perform FAT file system read
;                   BX = Physical cluster
;                   CX = No. of sectors
;                   DL = sector within cluster
;                   [NextAdd] = transfer address offset
;               si == 1 => perform direct disk I/O
;                   cx == # of sectors to read
;                   SecPos = position on disk of first sector to transfer
;
;       EXIT    AX = No. of sectors remaining
;               BX = Transfer address offset
;               CX = No. of sectors to be transferred
;               SI:DX = Physical sector address
;               [NextAdd] updated
;               if si == 0 =>
;                   DI = Next cluster
;                   [ClusNum] = Last cluster accessed
;               Carry set if error (currently user FAILed to I 24)
;
;       USES    AX, BX, CX, DX, DI, SI, Flags.
;
;       EFFECTS Switches Context (via UnPack)
;
;  Change history:
;  ---------------
;  74394 Nizinski 10/12/93 - direct i/o algorithm changed to
;                            properly handle overflow after ADD.
;  76756 Nizinski 01/04/94 - subtract 1 from high order sector position when
;                            borrow is necessary (when subtracting 1 from
;                            zero from low order word which results in FFFF).
;
;
;
Procedure   Optimize,NEAR
        ASSUME  DS:NOTHING,ES:NOTHING

        Assert      ISVPB,<ES,BP>,"Optimize"

        push    bp
        mov     bp,ax
        chkvselhd es,bp
        or      si,si                   ; for fat file system?
        jz      SHORT opt1              ; yes, go compute clustering info
;
; Direct I/O transfer.  We make the transfer size EXACTLY what was asked
; for as the device driver can handle it immediately.  There is no
; scattering of the sectors for Direct I/O
;
        push    bp
        mov     bp,CurrTCB
        mov     bx,[bp].TCBNextAdd      ; offset at which to begin transfer
        mov     ax,cx
        pop     bp
        mul     es:[bp].vpb_fsi.vpi_cbSector    ; get total number of bytes to transfer
        mov     bp,CurrTCB
        add     [bp].TCBNextAdd,ax      ; Assume caller truncated
                                            ; request for no boundary problem
        mov     dx,word ptr [bp].TCBSecPos.LowWord
        mov     si,word ptr [bp].TCBSecPos.HighWord ; Get position for next transfer
        add     dx,cx                   ; advance I/O position
        adc     si,0                    ; 74394 add CY into high word
;  The following line is commented out for 74394.
;       sbb     dx,1                    ; remember that NextSec will advance us
        sub     dx,1                    ; 74394 changed not to subtract CY
        sbb     si,0                    ; 76756 adjust for borrow (if any)
                                        ; 76756  if dx was 0 for above sub
        xchg    word ptr [bp].TCBSecPos.LowWord,dx
        xchg    word ptr [bp].TCBSecPos.HighWord,si; Get position into SI:DX and update next pos
        xor     ax,ax                   ; no further sectors to do (CARRY CLEAR)
        pop     bp                      ; Restore BP of entry
        RET

;
; Do the computation for normal file I/O.
;
opt1:   PUSH    DX
        PUSH    BX
        MOV     AL,ES:[bp].vpb_fsd.vpdFAT_cluster_mask
        INC     AL              ; Number of sectors per cluster
        MOV     AH,AL
        SUB     AL,DL           ; AL = Number of sectors left in first cluster
        MOV     DX,CX
;
; AL has number of sectors available in current cluster
; AH has number of sectors available in next cluster
; BX has current physical cluster
; DX has number of sectors left to transfer
; ES:BP points to VPB
; SI has DASD versus FAT-FS flag
;

        mov     cl,al                   ; (cl) = sectors in first cluster
        xor     ch,ch                   ; (cx) = sectors in first cluster
        cmp     cx,dx                   ; Do we have all the sectors we need?
        jae     BLKDON                  ;   yes, we have everything

        mov     al,ah                   ; (al) = sectors per cluster
        xor     ah,ah
        call    CheckExtent             ; (DI) = first cluster of next extent
        mov     ah,al                   ; (BX) = last cluster this extent
                                        ; (CX) = number of sector in extent
        jc      short OP_ERR
        inc     bx
        cmp     cx,dx
        jae     short BLKDON
        dec     bx

FINCLUS:
        push    bp
        mov     bp,CurrTCB
        mov     [bp].TCBClusNum,BX      ; Last cluster accessed
        pop     bp
        SUB     DX,CX                   ; Number of sectors still needed
        PUSH    DX
        MOV     AX,CX
        chkvselhd es,bp
        MUL     ES:[bp].vpb_fsi.vpi_cbSector ; Number of sectors * sector size
        push    bp
        mov     bp,CurrTCB
        mov     si,[bp].TCBNextAdd
        ADD     AX,SI                   ; Adjust by size of transfer
        mov     [bp].TCBNextAdd,ax
        pop     bp
        POP     AX                      ; Number of sectors still needed
        POP     DX                      ; Starting cluster
        SUB     BX,DX                   ; Number of new clusters accessed
        push    bp
        mov     bp,CurrTCB
        add     [bp].TCBLastPos,bx
        pop     bp
        POP     BX                      ; BL = sector postion within cluster
        PUSH    SI
        FigRec32 BP
        POP     BX
        CLC
opx:    pop     bp                      ; Restore BP of entry
        RET

OP_ERR: ADD     SP,4                    ; Clean up stack before return
        STC
        jmp     SHORT opx               ; Restore BP before exit

BLKDON: SUB     CX,DX           ; Number of sectors in cluster we don't want
        SUB     AH,CL           ; Number of sectors in cluster we accepted
        DEC     AH              ; Adjust to mean position within cluster
        push    bp
        mov     bp,CurrTCB
        mov     [bp].TCBSecClusPos,ah
        pop     bp
        MOV     CX,DX           ; Anyway, make the total equal to the request
        JMP     FINCLUS

EndProc Optimize

Break   <FigRec -- Figure sector in allocation unit>

;
;       ENTRY   DX = Physical cluster number
;               BL = Sector postion within cluster
;               ES:BP points to VPB
;
;       EXIT    SI:DX = sector number
;
;       USES    Flags
;
Procedure   FigRec,<hybrid,far16>
        ASSUME  DS:NOTHING,ES:NOTHING

        Assert      ISVPB,<ES,BP>,"FigRec"
        chkvselhd es,bp

        FigRec32 BP
        RET

EndProc FigRec


Break   <Allocate -- Assign disk space>

;***    Allocate - Allocate Disk Space
;***    AllocateEA - Allocate space for extended attributes
;
;       Allocate is called to allocate disk clusters.  The new clusters are
;       FAT-chained together, then tacked onto the end of the existing file.
;
;       The VPB contains the cluster # of the last free cluster allocated
;       (vpb_fsd.vpdFAT_next_free).  We start at this cluster and scan towards higher
;       numbered clusters, looking for the necessary free blocks.
;
;       If we get to the end of the disk without satisfaction:
;
;           IF (vpb_fsd.vpdFAT_next_free == 2) then we've scanned the whole disk.
;               return (insufficient_disk_space)
;           ELSE
;               vpb_fsd.vpdFAT_next_free = 2; start scan over from the beginning.
;
;       Note that there is no multitasking interlock.  There is no race
;       when examining the entrys in an in-core FAT block since there
;       will be no context switch.  When UnPack context switches while
;       waiting for a FAT read we are done with any in-core FAT blocks,
;       so again there is no race.  The only special concern is that V2
;       and V3 MSDOS left the last allocated cluster as "00"; marking
;       it EOF only when the entire alloc request was satisfied.  We
;       can't allow another activation to think this cluster is free,
;       so we give it a special temporary mark to show that it is,
;       indeed, allocated.
;
;       For 12-bit FATs, however, there IS a race condition when doing
;       an UnPack on a cluster entry that spans two sectors.  To prevent
;       this situation, we serialize allocation on 12-bit FAT media by
;       locking the VPB.
;
;       Note that when we run out of space this algorithm will scan from
;       dpb_next_free to the end, then scan from cluster 2 through the
;       end, redundantly scanning the later part of the disk.  This only
;       happens when we run out of space, so sue me.
;
;       ENTRY   BX = Last cluster of file (0 if null file)
;               CX = No. of clusters to allocate
;               ES:BP points to VPB
;               [ThisSFT] = Points to SFT
;               DS = TASKAREA
;
;       EXIT    'C' if error
;                   if disk is full,
;                       AX = ERROR_DISK_FULL
;                       CX = No.of clusters possible to allocate
;               'NC' if space allocated
;                   BX = First cluster allocated
;                   FAT is fully updated
;                   if file
;                       sf_fsd.sfdFAT_firFILEclus field set if file was null
;
;       USES    AX, BX, CX, DX, DI, SI, Flags
;
;       EFFECTS Switches Context

;***DT  Allocate - Cluster Allocate internal trace points.
;
;    TRACE MINOR = <RAS_PRE_INT_ALLOCATE>,
;          TP    = .Allocate,
;          TYPE  = (PRE,INT),
;          GROUP = FS,
;          DESC  = "(OS) Cluster Allocate Pre-Invocation",
;          REGS  = (CX),
;          FMT   = "Cluster Count Wanted = <FMT_WORD>"
;
;    TRACE MINOR = <RAS_POST_INT_ALLOCATE>,
;          TP    = .postAllocate,
;          TYPE  = (POST,INT),
;          GROUP = FS,
;          DESC  = "(OS) Cluster Allocate Post-Invocation",
;          REGS  = (CX),
;          FMT   = "Cluster Count Obtained = <FMT_WORD>"

Procedure   Allocate,NEAR

        ASSUME  DS:NOTHING,ES:NOTHING

Alloc_hVPB  equ [bp]

LocalVar        Alloc_Type,WORD         ; File or EA allocation
LocalVar        Alloc_Chain,WORD
LocalVar        Alloc_Start,WORD        ; start point for FAT CLUSTER search

        xor     si,si                   ; This is an allocation for a file
        jmp     SHORT ads03

Entry   AllocateEA
        mov     si,1                    ; This is an allocation for EA

ads03:
;       Maj/Min = 3201H, Data = cx (cluster count)
        MMPHPreClusterAllocate              ; MMIOPH Performance Hook: 3201H
                                            ; Data = cx (cluster count)

    ASSUME DS:TASKAREA, ES:NOTHING, SS:TASKAREA
        SegAssert SS,<DS>,"Allocate"
        Assert      ISVPB,<ES,BP>,"Allocate"
        DBPRT   40h,40h,<Allocate 40:40 - LasClu=$x cnt=$d\n>,<bx,cx>

        chkvselhd es,bp
        EnterProc <si>                  ; Initialize Type

        PUSH    CX
        PUSH    BX

        xor     DX,DX
        mov     Alloc_Chain,DX          ; clear beginning of chain variable
        mov     si, Alloc_hVPB          ; es:si -> VPB

;*  Serialize VPB, if allocating on 12-bit FAT

        cmp     es:[si].vpb_fsd.vpdFAT_FATentrysize,16 ; 16-bit FAT?
        jne     ads10             ;   No, go serialize
CpuMode 386
        mov     eax,es:[si].vpb_fsd.vpdFAT_pClusBitMap
        or      eax,eax

; #86745 fix begin ...

        jnz     ads05
        SaveReg <ds,di>
        lds     di,es:[si].vpb_fsi.vpi_pDPB
        test    ds:[di].dpb_flags,DPBNONREMOV
        RestoreReg <di,ds>
        jz      ads15                    ; Removable Media, do the old way

;; 106225  Should not use Cache stuff and FlushBuf when this code
;; is hit for locking a drive.
        SaveReg <ds,di>                        ;106225
        lds     di,es:[si].vpb_fsi.vpi_pDPB    ;106225 Get ptr to DPB.
        cmp     ds:[di].dpb_drive_lock,0       ;106225 Check if dpb_drive_lock
        RestoreReg <di,ds>                     ;106225 contains a PID.
        jnz     ads15                     ;106225 locked Drive, do the old way
;;106225 end fix

        SaveReg
        push    bx
        mov     bx,si                    ; si=hVPB
        call    UseCache
        pop     bx
        jc      ads15

        ;Call flushbuf so all changes to the FAT are on the disk for
        ; rebuilding the bitmap.
        mov     ax, Alloc_hVPB          ; #98594
        SaveReg <cx,es,di,si,bx>        ; #98594
        CALLFAR FlushBuf                ; #98594
        RestoreReg <bx,si,di,es,cx>     ; #98594

        and     es:[si].vpb_fMisc, not VPB_FM_INITCACHE_DONE
        SaveReg <cx,bx>
        call    Init_VPB_Cache
        RestoreReg <bx,cx>
        jc      short ads04
        mov     eax,es:[si].vpb_fsd.vpdFAT_pClusBitMap
        or      eax,eax
        jz      ads15
        jmp     short ads05
ads04:
        xor     eax,eax
        mov     es:[si].vpb_fsd.vpdFAT_pClusBitMap,eax
        jmp     ads15
ads05:

; #86745 fix end ...

        SaveReg <es,ds>
        FlatContext <ds,es>
        mov     ax,Alloc_Type
        CCALL32 AllocCluster,,nocall
        RestoreReg <ds,es>
    ASSUME DS:TASKAREA, ES:NOTHING
        jmp     comm_ret
CpuMode reset

ads10:  SaveReg <bp>
        mov     ax,si                   ; (ax) = VPB handle
        mov     bl,VPBALLOCATE          ; (bl) = VPB allocation lock bit
        CALLFAR WaitVPB                 ; Wait for VPB allocate bit to become 0
        or      es:[si].vpb_flags,VPBALLOCATE ; Set VPB allocate bit
        RestoreReg <bp>

ads15:  mov     bx,es:[si].vpb_fsd.vpdFAT_next_free
        mov     Alloc_Start,bx
        cmp     bx,2
        ja      SHORT ads20
        DBPRT   40h,40h,<next_free=$x >,<bx>

;   couldn't find enough free space beyond vpdFAT_next_free, or
;   vpdFAT_next_free is <=2 or >vpdFAT_max_cluster.  Reset it and restart
;   the scan

ads18:  mov     Alloc_Start,2
        mov     bx,1                    ; Counter next instruction so first
                                        ;       cluster examined is 2

;   Scanning forward for a free cluster
;
;       (BX) = forwards scan pointer
;       (CX) = clusters remaining to be allocated
;       (DX) = current last cluster in chain
;       (TOS) = last cluster of file

ads20:  INC     BX
        mov     si, Alloc_hVPB
        CMP     BX,ES:[si].vpb_fsd.vpdFAT_max_cluster
        ja      ads70                   ; at end of disk

        push    bp
        mov     bp,si                   ; (BP) = VPB handle
        call    UnPack                  ; check out this cluster
        pop     bp
        jc      ads50                   ; FAT error             [INTERR?]
        jnz     SHORT ads20             ; not free, keep on truckin

;   Have found a free cluster.  Add it to the chain
;
;       (BX) = found free cluster #
;       (DX) = current last cluster in file

        push    bp
        mov     bp, Alloc_hVPB
        mov     es:[bp].vpb_fsd.vpdFAT_next_free,bx        ; next time start search here
        xchg    ax,dx                   ; save (DX) in ax
        mov     dx,1                    ; mark this free guy as "1"
        call    Pack                    ; set special "temporary" mark
        pop     bp
        jc      ads50                   ; FAT error             [INTERR?]
        mov     si, Alloc_hVPB
        CMP     ES:[si].vpb_fsd.vpdFAT_free_cnt,-1 ; Free count valid?
        JZ      SHORT ads30             ; No
        DEC     ES:[si].vpb_fsd.vpdFAT_free_cnt    ; Reduce free count by 1
ads30:  xchg    ax,dx                   ; (DX) = current last cluster in file
        DBPRT   40h,40h,< Chain $x onto $x (remcnt=$d-1) >,<bx,dx,cx>
        or      dx,dx                   ; previous cluster zero?
        jz      SHORT ads40             ; yes, don't overwrite FAT byte
        XCHG    BX,DX
        MOV     AX,DX
        push    bp
        mov     bp,si                   ; (BP) = VPB handle
        call    Pack                    ; link free cluster onto file
        pop     bp
        jc      SHORT ads50             ; FAT error             [INTERR?]

        test    [Alloc_Type],-1
        jnz     SHORT ads35             ; Don't update SFT for EA allocation

        SaveReg <ax,bx>
        mov     bx,Alloc_hVPB
        call    UseCache                ; Can we use the cache for this vol ?
        RestoreReg <bx,ax>
        jc      ads35

        SaveReg <ax>
        mov     ax,Alloc_hVPB           ; (AX) = VPB handle
        call    WriteFATOrdered         ; Write FAT in correct order
        RestoreReg <ax>

ads35:  xchg    BX,AX                   ; (BX) = last one we looked at
        jmp     SHORT ads45
ads40:  mov     Alloc_Chain,bx          ; save first cluster of chain

        test    [Alloc_Type],-1
        jnz     SHORT ads45             ; Don't update SFT for EA allocation

        SaveReg <es,di,ax>
        mov     di,CurrTCB
        les     di,TASKAREA:[di].TCBThisSFT
        Assert  ISSFT,<ES,DI>,"Allocate/ads40"
        CALLFAR GetMRUBufSec
        MOV     ES:[DI.sf_fsd.sfdFAT_LastFATSec],ax
        RestoreReg <ax,di,es>

ads45:  mov     dx,bx                   ; (DX) = current end of file
        dec     cx
        jz      ads46
        jmp     ads20                   ; alloc more if needed

;   We've successfully extended the file.  Clean up and exit
;
;       (BX) = last cluster in file

ads46:  MOV     DX,0FFFFH
        push    bp
        mov     bp,Alloc_hVPB           ; (BP) = VPB handle
        call    Pack                    ; mark last cluster EOF
        pop     bp

        pushf
        test    [Alloc_Type],-1
        jnz     SHORT ads47             ; Don't update SFT for EA allocation
        SaveReg <ax,bx>
        mov     bx,Alloc_hVPB
        call    UseCache                ; Can we use the cache for this vol ?
        RestoreReg <bx,ax>
        jc      ads47

        SaveReg <ax>
        mov     ax,Alloc_hVPB           ; (AX) = VPB handle
        call    WriteFATOrdered         ; Write FAT in correct order
        RestoreReg <ax>
ads47:  popf

;   Note that FAT errors jump here to clean the stack and exit.  this saves us
;   2 whole bytes.  Hope its worth it...
;
;       'C' set iff error
;       calling (BX) and (CX) pushed on stack

ads50:  POP     BX
        POP     CX              ; Don't need this stuff since we're successful
        jc      adserr
        mov     ax,Alloc_Chain          ; (ax) = first cluster allocated
;
; The allocation when fine. If we were allocating for a file, then we
; need to update the cluster information in the SFT and, for existing files,
; also have to link in the new chain to the old.
; For extended attribute allocations, we do not update the SFT.
;
        test    [Alloc_Type],-1
        jnz     SHORT adsret            ; Don't update SFT for EA allocation
        OR      BX,BX                   ; first allocation to file?
        jz      SHORT ads60             ; yes
        mov     dx,ax
        push    bp
        mov     bp,Alloc_hVPB           ; (BP) = VPB handle
        call    Pack                    ; add new chain to file
        pop     bp

        jmp     SHORT adsret
    ASSUME DS:TASKAREA, ES:NOTHING
;
;       We were doing the first allocation for a new file.  Update
;       the SFT cluster info
;
ads60:  PUSH    ES
        mov     di,CurrTCB
        les     di,TASKAREA:[di].TCBThisSFT
        Assert  ISSFT,<ES,DI>,"Allocate/ads50"
        MOV     ES:[DI.sf_fsd.sfdFAT_firFILEclus],AX
        MOV     ES:[DI.sf_fsd.sfdFAT_lstclus],AX

        POP     ES
adsret: mov     bx,ax
        jmp     SHORT comm_ret


;**     we're at the end of the disk, and not satisfied.  See if we've
;       scanned ALL of the disk...

ads70:  cmp     Alloc_Start,2
        DBPRT   40h,40h,< == At END.  started at $x >,<es:[vpb_fsd.vpdFAT_next_free]>
        jnz     ads18                   ; start scan from front of disk

;       Sorry, we've gone over the whole disk, with insufficient
;       luck.  Lets give the space back to the free list and tell
;       the caller how much he could have had.  We have to make sure
;       we remove the "special mark" we put on the last cluster we
;       were able to allocate, so it doesn't become orphaned.
;
;       (CX) = clusters remaining to be allocated
;       (TOS) = last cluster of file (before call to Allocate)
;       (TOS+1) = # of clusters wanted to allocate

        mov     bx,Alloc_Chain          ; get start of allocated chain
        or      bx,bx                   ; any allocated?
        jz      SHORT ads80             ; no
        push    bp
        mov     bp,Alloc_hVPB           ; (BP) = VPB handle
        call    Release
        pop     bp
ads80:  POP     BX                      ; (BX) = last cluster of file
        POP     AX                      ; No. of clusters requested
        SUB     AX,CX                   ; AX=No. of clusters allocated
        xchg    ax, cx                  ; CX=No. of clusters allocated
        mov     ax, ERROR_DISK_FULL
;       DBPRT   <>,<>,<"disk full in Allocate\n">
adserr: STC

comm_ret:

;*  Release VPB, if allocating on 12-bit FAT

        pushf
        mov     si,Alloc_hVPB           ; (BP) = VPB handle
        cmp     es:[si].vpb_fsd.vpdFAT_FATentrysize,16 ; 16-bit FAT?
        je      SHORT ads81             ;   YES, no lock to release

        SaveReg <ax,bx>
        mov     ax,si                   ; (ax) = VPB handle
        mov     bl,VPBALLOCATE          ; (bl) = VPB allocation lock bit
        CALLFAR WakeVPB                 ; Release VPB allocation lock
                                        ; (WakeVPB clears bit)
        RestoreReg <bx,ax>

ads81:  popf

;       Maj/Min = 3281H, Data = cx (cluster count)
        MMPHPostClusterAllocate         ; MMIOPH Performance Hook: 3281H
                                        ; Data = cx (cluster count)

Entry   postAllocate,near               ; DynTrc Tracepoint Location

        LeaveProc
        RET

EndProc Allocate

Break   <WriteFATOrdered -- Write FAT In Correct Order>

;**     WriteFATOrdered -- Write FAT In Correct Order
;
;       Make sure that if last FAT sector which was used to allocate
;       space for a file is written to disk if it is not the same as the
;       previous last sector.
;
;       ENTRY   AX = hVPB
;
;       EXIT    NONE
;
;       USES    Flags
Procedure WriteFATOrdered,<hybrid,far16>
LocalVar hVPB,WORD

        EnterProc <ax>

        SaveReg <ax,es,di,ds,dx,cx,si,bx,bp>
        mov     di,CurrTCB
        les     di,TASKAREA:[di].TCBThisSFT
        Assert  ISSFT,<ES,DI>,"WriteFATOrdered"
        CALLFAR GetMRUBufSec
        cmp     ax,es:[DI.sf_fsd.sfdFAT_LastFATSec]
        MOV     ES:[DI.sf_fsd.sfdFAT_LastFATSec],ax
        je      wrfo3

        mov     si,hVPB
        VPBContext es,si                ; es:[si]->VPB
        cmp     word ptr es:[si.vpb_fsd.vpdFAT_dir_sector],ax
        jbe     wrfo3

        MOV     DX,AX
        XOR     AL,AL
        MOV     SI,1
        SaveReg <bp>
        mov     bp,hVPB
        VPBContext es,bp
; NOTE: It is OK on current systems to zero out the high word of the FAT
; sector since the FAT resides at the beginning of the disk. This may change.
        XOR     CX,CX                   ; CX:DX = sector for FAT
        SaveReg <dx,bp>
        CALLFAR GetBuffrB
        RestoreReg <cx,dx>
        RestoreReg <bp>
        jc      wrfo3
        mov     di,CurrTCB
        lds     di,TASKAREA:[di].TCBCurBuf
    ASSUME      DS:NOTHING

ifdef FSSTRICT
        cmp     word ptr [di].buf_sector+2,0
        jne     ie_mc                   ; high sector is bad
        cmp     word ptr [di].buf_sector,dx
        jne     ie_mc                   ; low sector is bad
        cmp     [di].buf_hVPB,cx
        je      wrfo1                   ; This is the buffer we wanted
ie_mc:
        InternalError <WriteFATOrdered - Invalid buffer from GetBuffrB>
wrfo1:
endif   ; FSSTRICT

        test    [di].buf_flags,BUF_DIRTY ; Is buffer dirty?
        jz      wrfo2                   ;   no,
        SaveReg <bp>
        mov     bx,1                    ; don't discard buffer
        CALLFAR BufWrite
        RestoreReg <bp>

        FlatContext <ds,es>
        movzx   eax,hVPB
        CCALL32  _BufFlushFATDir,<eax>

wrfo2:  CALLFAR RlseBuffr

wrfo3:  RestoreReg <bp,bx,si,cx,dx,ds,di,es,ax>
        LeaveProc
        ret
EndProc WriteFATOrdered

Break   <Release -- DEASSIGN DISK SPACE>

;**     Release -- Deassign disk space
;**     RelBlks -- Deassign disk space, mark EOF
;
;       Frees cluster chain starting with [BX].  The RelBlks entry
;       will mark the first cluster with EOF if entered with BX = -1.
;
;       We release the chain until it points to a free cluster,
;       reaches a cluster marked EOF, or reaches a cluster marked
;       "01", indicating an allocation still in progress.
;
;       ENTRY   BX = Cluster in file
;               ES:BP points to VPB
;               DX = [0,-1]; entry to set in first cluster if RelBlks called.
;
;       EXIT    Carry set if error (currently user FAILed to I 24)
;
;       USES    AX, BX, DX, DI, Flags

;***DT  preClusterRelease - Cluster Release pre-invocation internal trace point
;
;    TRACE MINOR = <RAS_PRE_INT_RELEASE>,
;          TP    = .preRelease,
;          TYPE  = (PRE,INT),
;          GROUP = FS,
;          DESC  = "(OS) Cluster Release Pre-Invocation",
;          REGS  = (BX),
;          FMT   = "Cluster Release = <FMT_WORD>"

Procedure   Release,<HYBRID,FAR16>
        ASSUME  DS:NOTHING,ES:NOTHING
        chkvselhd es,bp

        XOR     DX,DX

Entry   RelBlks
        Assert      ISVPB,<ES,BP>,"RelBlks"

;       Maj/Min = 3202H, Data = bx (file cluster)
        MMPHPreCulsterDeallocate        ; MMIOPH Performance Hook: 3202H
                                        ; Data = bx (file cluster)

;   Enter here with DX=0FFFFH to put an end-of-file mark in the first cluster
;   and free the rest in the chain.

    ASSUME DS:TASKAREA

        SegAssert SS,<DS>,"RelBlks"

Entry   preRelease                      ; pre-invocation Dynamic Trace
        call    UnPack
        LJBE    rel2                    ; j if carry or zero

CpuMode 386
        SaveReg <esi>
        OR      DX,DX
        LJNZ    rel0                    ; Was putting EOF mark
        cmp     es:[bp].vpb_fsd.vpdFAT_FATentrysize,16 ; 16-bit FAT?
        LJNE    rel0                    ; no,
        mov     esi,es:[bp].vpb_fsd.vpdFAT_pClusBitMap
        or      esi,esi

; #985594 (expansion of #86745) fix begin ...
; This fix will cause the bitmap to be built if the drive is eligible
;  and it does not exist.  I also converted a number of the jumps to
;  the macro that will conditionally make it a long jump if needed.

        LJNZ     rel02
        SaveReg <ds,di>
        lds     di,es:[bp].vpb_fsi.vpi_pDPB
        test    ds:[di].dpb_flags,DPBNONREMOV
        RestoreReg <di,ds>
        LJZ     rel0                     ; Removable Media, do the old way

;; 106225  Should not use Cache stuff and FlushBuf when this code
;; is hit for locking a drive.
        SaveReg <ds,di>                        ;106225
        lds     di,es:[bp].vpb_fsi.vpi_pDPB    ;106225 Get ptr to DPB
        cmp    ds:[di].dpb_drive_lock,0        ;106225 Check if dpb_drive_lock
        RestoreReg <di,ds>                     ;106225 contains a PID.
        LJNZ     rel0                          ;106225 Locked Drive, do the old way

;;106225 fix end

        push    bx
        mov     bx,bp                    ; bp=hVPB
        call    UseCache
        pop     bx
        LJC     rel0

        ;Call flushbuf so all changes to the FAT are on the disk for
        ; rebuilding the bitmap.
        mov     ax, bp                  ; #98594
        SaveReg <cx,es,di,si,bx>        ; #98594 Warning- Flushbuf can zero
        CALLFAR FlushBuf                ; #98594 BX which will cause Pack
        RestoreReg <bx,si,di,es,cx>     ; #98594 to fail.

        and     es:[bp].vpb_fMisc, not VPB_FM_INITCACHE_DONE
        SaveReg <cx,bx,si>
        mov     si,bp
        call    Init_VPB_Cache
        RestoreReg <si,bx,cx>
        LJC     rel01
        mov     esi,es:[bp].vpb_fsd.vpdFAT_pClusBitMap
        or      esi,esi
        LJZ     rel0
        jmp     short rel02
rel01:
        xor     esi,esi
        mov     es:[bp].vpb_fsd.vpdFAT_pClusBitMap,esi
        jmp     short rel0
rel02:

; #98594 fix end ...

        SaveReg <bp,es,ds,di,dx>
        FlatContext <ds,es>
        mov     di,bp
        CCALL32 FreeCluster,,nocall
        RestoreReg <dx,di,ds,es,bp>
rel0:   RestoreReg <esi>
    ASSUME DS:TASKAREA, ES:NOTHING
CpuMode reset

        MOV     AX,DI
        PUSH    DX
        call    Pack
        POP     DX
        LJC     rel2
        OR      DX,DX
        LJNZ    rel1                               ; Was putting EOF mark
        chkvselhd es,bp
        CMP     ES:[bp].vpb_fsd.vpdFAT_free_cnt,-1 ; Free count valid?
        LJZ     rel1                               ; No
        INC     ES:[bp].vpb_fsd.vpdFAT_free_cnt    ; Increase free count by 1
rel1:
        MOV     BX,AX
        DBPRT   40h,40h,<REL$x >,<ax>
        dec     ax                      ; check for "1"
        LJZ     rel2
        call    IsEOF
        LJB     Release           ; Carry clear if JMP not taken
;
; Common return point.
;
rel2:
;       Maj/Min = 3282H, Data = none

        MMPHPostCulsterDeallocate       ; MMIOPH Performance Hook: 3282H
                                        ; Data = none

ret12:  RET
EndProc Release
Break   <AllocateEAVL -- Assign disk space for EAs under a volume alloc lock>
;***    AllocateEAVL - call allocateea under a vol.alloc lock
;
;       this takes a share volume alloc lock, calls AllocateEA and then
;       releases the volume lock. see note in Allocate header
;
;       ENTRY   BX = Last cluster of file (0 if null file)
;               CX = No. of clusters to allocate
;               ES:BP points to VPB
;               [ThisSFT] = Points to SFT
;               DS = TaskArea
;
;       EXIT    'C' if error
;                   if disk is full,
;                       AX = ERROR_DISK_FULL
;                       CX = No.of clusters possible to allocate
;               'NC' if space allocated
;                   BX = First cluster allocated
;                   FAT is fully updated
;                   if file
;                       sf_fsd.sfdFAT_firFILEclus field set if file was null
;
;       USES    AX, BX, CX, DX, DI, SI, Flags
;
;       EFFECTS Switches Context

Procedure       AllocateEAVL,NEAR
        push    cx
        mov     cx,VPB_ALLOC_SHARE
        CALLFAR Volume_Alloc_Lock
        pop     cx
        call    AllocateEA
        push    cx
        mov     cx,VPB_ALLOC_SHARE
        CALLFAR Volume_Alloc_Unlock
        pop     cx
        ret
EndProc AllocateEAVL

Break   <AllocateHotFix -- Assign disk space for HotFixing
;***    AllocateHotFix - call allocateea
;
;       calls AllocateEA
;
;       ENTRY   BX = Last cluster of file (0 if null file)
;               CX = No. of clusters to allocate
;               ES:BP points to VPB
;               [ThisSFT] = Points to SFT
;               DS = TaskArea
;
;       EXIT    'C' if error
;                   if disk is full,
;                       AX = ERROR_DISK_FULL
;                       CX = No.of clusters possible to allocate
;               'NC' if space allocated
;                   BX = First cluster allocated
;                   FAT is fully updated
;                   if file
;                       sf_fsd.sfdFAT_firFILEclus field set if file was null
;
;       USES    AX, BX, CX, DX, DI, SI, Flags
;
;       EFFECTS Switches Context

Procedure       AllocateHotFix,<hybrid,far16>
        call    AllocateEA
        ret
EndProc AllocateHotFix

Break   <ReleaseVL -- DEASSIGN DISK SPACE under a volume alloc lock>

;**     ReleaseVL -- Deassign disk space
;
;       This takes an exclusive volume alloc lock and calls release
;       does a flushbuf for the volume and then releases the volume alloc
;       lock
;
;       ENTRY   BX = Cluster in file
;               ES:BP points to VPB
;               DX = [0,-1]; entry to set in first cluster if RelBlks called.
;
;       EXIT    Carry set if error (currently user FAILed to I 24)
;
;       USES    AX, BX, DX, DI, Flags

Procedure       ReleaseVL,NEAR
        rvl_VPB equ     [bp]            ; caller's bp = hVPB
LocalVar        rvl_errCode,WORD        ;
        push    cx
        mov     cx,VPB_ALLOC_EXCL
        CALLFAR Volume_Alloc_Lock
        pop     cx
        call    Release
        EnterProc       <ax>
        pusha
        jc      rvl10

; flush buffers only if release succeeded

        mov     ax,rvl_VPB      ; ax = hVPB
        CALLFAR FlushBuf
        jnc     rvl10

; If flushbuf fails, return flushbuf's error code instead of release's error
; code
        mov     rvl_errCode,ax  ;

rvl10:  mov     cx,VPB_ALLOC_EXCL
        CALLFAR Volume_Alloc_Unlock
        POPA
        mov     ax,rvl_errCode
        LeaveProc
rvlx:   ret

EndProc ReleaseVL



Break   <GetEOF -- Find the end of a file>

;***    GetEOF -- Find the end of a file
;
;       An allocation chain is traversed until an error occurs or EOF
;       is reached.
;
;       ENTRY   ES:BP point to VPB
;               BX = Cluster in a file
;               DS = TASKAREA
;
;       EXIT    'NC' if success
;                   BX = Last cluster in the file
;               'C' if error (currently user FAILed to I 24)
;
;       USES    BX, DI, SI, Flags
;
;       EFFECTS Switches Context
;
Procedure   GetEOF,NEAR
        ASSUME  DS:NOTHING,ES:NOTHING

        Assert      ISVPB,<ES,BP>,"GetEOF"
        chkvselhd es,bp
        call    UnPack
        jc      SHORT geofx     ; No more clusters. EOF.
        PUSH    BX
        MOV     BX,DI
        call    IsEOF
        POP     BX
        JAE     SHORT geofx     ; Carry clear if jmp
        MOV     BX,DI
        JMP     SHORT GetEOF

geofx:  RET

EndProc GetEOF

HIGH4CODE ENDS
        END
