PAGE    ,132
TITLE   FATEA.ASM - low level routine for FAT EA support
NAME    FATEA

SCCSID  equ     "@(#)fatea.asm  6.7 92/03/18"

;*** Low level extended attribute support for the FAT file system.
;
;       See v12 DCR 508 for more info.
;
; BUGBUG philipl 02-Jun-1989: The handle arithmetic in this file
; depends on clusters being < 64K bytes long.  All of the math
; is based on word values.  Things will go quickly to hell if
; this assumption is wrong.
;
; MODIFICATION HISTORY:
;
;      11/27/95 - MLM defect 72417
;                 moved from PTDA to TCB:
;                 Sattrib, Curr_Dir_End, WFP_Start, WFP_Path_End

.xlist
include fnofs.inc
include dosseg.inc
.xcref
include basemaca.inc
include osmaca.inc
include oldmaca.inc
include dossymc.inc
include dpb.inc
include argframe.inc
include buffer.inc
include curdir.inc
include dirent.inc
include error.inc
include extattr.inc
include fatea.inc
include fsc.inc
include filemode.inc
include filesys.inc
include tkexport.inc
include mft.inc
include vmowner.inc
include mi.inc
include opncreat.inc
include protmode.inc
include seldesc.inc
include virtsym.inc
include sft.inc
include vpb.inc
include osconst.inc
include pgexport.inc
include vmexport.inc
include codelock.inc
.cref
.list
        ModName , FNO_FATEA_ASM

NOINTERRPRT     equ     1       ; BUGBUG: shrinks code to fit in HIGH4CODE

                                        ;72417 removed PTDA entries now
                                        ;      in TCB

        ReferTask       CurrTCB,WORD    ; Current thread control block
        ReferTask       ptda_handle,WORD

        ReferGDT        GDT_VPB,WORD    ; VPB BMP Selector

        EXTRN           AllocateEAVL:NEAR
        EXTRN           DOS_OpenCreate:NEAR
        EXTRN           DOS_Read:NEAR
        EXTRN           DOS_Write:NEAR
        EXTRN           DirFromSFT:NEAR
        EXTRN           DoDiskWrite:NEAR
        EXTRN           Pack:NEAR
        EXTRN           ReleaseVL:NEAR
        EXTRN           SetupForIO_EA:NEAR
        EXTRN           UseCache:NEAR

        EXTRNFAR        FSD_Commit
        EXTRNFAR        FindPathEnd
        EXTRNFAR        FreeTempBuffer
        EXTRNFAR        FStrCpy
        EXTRNFAR        GetCDSFromDrv
        EXTRNFAR        GetTempBuffer
        EXTRNFAR        MFTclose
        EXTRNFAR        ProcRun
        EXTRNFAR        ProcBlock
        EXTRNFAR        SegAlloc
        EXTRNFAR        SegFree
        EXTRNFAR        SFFromSFNNoRefChk
        EXTRNFAR        SFLinkFree
        EXTRNFAR        SFNAlloc
        EXTRNFAR        sft_lseek

        EXTRN32         CodeLockProc
        EXTRN32         CodeUnlockProc
        EXTRNF32        MFT_Enter
        EXTRN32         _BufFlushFATDir

        EXTRNFAR        CMKill
        EXTRNFAR        CMShrink
        EXTRNFAR        CMSkip
        EXTRNFAR        EndTransfer
        EXTRNFAR        FigRec
        EXTRNFAR        FlushBuf
        EXTRNFAR        FNotify_Handle_Wake
        EXTRNFAR        FSD_FlushBuf
        EXTRNFAR        GetBuffr
        EXTRNFAR        PackName
        EXTRNFAR        RlseBuffr
        EXTRNFAR        ShSU
        EXTRNFAR        SKPCLP
        EXTRNFAR        HogEAFile                                 ;  76830
        EXTRNFAR        IncVPBRef                                 ;  76830


;BUGBUG put this here instead of FATEA.INC because I am unable
;to delta FATEA.INC. move to FATEA.INC at first oportunity

EA_FIRSTHANDLE  equ     1               ; first EA handle in file


DATA segment

EAFile          db      "A:\EA DATA. SF",0      ; WFP Fat EA file for drive 0
cbEAFile  = $ - EAFile                          ; length of EA file name
public EADirName
EADirName       db      "EA DATA  SF"           ; EA file name in Dir entry

DATA ends

CHECKSFT        macro
ifdef FSSTRICT
        call    IsSFT
endif
        endm

          FARCODE HIGH4
FATEASWAPCODE SEGMENT


;***    EASerialize - enter EA serialization
;
;       EASerialize is called when a thread is about to enter
;       protected parts of EA access code.  There are two types
;       of protection, one for read and one for write.  Read
;       protection is shared.  There can be multiple readers in
;       the code at a time.  Write access is exclusive.  Only
;       one writer at a time is allowed access to the EA code.
;
;       With multiple readers allowed, the possiblity of writer
;       starvation was high so special measures were taken to
;       insure writers an opportunity.
;
;       When a writer calls EASerialize, if no other writer
;       is waiting, it takes the EAChanging flag.  When this
;       flag is taken, no more writers will be allowed to
;       enter serialization.  Once all of the current readers
;       have exited, the writer will be allowed to continue.
;       When the writer completes, the waiter with the highest
;       priority (reader or writer) will be allowed to enter
;       serialization.
;
;       Note: this routine may block.
;       NOTE: FATEASWAPCODE must be locked by the caller of this routine.
;
;       ENTRY   DS:BX -> VPB for volume being serialized
;               AX - serialization type (EA_READ or EA_WRITE)
;
;       EXIT    thread has entered the requested EA serialization.
;
;       USES    none (including flags)
;
;       ALGORITHM
;
;       EASerialize
;         while (EAChanging)
;           EAWait
;         if (IsWriter)
;           EAChanging++
;           while (EAReaders)
;             EAWait
;         else
;           EAReaders++

Procedure EASerialize,near
        assume  ds:nothing, es:nothing

eas10:  test    [bx].vpb_fsd.vpdFAT_eaflags,eavpb_changing ; Is write serialize taken?
        jz      eas20                   ;   no, go get serialization

        call    EAWait                  ; wait for the writer to complete
        jmp     short eas10             ; try it again

eas20:  cmp     ax,EA_WRITE             ; Is this a WRITE serialize?
        jne     eas40                   ;   no, go do read serialize.

        or      [bx].vpb_fsd.vpdFAT_eaflags,eavpb_changing  ; Get write serialization

eas30:  cmp     [bx].vpb_fsd.vpdFAT_eareaders,0 ; Are there any reads in progress?
        je      easx                    ;   no, no need to wait

        call    EAWait                  ; wait for the readers to complete
        jmp     short eas30             ; try it again

eas40:  inc     [bx].vpb_fsd.vpdFAT_eareaders   ; bump count, reader in serialized zone
easx:   ret

EndProc EASerialize


;***    EAUnserialize - exit EA serialization
;
;       EAUnserialize is called when a thread exits the protected
;       parts of EA access code.  There are two types of exits,
;       one for read and one for write.  Each decrements a separate
;       counter, but both check to see if any thread need to be
;       woken up.  If they do, the thread calls WakeEAWaiters to do so.
;
;       See EASerialize for details of the EA serialization algorithm.
;
;       NOTE: FATEASWAPCODE must be locked by the caller of this routine.
;
;       ENTRY   DS:BX->VPB
;               AX - serialization type (EA_READ or EA_WRITE)
;
;       EXIT    thread wakes waiters as necessary
;
;       USES    none (including flags)
;
;       ALGORITHM
;
;       EAUnserialize
;         if (IsReader)
;           EAReaders--
;         else
;           EAChanging--        /* if (IsWriter && EAReaders) internal error */
;         if (!EAReaders && EAWaiters)
;           WakeEAWaiters

Procedure EAUnserialize,near
        assume  ds:nothing, es:nothing

        cmp     ax,EA_READ              ; Is this a READ unserialize?
        jne     eau10                   ;   no, go do write unserialize.

        dec     [bx].vpb_fsd.vpdFAT_eareaders   ; Were we the last reader?
ifdef FSSTRICT
        js      eauie1                  ; count becomes negative, go panic
endif
        jnz     eaux                    ;   No, no need to wake waiters

        jmp     short eau20             ;   Last reader, see if someone needs
                                        ;   to wake up
eau10:  and     [bx].vpb_fsd.vpdFAT_eaflags,NOT eavpb_changing  ; release serialization

IFDEF FSSTRICT
        cmp     [bx].vpb_fsd.vpdFAT_eareaders,0 ; Are there any reads in progress?
        je      eau20                   ; no readers, everything is OK

        InternalError  <EAUnserialize: number of readers is nonzero>
ENDIF
        ; number of readers is zero, everything is OK.

eau20:  cmp     [bx].vpb_fsd.vpdFAT_eawaiters,0 ; Is there anyone waiting?
        je      eaux                    ; no waiters, no need to wake anyone

        call    WakeEAWaiters           ; wake up any waiters
eaux:   ret

ifdef FSSTRICT
eauie1: InternalError <EAUnserialize: negative>
endif
EndProc EAUnserialize


;***    EAWait - Wait for EA access
;
;       This routine blocks threads on the EA block ID.
;       It increments a count to show that there are threads waiting
;       for access to EAs, but then ProtBlocks the thread until the
;       next time WakeEAWaiters is called.
;
;       ENTRY   DS:BX -> VPB for volume to be serialized.
;
;       EXIT    thread blocked until WakeEAWaiters is called.
;
;       USES    none (including flags)
;
;       ALGORITHM
;
;       EAWait
;         EAWaiters++
;         ProtBlock (EABlockID)
;         EAWaiters--

Procedure EAWait,near
        assume  ds:nothing, es:nothing

        SaveReg <ax,bx,cx,dx,di,ds,es>  ; preserve all registers

        inc     [bx].vpb_fsd.vpdFAT_eawaiters   ; this thread is waiting

        mov     ax,EABlockIDHigh
        mov     bx,EABlockIDLow         ; (ax:bx) = blk key
        mov     di,-1
        mov     cx,di                   ; (di:cx) = never time out
        mov     dh,1                    ; (dh) = NON interruptible
        CALLFAR ProcBlock

        RestoreReg  <es,ds,di,dx,cx,bx,ax>
        dec     [bx].vpb_fsd.vpdFAT_eawaiters   ; this thread is no longer waiting
        ret

EndProc   EAWait


;***    WakeEAWaiters - Wake up threads waiting for EA access
;
;       When the last thread working on EAs exits to find a thread
;       waiting for access, it calls this routine to wake the waiters.
;       This routine will wake all threads (both reading and writing)
;       that are waiting for access to EAs.
;
;       ENTRY   none
;
;       EXIT    all threads waiting on EA access wake
;
;       USES    none (including flags)
;
;       ALGORITHM
;
;       WakeEAWaiters
;         ProcRun (EABlockID)

Procedure WakeEAWaiters,near
        assume  ds:nothing, es:nothing

        pushf
        SaveReg <ax,bx>

        mov     ax,EABlockIDHigh
        mov     bx,EABlockIDLow         ; (ax:bx) = block key
        CALLFAR ProcRun

        RestoreReg  <bx,ax>
        popf
        ret

EndProc   WakeEAWaiters


;**     DiskReadFEAlist
;
;       ENTRY   bx:di -> buffer (must be big enough and valid)
;               es:si -> sft
;               cx     = number of bytes to read
;
;       EXIT    if 'NC'
;                 dx = need EA count
;                 buffer filled in
;               if 'CY'
;                 ax = error code
;
;       USES    ax,bx,cx,dx,si,di,ds,es,flags
;
;       PSEUDO
;
;       if (ea handle in sft is null) then
;         return empty fealist
;       call SetupFatEA
;       call EASerialize (read)
;       call EAHandleToOffset to figure out offset of EA header
;       call GetEADataInfo to sanity check the EA data header
;       call EZ_DLseek to skip EA data header
;       call EZ_DRead to read in fealist
;       call EAUnserialize (read)

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

LocalVar drf_pBuf,DWORD                 ; -> result buffer
LocalVar drf_cbWant,WORD                ; number of bytes to read
LocalVar drf_needEA,WORD                ; need EA
LocalVar drf_pEASFT,DWORD               ; -> of ea file sft
LocalVar drf_hVPB,WORD                  ; hVPB
LocalVar drf_hEA,WORD                   ; ea handle from sft

        EnterProc <bx,di, cx, 0>        ; save parameters; needEA = 0
        SaveReg <ds>

        mov     ax,es:[si].sf_fsd.sfdFAT_EAHandle ; ax = ea handle
        or      ax,ax                           ; null handle?
        LJZ     drf110                          ; yes, return empty fealist

        mov     drf_hEA,ax                      ; save ea handle
        mov     bx,es:[si].sf_fsi.sfi_hVPB      ; bx = hVPB
        mov     drf_hVPB,bx                     ; save for later use

        VPBContext ds,bx
        les     di,ds:[bx].vpb_fsd.vpdFAT_pEASFT ; es:di -> ea file's sft
        mov     drf_pEASFT.Segmt,es
        mov     drf_pEASFT.Offst,di

        mov     ax,EA_READ
        call    SetupFatEA
        LJC     drf90                           ; can't open ea file

        mov     ax,EA_READ
        call    EASerialize                     ; read serialize

        les     di,drf_pEASFT
        mov     bx,[CurrTCB]
        push    TASKAREA:[bx].TCBThisSFT.Segmt
        push    TASKAREA:[bx].TCBThisSFT.Offst
        mov     TASKAREA:[bx].TCBThisSFT.Segmt,es
        mov     TASKAREA:[bx].TCBThisSFT.Offst,di
        mov     di,drf_hVPB
        VPBContext es,di
        mov     ax,drf_hEA
        mov     cx,1
        call    EAHandleToOffset        ; dx:ax=ea offset; cx=ea cluster offset
        jc      drf10                   ; error, restore TCBThisSFT and exit

; sanity check EA data header
; cx = cluster offset
; TCBThisSFT -> ea sft

        SaveReg <dx,ax>
        mov     di,drf_hVPB
        VPBContext es,di                ; es:di -> vpb
        mov     ax,drf_hEA
        call    GetEADataInfo
        RestoreReg <dx,cx>              ; cx:dx = byte offset to eadata header

drf10:  mov     si,[CurrTCB]
        pop     TASKAREA:[si].TCBThisSFT.Offst
        pop     TASKAREA:[si].TCBThisSFT.Segmt
        jc      drf50

        mov     drf_needEA,bx           ; save need ea count

; seek to EA data pass header
; cx:dx = byte offset to eadata header

        add     dx,size EADataHeader    ; skip header
        adc     cx,0                    ; cx:dx = byte offset to eadata
        les     di,drf_pEASFT
        xor     ax,ax                   ; absolute seek
        CALLFAR sft_lseek
        jc      drf10

; read in EA data

        les     di,drf_pEASFT
        mov     cx,drf_cbWant           ; number of bytes to read
        lds     si,drf_pBuf             ; ds:si -> buffer
        call    EZ_DRead                ; read fealist

drf50:  pushf
        SaveReg <ax>
        mov     ax,EA_READ
        mov     bx,drf_hVPB
        VPBContext ds,bx
        call    EAUnserialize
        RestoreReg <ax>
        popf

drf90:  mov     dx,drf_needEA                   ; dx = need EA count
        RestoreReg <ds>
        LeaveProc
        ret

drf110: lds     si,drf_pBuf                     ; ds:si -> buffer
        mov     cx,MINFEALISTSIZE               ; cx = size of EMPTY FEAlist
        mov     ds:[si].feal_cblist.hiword,0
        mov     ds:[si].feal_cblist.loword,cx
        clc                                     ; no error
        jmp     short drf90                     ; exit

EndProc DiskReadFEAlist


;**     SetupFatEA - (create and) open EA file
;
;       If ax = EA_READ, SetupFatEA tries to open the EA file. If the EA file
;       exist, then it verifies the file header and save the handle count in
;       the VPB. If the file doesn't exist it returns ERROR_CANNOT_OPEN_EA_FILE.
;
;       If ax = EA_WRITE, SetupFatEA tries to create the EA file if it doesn't
;       exist already. It then initializes the file by writing a null EA table
;       into it.
;
;       ENTRY   ax = EA_READ or EA_WRITE
;               bx = hVPB
;
;       EXIT    if 'NC' - success
;                 if ea file already exist then ea file is opened and handle
;                 count in vpb updated
;                 if ea file did not exist and EA_WRITE was specified, then
;                 EA file is created, opened, and handle count updated
;
;               if 'CY' - error
;                 ax = ERROR_CANNOT_OPEN_EA_FILE
;                   if ea file did not exist and EA_READ was specified
;                 ax = ERROR_DISK_FULL
;                 etc.
;
;       USES    ax,bx,cx,dx,esi,di,ds,es,flags
;
;       PSEUDO
;
;       if EA file already opened then
;         return
;       EASerialize(write)
;       if EA file already opened then
;         EAUnserialize(write)
;         return
;       EZ_DOpen ea file
;       if error then
;         EAUnserialize(write)
;         if (write)
;           return ERROR_CREATE_EA_FILE
;         else
;           return ERROR_CANNOT_OPEN_EA_FILE
;       if (file created)
;         call GenNullEAFile
;         write null EA file
;         flush EA file
;         call FreeNullEAFile
;       else
;         SegAlloc a buffer
;         read file header into buffer
;         sanity check file header
;         count handle
;         free buffer
;       set flag to indicate EA file is opened
;       EAUnserialize(write)
;
;  Change history:
;  ---------------
;  76830 Skip Nizinski 01/21/94 If the ptr to the EA SFT in the vpb
;                      is zero, it means that the EA FILE either
;                      never existed, or that the user deleted it.
;                      When the SFT is deleted, MFTCLOSE will zero
;                      out the EA SFT ptr in the VPB (see 74088).
;                      When this occurs, the SFT is returned to the
;                      SFT free pool, so we need to allocate a new
;                      SFT for our new EA FILE. We do this by calling
;                      HogEAFile.

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

LocalVar sfe_rdwr,WORD                  ; EA_READ or EA_WRITE
LocalVar sfe_pVPB,DWORD                 ; -> vpb
LocalVar sfe_pSFT,DWORD                 ; -> SFT
LocalVar sfe_pImage,DWORD               ; -> ea file image in memory
LocalVar sfe_open,<size Open3Struc>     ; parameters to DOS_OpenCreate

        CodeLock FATEA_GROUP
        AssertNoBuf                     ; Ensure thread does not already have
                                        ; a buffer. It will cause a deadlock
                                        ; if it does.
        VPBContext ds,bx                                ; ds:bx -> vpb

ifdef FSSTRICT          ;  check EA sft
        cmp     ds:[bx].vpb_FSC.Offst,FSC_LOCAL         ; fat file system?
        LJNE    sfeie1                                  ; no, go panic
endif
        mov     dx,ds:[bx].vpb_fsd.vpdFAT_eaflags       ; ax = eaflags
        test    dx,eavpb_fileopen                       ; EA file opened?
        jz      sfe10                                   ; no, go open it

; if drive door has been opened and closed but the vpb remain unchanged, we
; need to re-open the EA file because the user may have put the
; floppy in another machine and moved the EA file. This makes the EA sft and
; cluster map invalid.
; Before we attempt re-open, we should intialize the sft

        test    dx,eavpb_dooropen                       ; door opened?
        jnz     sfe10                                   ; yes, re-open EA file
        jmp     sfex1

; We now request write serialization. When we return from EASerialize,
; either the EA file is opened (by another process) or we need to open it.

sfe10:  EnterProc <ax, ds,bx>           ; rdwr = ax, pVPB = ds:bx
        mov     ax,EA_WRITE
        call    EASerialize             ; make sure pEASFT won't change
        lds     bx,sfe_pVPB             ; ds:bx -> vpb
        mov     dx,ds:[bx].vpb_fsd.vpdFAT_eaflags       ; dx = eaflags
        test    dx,eavpb_fileopen       ; EA file opened?
        jz      sfe15                   ; no, go open it
        test    dx,eavpb_dooropen       ; door opened?
        jnz     sfe15                   ; yes, go re-open EA file

ifdef FSSTRICT
; since we use write thru, EA file should always be clean. Verify this.

        les     di,ds:[bx].vpb_fsd.vpdFAT_pEASFT        ; es:di -> ea file sft
        test    es:[di].sf_flags,DEVID_FILE_CLEAN       ; EA file is clean?
        LJZ     sfeie2                                  ; no, go panic

        cmp     es:[di].sf_fsd.sfdFAT_EAHandle,0        ; ea file has ea?
        LJNZ    sfeie3                                  ; yes, go panic
endif

        jmp     sfe50                                   ; exit

sfe15:  les     di,ds:[bx].vpb_fsi.vpi_pDPB     ; es:di -> DPB
        mov     al,es:[di].dpb_drive    ; al = 0-based drive number
        add     al,'A'                  ; al = drive letter

; There's no need to free the dummy sft since we need one to pass to
; DOS_OpenCreate
; No need to remove file name from sharer since EZ_DOpen doesn't call the
; sharer.

; the openmode in the sft is already set to EAORmode in HogEAFile
; al = drive letter
; ds:bx -> vpb

        mov     sfe_open.opn3s_Attrs,EAAttr
        DosContext <es>,,noassume
        lea     di,EAFile                       ; es:di -> "A:\EA DATA. SF"
        mov     byte ptr es:[di],al             ; put in correct drive letter
        mov     sfe_open.opn3s_NamePtr.Segmt,es
        mov     sfe_open.opn3s_NamePtr.Offst,di

; compute null EA file size. If eventually we need to create EA file, this
; will cause the open to fail if not enough room is available for the null
; EA file. However, when open failed we still need to remove the 0 size file
; Or is this a bug in DOS_Opencreate?
; ds:bx -> vpb

        mov     ax,1                            ; don't generate EA file
        call    GenNullEAFile                   ; compute null EA file size
        mov     sfe_open.opn3s_Size.loword,cx
        mov     sfe_open.opn3s_Size.hiword,0

        mov     sfe_open.opn3s_Flag,exist_open or nexist_fail
        cmp     sfe_rdwr,EA_WRITE               ; create if not exist?
        jne     sfe20
        mov     sfe_open.opn3s_Flag,exist_open or nexist_create

sfe20:  lds     bx,sfe_pVPB
        les     di,ds:[bx].vpb_fsd.vpdFAT_pEASFT        ; es:di -> ea file sft
; If vpdFAT_pEASFT pointer is zero, call HogEAFile to allocate a new
; SFT which we'll need for calling EZ_DOpen.                          76830
        mov     ax,es                   ; get selector to EA SFT      76830
        or      ax,ax                   ; selector zero?              76830
        jnz     sfe22                   ; not 0. use sft ptr in vpb   76830
; EA SFT does not exit. Call HogEAFile to obtain it and enter         76830
; it in the sharer.                                                   76830
        SaveReg <ax,bx,ds>              ;                             76830
        mov     ax,bx                   ; pass in hVPB                76830
        CALLFAR HogEAFile               ; get a new EA SFT            76830
        jc      sfe60                   ; get out if error.           76830
        RestoreReg <ds,bx,ax>           ;                             76830
        SaveReg <bp,bx>                 ;                             76830
        mov     bp,bx                   ; pass in hVPB                76830
        CALLFAR IncVPBRef               ;                             76830
        RestoreReg <bx,bp>              ;                             76830
        les     di,ds:[bx].vpb_fsd.vpdFAT_pEASFT ; es:di->ea file sft 76830
        mov     sfe_pSFT.Segmt,es       ; save for later use          76830
        mov     sfe_pSFT.Offst,di       ;                             76830
        jmp     SHORT sfe25             ; go open (create) ea file    76830
sfe22:                                  ;                             76830
        mov     sfe_pSFT.Segmt,es       ; save for later use
        mov     sfe_pSFT.Offst,di

; The reason EA file may not be entered into the sharer is in the rare case
; when a user opened the EA file before we do in HogEAFile. Here we try again
; hoping the user has closed the EA file.

        CpuMode 386

        cmp     es:[di].sf_MFT,SF_MFT_NULL ; EA file entered into sharer?
        je      SHORT sfe23             ; no, try entering it into sharer

        test    ds:[bx].vpb_fsd.vpdFAT_eaflags,eavpb_dooropen   ; door opened?
        jz      SHORT sfe25             ; no, leave cluster map alone

        SaveReg <di>
        mov     edi,es:[di].sf_MFT
        FlatContext <ds>,ax             ; ds:edi -> mft
        CALLFAR CMKill                  ; remove possibly invalid cluster map
        RestoreReg <di>

        CpuMode reset

ifdef FSSTRICT
; fill SFT with hopefully invalid values to cause error if used. They should
; never be used because we will re-open the file which should fill in
; valid values.

        mov     ax,-1
        mov     es:[di].sf_fsd.sfdFAT_dirsec.Segmt,ax
        mov     es:[di].sf_fsd.sfdFAT_dirsec.Offst,ax
        mov     es:[di].sf_fsd.sfdFAT_dirpos,al
        mov     es:[di].sf_fsd.sfdFAT_cluspos,ax
        mov     es:[di].sf_fsd.sfdFAT_lstclus,ax
endif

; no need to enter file into sharer -- already done. Just re-open the file
; no need to close the file either -- closing the file is a no-op since the
; file is clean

        jmp     short sfe25             ; go re-open file

; The ea file has not been entered into the sharer; probably it was opened by
; a user as soon as the volume was mounted. We now try again to enter it into
; the sharer. If we fail, we don't open the file to avoid corrupting it.

sfe23:  mov     bx,sfe_pVPB.Offst       ; bx = hVPB
        call    EA_EnterShare           ; get exclusive access to ea file

; bugbug 29-Jun-89 peterwu - should ERROR_EA_FILE_BUSY if error
; bugbug Aug-14-1990 Edd - EA_EnterShare scrogs es:di,ds:... need to restore
        mov     es,sfe_pSFT.Segmt       ; restore from stack frame @002a, Edd
        mov     di,sfe_pSFT.Offst       ; @002a, Edd

        LJC     sfe50                   ; can't do it; return error

sfe25:  lea     bx,sfe_open             ; ss:bx -> open3struc

; we can't just set sfi_mode to open for read in hogeafile because if we need
; to re-open the ea file again, the sfi_mode would be wrong (would be set to
; EAORWmode). So we set here every time we open the ea file.

        mov     es:[di].sf_fsi.sfi_mode,EAORmode
        call    EZ_DOpen                ; open or create the EA file
        LJC     sfe60                   ; can't open/create the file

; we just opened the file for read only. Now change the mode to allow both
; read and write

        lds     si,sfe_pSFT             ; ds:si -> SFT
        mov     ds:[si].sf_fsi.sfi_mode,EAORWmode
        mov     ds:[si].sf_ref_count,1  ; is this necessary?

        cmp     cx,file_exist           ; did the EA file already exist
        LJE     sfe30                   ; yes, go read it in

; We just created the EA file. Write the header, cache, and handle table.

    assume ds:nothing,es:nothing

        xor     ax,ax                   ; ax = generate EA file
        mov     bx,sfe_pVPB.Offst       ; bx = hVPB
        call    GenNullEAFile           ; ds:si -> null EA file image; cx=size
        mov     sfe_pImage.Segmt,ds
        mov     sfe_pImage.Offst,si

; bugbug - should we set JFN_FAIL or not? Or should we copy from open flag?
; if we copy from open flag, would it work if the call comes from a path-based
; API (e.g. DosQPathInfo)

        mov     al,JFN_WRTTHR or JFN_NCACHE
        les     di,sfe_pSFT             ; es:di -> sft
        call    EZ_DWrite               ; write null EA file

; bugbug 29-Jun-89 peterwu - should remove EA file if write error

        jc      sfe27

        mov     bx,sfe_pVPB.Offst       ; bx = hVPB
        lds     si,sfe_pImage
        call    CountHandle             ; count number of handles

sfe27:  pushf
        SaveReg <ax>                    ; preserve error code
        lds     si,sfe_pImage
        call    FreeNullEAFile
        RestoreReg <ax>                 ; ax = error code from EZ_DWrite
        popf
        jc      sfe50

; flush the file

        les     di,sfe_pSFT             ; es:di -> sft
        call    EZ_DCommit

        jc      sfe50                   ; flush error
        jmp     short sfe40

; read in EA file header

; bugbug 29-Jun-89 peterwu - should check EA file size > null EA file size
; if not, return ea_file_corrupt

sfe30:  xor     ax,ax
        mov     bx,size EAFileHeader    ; ax:bx = size
        mov     cx,SA_GDT or (D_DATA0 shl 8)
        mov     si,HF_SWAPPABLE OR HF_NONCONTIG
        mov     di,FSEATMPOWNER
        xor     dx,dx
        CALLFAR SegAlloc                ; ax = handle, bx = selector
        jc      sfe50

        mov     sfe_pImage.Segmt,bx
        mov     sfe_pImage.Offst,0
        les     di,sfe_pSFT             ; es:di -> sft
        mov     cx,size EAFileHeader
        mov     ds,bx
        xor     si,si                   ; ds:si -> buffer
        call    EZ_DRead
        jc      sfe35

; bugbug 29-Jun-89 peterwu - need to verify #bytes read

        les     di,sfe_pImage           ; ds:si -> ea file header
        call    EAFileSane
        jc      sfe35

        lds     si,sfe_pImage           ; ds:si -> ea file header
        mov     bx,sfe_pVPB.Offst       ; bx = hVPB
        call    CountHandle

sfe35:  pushf
        SaveReg <ax>
        mov     ax,sfe_pImage.Segmt     ; ax = segment to free
        CALLFAR SegFree
        RestoreReg <ax>
        popf
        jc      sfe50

; set flag to indicate EA file has been opened

sfe40:  lds     bx,sfe_pVPB             ; ds:bx -> vpb
        or      ds:[bx].vpb_fsd.vpdFAT_eaflags,eavpb_fileopen ; EA file opened
        and     ds:[bx].vpb_fsd.vpdFAT_eaflags,not eavpb_dooropen
                                        ; clear dooropen flag

sfe50:  pushf
        SaveReg <ax>
        mov     ax,EA_WRITE
        lds     bx,sfe_pVPB             ; ds:bx -> vpb
        call    EAUnserialize
        RestoreReg <ax>
        popf

sfex:   LeaveProc
sfex1:  CodeUnlock FATEA_GROUP
        ret

; can't open/create the file, return error

sfe60:  mov     ax,ERROR_CREATE_EA_FILE
        cmp     sfe_rdwr,EA_WRITE               ; did we try to create?
        stc
        je      sfe50                           ; yes
        mov     ax,ERROR_CANNOT_OPEN_EA_FILE    ; no
        jmp     short sfe50

ifdef FSSTRICT
sfeie1: InternalError <SetupFatEA: bad vpb>
sfeie2: InternalError <SetupFatEA: dirty>
sfeie3: InternalError <SetupFatEA: handle>
endif
EndProc SetupFatEA


;**     FileSane - check EA file header
;
;       Check to make sure logtype, formattype, file signature, and log data
;       are all sane.
;
;       ENTRY   es:di -> ea file header
;
;       EXIT    if 'NC' - file is sane
;                 ax preserved
;               if 'CY' - file is insane
;                 ax = ERROR_EA_FILE_CORRUPT
;
;       USES    ax, di, flags

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

        cmp     es:[di].eaf_signature,EA_FSIGNATURE ; correct file signature?
        jne     efs20

        .errnz EA_LOGTYPE
        .errnz eaf_logtype - eaf_formattype - 2
        .errnz ls_clusval1 - ls_cluster1 - 2
        .errnz ls_cluster2 - ls_clusval1 - 2
        .errnz ls_clusval2 - ls_cluster2 - 2
        .errnz ls_cluster3 - ls_clusval2 - 2
        .errnz ls_clusval3 - ls_cluster3 - 2
        .errnz ls_handle   - ls_clusval3 - 2
        .errnz ls_handval  - ls_handle   - 2

        xor     ax,ax
        lea     di,[di].eaf_formattype
        mov     cx,(size LogStruc + size eaf_formattype) / 2
        repe    scasw
        jnz     efs20

        ret

efs20:  mov     ax,ERROR_EA_FILE_CORRUPT
        stc
        ret

EndProc EAFileSane


;**     CountHandle - count EA handles given EA file header
;
;       ENTRY   ds:si -> ea file header
;               bx     = hVPB
;
;       EXIT    if 'NC' - success
;                 vpdFAT_eahandles field updated
;               if 'CY'
;                 ax = ERROR_EA_FILE_CORRUPT
;
;       USES    ax,bx,cx,dx,si,di,ds,es,flags
;
;       PSEUDO
;
;       header & offtable size in cluster = cluster number of first EA data
;       #handles = (header + offtable size in bytes - header size in bytes) / 2

Procedure CountHandle
    assume ds:nothing,es:nothing

ifdef FSSTRICT
        cmp     ds:[si].eaf_signature,EA_FSIGNATURE ; ds:si -> file header ?
        jne     coh20                   ; no, return error
endif

        VPBContext es,bx                ; es:bx -> vpb
        mov     al,es:[bx].vpb_fsd.vpdFAT_cluster_mask ; al=sector/cluster - 1
        xor     ah,ah
        inc     ax                      ; ax = sector/cluster
        mul     es:[bx].vpb_fsi.vpi_cbSector    ; dx:ax = cluster size in bytes
        mul     ds:[si].eaf_basetable   ; dx:ax = size of header & offtable
        sub     ax,size EAFileHeader
        sbb     dx,0                    ; dx:ax = size of offtable in bytes

; divide by two to get size of offtable in words

        shr     dx,1                    ; divide high order word first

; bugbug 06-Jun-89 peterwu - assume size < 64k

        jnz     cohie1
        rcr     ax,1                    ; ax = size of offtable in words

ifdef FSSTRICT
        jc      cohie2                  ; size not divisible by two
endif

        mov     es:[bx].vpb_fsd.vpdFAT_eahandles,ax
        ret

coh20:  mov     ax,ERROR_EA_FILE_CORRUPT
        stc
        ret

cohie1: InternalError <CountHandle: offtable too big>

ifdef FSSTRICT
cohie2: InternalError <CountHandle: uneven>
endif

EndProc CountHandle


;**     EZ_DRead - simplified DOS_Read
;
;       This front-end to DOS_Write takes care of saving and restoring TCB
;       variables.
;
;       ENTRY   es:di -> sft
;               ds:si -> buffer
;               cx     = no. of bytes to read
;
;       EXIT    if 'NC'
;                 cx = no. of bytes read
;               if 'CY'
;                 ax = error code
;
;       USES    ax,bx,cx,dx,si,di,ds,es,flags

Procedure EZ_DRead
    assume ds:nothing,es:nothing,ss:TASKAREA
        CHECKSFT
        mov     dx,ds
        TaskContext <ds>
        mov     bx,[CurrTCB]
        push    TASKAREA:[bx].TCBThisSFT.Segmt
        push    TASKAREA:[bx].TCBThisSFT.Offst
        push    TASKAREA:[bx].TCBThishVPB
        push    TASKAREA:[bx].TCBDMAAdd.Segmt
        push    TASKAREA:[bx].TCBDMAAdd.Offst

        mov     ax,es:[di].sf_fsi.sfi_hVPB      ; ax = hVPB
        mov     TASKAREA:[bx].TCBThishVPB,ax
        mov     TASKAREA:[bx].TCBThisSFT.Segmt,es
        mov     TASKAREA:[bx].TCBThisSFT.Offst,di
        mov     TASKAREA:[bx].TCBDMAAdd.Segmt,dx
        mov     TASKAREA:[bx].TCBDMAAdd.Offst,si

        call    DOS_Read
    assume ds:nothing

        mov     si,[CurrTCB]
        pop     TASKAREA:[si].TCBDMAAdd.Offst
        pop     TASKAREA:[si].TCBDMAAdd.Segmt
        pop     TASKAREA:[si].TCBThishVPB
        pop     TASKAREA:[si].TCBThisSFT.Offst
        pop     TASKAREA:[si].TCBThisSFT.Segmt
        ret

EndProc EZ_DRead


;**     EZ_DCommit - simplified FSD_Commit
;
;       This front-end to FSD_Commit takes care of saving and restoring TCB
;       variables.
;
;       ENTRY   es:di -> sft
;
;       EXIT    if 'NC' - success
;               if 'CY'
;                 ax = error code
;
;       USES    ax,bx,cx,dx,si,di,ds,es,flags

Procedure EZ_DCommit
    assume ds:nothing,es:nothing,ss:TASKAREA
        CHECKSFT
        TaskContext <ds>
        mov     si,[CurrTCB]
        push    TASKAREA:[si].TCBThisSFT.Segmt
        push    TASKAREA:[si].TCBThisSFT.Offst
        push    TASKAREA:[si].TCBThishVPB

        mov     bx,es:[di].sf_fsi.sfi_hVPB      ; bx = hVPB
        mov     TASKAREA:[si].TCBThishVPB,bx    ; for DOS_Commit
        mov     TASKAREA:[si].TCBThisSFT.Segmt,es
        mov     TASKAREA:[si].TCBThisSFT.Offst,di
        mov     ax,FS_COMMIT_ONE                ; commit this file
        CALLFAR FSD_Commit
    ASSUME DS:NOTHING
        mov     si,[CurrTCB]
        pop     TASKAREA:[si].TCBThishVPB
        pop     TASKAREA:[si].TCBThisSFT.Offst
        pop     TASKAREA:[si].TCBThisSFT.Segmt
        ret

EndProc EZ_DCommit


;**     EZ_DWrite - simplified DOS_Write
;
;       This front-end to DOS_Write takes care of saving and restoring TCB
;       variables.
;
;       ENTRY   es:di -> sft
;               ds:si -> data to write
;               cx     = number of bytes to write
;               al     = jfn flags to use (e.g. JFN_WRTTHR, JFN_NCACHE, ...)
;
;       EXIT    if NC
;                 cx = bytes written
;                 sft updated
;               if CY
;                 ax = error code
;
;       USES    ax,bx,cx,dx,si,di,ds,es,flags

Procedure EZ_DWrite
    assume ds:nothing,es:nothing,ss:TASKAREA
        CHECKSFT

; save TCB variables

        mov     dx,ds                   ; ax:si -> data to write
        TaskContext <ds>                ; use DS to reduce code size
        mov     bx,[CurrTCB]
        push    TASKAREA:[bx].TCBThisSFT.Segmt
        push    TASKAREA:[bx].TCBThisSFT.Offst
        mov     ah,TASKAREA:[bx].TCBJfnFlag
        push    ax
        push    TASKAREA:[bx].TCBDMAAdd.Segmt
        push    TASKAREA:[bx].TCBDMAAdd.Offst

; set TCB variables

        mov     TASKAREA:[bx].TCBJfnFlag,al
        mov     TASKAREA:[bx].TCBDMAAdd.Segmt,dx
        mov     TASKAREA:[bx].TCBDMAAdd.Offst,si
        mov     TASKAREA:[bx].TCBThisSFT.Segmt,es
        mov     TASKAREA:[bx].TCBThisSFT.Offst,di
        call    DOS_Write

; restore TCB vars

        TaskContext <ds>                ; use DS to reduce code size
        mov     bx,[CurrTCB]
        pop     TASKAREA:[bx].TCBDMAAdd.Offst
        pop     TASKAREA:[bx].TCBDMAAdd.Segmt
        pop     ax
        mov     TASKAREA:[bx].TCBJfnFlag,ah
        pop     TASKAREA:[bx].TCBThisSFT.Offst
        pop     TASKAREA:[bx].TCBThisSFT.Segmt
        ret

EndProc EZ_DWrite


ifdef FSSTRICT
;***    IsSFT - make sure pointer points to a SFT
;
;       Assume FCB SFTs are valid without looking at the SFN.
;
;       ENTRY   es:di -> sft
;
;       EXIT    none
;
;       USES    flags
;
Procedure IsSFT
    assume ds:nothing,es:nothing

        CodeLock FATEA_GROUP            ; returns with CY if error

        SaveReg <ax,bx,dx>

        mov     ax,es
        mov     dx,di                   ; ax:dx = es:di -> sft
        mov     bx,es:[di].sf_fsi.sfi_selfsfn ; bx = sfn
        test    es:[di].sf_flags,SF_FCB ; FCB SFT?
        jnz     isf20                   ; Yes. Cannot check SFN for validity
        CALLFAR SFFromSFNNoRefChk       ; es:di -> sft
        jnc     isf10
        jnz     isf10
        jmp     short isfie1

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

isf20:  RestoreReg <dx,bx,ax>
        CodeUnlock FATEA_GROUP
        ret

isfie1: InternalError <IsSFT: no>
EndProc IsSFT
endif


;**     GenNullEAFile - generate a null EA file in memory
;
;       The null EA file will be written to a newly created EA file.
;       This null EA file can be a static data area or may be created
;       at run time. The caller need not know. But it should call
;       FreeNullEAFile when it has finished using the null EA file data.
;
;       ENTRY   bx = hVPB
;               ax = 0 generate the null EA file in memory
;                    1 don't genereate null EA file, just return size of null
;                      EA file in cx.
;
;       EXIT    if 'NC' - success
;                 ds:si -> null EA file in memory (if ax = 0 on entry)
;                 cx     = size of null EA file
;               if 'CY' - error
;
;       PSEUDO
;
;       if (cluster size > 64k)
;         panic
;       if (cluster size < ea file header size)
;         panic
;       if (cluster size == header size)
;         filesize = 2 clusters
;       else
;         filesize = 1 cluster
;       Segalloc (filesize)
;       initialize segment

Procedure GenNullEAFile
    assume ds:nothing,es:nothing

LocalVar gne_flag,WORD                  ; 1 = report size only
LocalVar gne_cbFile,DWORD               ; null ea file size
LocalVar gne_cache1,WORD                ; first entry in cache table

        EnterProc <ax>

; compute cluster size in bytes

        mov     di,bx
        VPBContext es,di                ; es:di -> vpb
;       xor     dx,dx                   ; (dx) = 0
;       mov     ax,es:[di].vpb_fsi.vpi_cbSector ; dx:ax = cluster size in bytes
;       mov     cl,es:[di].vpb_fsd.vpdFAT_cluster_shift ; al=sector/cluster - 1
;       shl     ax,cl                   ; (ax) = bytes per cluster
;       adc     dx,0                    ; (dx:ax) = bytes per cluster

        xor     dx,dx                   ; (dx) = 0
        mov     ax,size EAFileHeader+EA_BLOCKSIZE ; (dx:ax) = minimum file size
        call    OffsetToClusSec         ; (ax) = cluster, (dx) = sector
                                        ; (bx) = offset of first byte after file
        or      dx,bx                   ; Do we need to round the cluster up?
        jz      gne05                   ; no, even cluster boundry

        inc     ax                      ; (ax) = number of whole clusters needed
gne05:  mov     gne_cache1,ax           ; save the number of clusters
        mov     bx,ax                   ; (bx) = number of clusters
        mov     ax,es:[di].vpb_fsi.vpi_cbSector ; (ax) = bytes per sector
        mov     cl,es:[di].vpb_fsd.vpdFAT_cluster_shift ; cl = log2 sec/clus
        shl     ax,cl                   ; (ax) = bytes per cluster
        mul     bx                      ; (dx:ax) = number of byte for the file

; allocate memory
; dx:ax = size to allocate

; bugbug 06-Jun-89 peterwu - assume cluster size is less than 64k
; because SegAlloc can't allocate > 64k segment

gne10:  or      dx,dx                   ; size > 64k ?
        jnz     gneie1                  ; yes, go panic

        mov     gne_cbFile.hiword,dx    ; save size of file
        mov     gne_cbFile.loword,ax

        cmp     gne_flag,0              ; generate EA file?
        jnz     gne15                   ; no, just return size of file

        mov     bx,ax
        mov     ax,dx                   ; ax:bx = size of cluster
        mov     cx,SA_GDT or (D_DATA0 shl 8)
        mov     si,HF_SWAPPABLE OR HF_NONCONTIG
        mov     di,FSEATMPOWNER
        xor     dx,dx
        CALLFAR SegAlloc                ; ax = handle, bx = selector
        jc      gne20                   ; can't allocate

    assume ds:nothing,es:nothing

; initialize the the data to be written into the new EA file

        mov     ds,bx                   ; ds = ea file segment
        xor     si,si                   ; ds:si -> beginning of segment

        mov     es,bx                   ; es = ea file segment
        mov     es:[eaf_signature],EA_FSIGNATURE; set the file signature
        mov     di,eaf_formattype       ; es:di -> beginning of the log
        xor     ax,ax                   ; (ax) = 0, clear the log
        mov     cx,(eaf_basetable-eaf_formattype)/2 ; (cx) = count words of log
        rep     stosw                   ; fill log, es:di -> base table
                                        ; initialize base table
        mov     ax,gne_cache1           ; (ax) = cluster offset of first EA data
        mov     cx,EA_BASEENTRIES       ; (cx) = number of entries in base table
        rep     stosw                   ; initialize base table
        mov     bx,gne_cbFile.hiword
        mov     cx,gne_cbFile.loword    ; bx:cx = file size
        sub     cx,size EAFileHeader
        sbb     bx,0                    ; bx:cx = size of offset table in bytes
ifdef FSSTRICT
        jc      gneie3

        cmp     di,size EAFileHeader    ; es:di -> offset table
        jne     gneie4
endif
                                        ; initialize offset table to -1's
        mov     ax,EA_INVALID           ; (ax) = invalid handle
        shr     bx,1                    ; (bx:cx) = offset table size
        rcr     cx,1                    ; (cx) = size of offset table in words
        rep     stosw                   ; initialize the offset table

gne15:  mov     cx,gne_cbFile.loword

gne20:  LeaveProc
        ret

gneie1: InternalError <GenNullEAFile: cluster too big>
ifdef FSSTRICT
gneie3: InternalError <GenNullEAFile: overflow3>
gneie4: InternalError <GenNullEAFile: DI not offset to offset table>
endif

EndProc GenNullEAFile


;**     FreeNullEAFile - remove null EA file created by GenNullEAFile
;
;       ENTRY   ds:si -> null EA file generated by GenNullEAFile
;
;       EXIT    null EA file de-allocated
;
;       USES    ax,bx,cx,dx,si,di,ds,es,flags

Procedure FreeNullEAFile
    assume ds:nothing,es:nothing
        mov     ax,ds
        CALLFAR SegFree
        ret
EndProc FreeNullEAFile


;**     EZ_DOpen - simplified DOS_OpenCreate
;
;       This routine provides a simplified and re-entrant version of
;       DOS_OpenCreate
;
;       ENTRY   es:di -> sft (mode and flags field set)
;               ss:bx -> open3struc with these fields filled in:
;                          flag, attrs, size, NamePtr,
;                          EABufPtr will be set to 0,
;                          FileHandleLockIDPtr will be set to 0
;
; bugbug 21-Jun-89 peterwu - file name in NamePtr is assumed to be cbEAFile
; or shorter due to statically allocated variable on the stack. If necessary,
; we can dynamically allocate the variable on the stack after we figure out
; the file name length.
;
;       EXIT    flags from DOS_OpenCreate
;               if Not Carry
;                   CX = Action taken (FILE_EXIST, FILE_CREATE, FILE_REPLACE)

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

LocalVar ezd_pSFT,DWORD
LocalVar ezd_popen,WORD
LocalVar ezd_WFP,cbEAFile               ; "A:\EA DATA. SF"

        CHECKSFT
        EnterProc <es,di, bx>

; parameters to DOS_Opencreate:
; ss:bx -> open3struc
; wfp_start -> wfp
; Curr_Dir_End
; thiscds (or -1 in low word)
; tcbthissft (mode and flags field set)
; tcbthishvpb
; sattrib
; tcbjfnflag??

; save PTDA variables before we destroy them

        TaskContext <ds>                ; use ds to reduce code size

                                        ;72417 moved from PTDA to TCB
        mov     si,[CurrTCB]                         ;### PTR B720732
        push    TASKAREA:[si].TCBWFP_Start
        push    TASKAREA:[si].TCBWFP_Path_End
        push    TASKAREA:[si].TCBCurr_Dir_End
                                        ;72417 end
        push    TASKAREA:[si].TCBThisCDS.Segmt       ;### PTR B720732
        push    TASKAREA:[si].TCBThisCDS.Offst       ;### PTR B720732
;### PTR B720732        push    [ThisCDS].Segmt
;### PTR B720732        push    [ThisCDS].Offst
                                        ;72417 moved from PTDA to TCB
        push    TASKAREA:[si].TCBSattrib
                                        ;72417 end
        xor     ax,ax
;### PTR B720732        mov     si,[CurrTCB]
        mov     al,TASKAREA:[si].TCBJfnFlag
        push    ax
        push    TASKAREA:[si].TCBThisSFT.Segmt
        push    TASKAREA:[si].TCBThisSFT.Offst
        push    TASKAREA:[si].TCBThishVPB
        push    TASKAREA:[si].TCBThisFSC.Segmt
        push    TASKAREA:[si].TCBThisFSC.Offst

; set up parameters for DOS_OpenCreate
; si = CurrTCB

        mov     bx,ezd_popen            ; ss:bx -> open3struc
        xor     ax,ax
        mov     TASKAREA:[bx].opn3s_FileHandleLockIDPtr.Segmt,ax
        mov     TASKAREA:[bx].opn3s_FileHandleLockIDPtr.Offst,ax
        mov     TASKAREA:[bx].opn3s_EABufPtr.Segmt,ax
        mov     TASKAREA:[bx].opn3s_EABufPtr.Offst,ax
        lds     si,TASKAREA:[bx].opn3s_NamePtr  ; ds:si -> wfp
    assume ds:nothing

        TaskContext <es>,,noassume
        lea     di,ezd_WFP              ; es:di -> ezd_WFP
                                        ;72417 moved from PTDA to TCB
        SetBP_CurrTCB
        mov     [bp].TCBWFP_Start,di    ; [WFP_Start] -> ezd_WFP
        UnSetBP_CurrTCB
                                        ;72417 end
        CALLFAR FStrCpy                 ; copy filename to ezd_WFP
    assume ds:nothing,es:nothing

; set up WFP_Path_End

        TaskContext <es>,,noassume
        lea     di,ezd_WFP              ; es:di -> ezd_WFP
        CALLFAR FindPathEnd             ; es:di-1 -> last '\' in path
ifdef FSSTRICT
        LJC     ezdie1                  ; can't find it?! go panic
endif
                                        ;72417 moved from PTDA to TCB
        SetBP_CurrTCB
        mov     [bp].TCBWFP_Path_End,di ; needed by DOS_Opencreate

        mov     [bp].TCBSattrib,attr_system or attr_hidden
        mov     [bp].TCBCurr_Dir_End,-1 ; too lazy to set it up
        UnSetBP_CurrTCB
                                        ;72417 end

; set up ThisCDS

        mov     al,ezd_WFP              ; al = drive letter
        sub     al,'A'                  ; al = drive number 0 based
        CALLFAR GetCDSFromDrv
        mov     ax,ERROR_INVALID_DRIVE  ; assume error
        jc      ezd10                   ; error, go error exit

; set up other TCB vars

        les     di,ezd_pSFT             ; es:di -> sft
        mov     si,[CurrTCB]
        mov     TASKAREA:[si].TCBThisSFT.Segmt,es
        mov     TASKAREA:[si].TCBThisSFT.Offst,di
        mov     ax,es:[di].sf_fsi.sfi_hVPB
        mov     TASKAREA:[si].TCBThishVPB,ax
        mov     TASKAREA:[si].TCBJfnFlag,JFN_FAIL or JFN_WRTTHR or JFN_NCACHE
        call    DOS_OpenCreate
    assume ds:nothing

; restore PTDA variables. But preserve ax and flags from DOS_OpenCreate

ezd10:  mov     si,[CurrTCB]
        pop     TASKAREA:[si].TCBThisFSC.Offst
        pop     TASKAREA:[si].TCBThisFSC.Segmt
        pop     TASKAREA:[si].TCBThishVPB
        pop     TASKAREA:[si].TCBThisSFT.Offst
        pop     TASKAREA:[si].TCBThisSFT.Segmt
        pop     bx
        mov     TASKAREA:[si].TCBJfnFlag,bl
                                        ;72417 moved from PTDA to TCB
        pop     TASKAREA:[si].TCBSattrib
                                        ;72417 end
        pop     TASKAREA:[si].TCBThisCDS.Offst       ;### PTR B720732
        pop     TASKAREA:[si].TCBThisCDS.Segmt       ;### PTR B720732
;### PTR B720732        pop     [ThisCDS].Offst
;### PTR B720732        pop     [ThisCDS].Segmt
                                        ;72417 moved from PTDA to TCB
        pop     TASKAREA:[si].TCBCurr_Dir_End
        pop     TASKAREA:[si].TCBWFP_Path_End
                                        ;72417 end

ifdef FSSTRICT                          ; see if WFP_Start changed
        pushf
        lea     bx,ezd_WFP
                                        ;72417 moved from PTDA to TCB
        cmp     TASKAREA:[si].TCBWFP_Start,bx
        jne     ezdie2                  ; changed, go panic
        popf
endif

        pop     TASKAREA:[si].TCBWFP_Start
                                        ;72417 end

        LeaveProc
        ret

ifdef FSSTRICT
ezdie1: InternalError <EZ_DOpen: no end>
ezdie2: InternalError <EZ_DOpen: WFP_Start changed>
endif

EndProc EZ_DOpen

;***    EAHandleToOffset - convert the EAHandle to a logical byte offset
;
;       EAHandleToOffset reads the EABaseTable and EAOffTable from
;       the file, adds and converts the cluster offsets into a logical
;       byte offset in the file.
;
;       NOTE: This routine is closely related to FixBaseOffTables.
;       Much of the code is the same and the routines should probably
;       be merged sometime in the future.  Bugs in one may well be
;       bugs in the other.
;
;       ENTRY   [TCBThisSFT] -> SFT for "EA DATA. SF"
;               ES:DI -> VPB
;               CX -  !0 get cluster and byte offset, 0 get only cluster offset
;               AX -  handle
;
;       EXIT    'NC' if success
;                   CX - logical cluster offset of the EAData
;                   if CX != 0 on entry
;                     DX:AX - logical byte offset of the EAData
;
;               'C' if error
;                   AX = error code
;
;       USES    AX, BX, CX, DX, SI, DS, Flags
;
;
;       ALGORITHM
;
;       EAHandleToOffset
;         if (handle < cEAHandles)
;           compute the sector with the handle
;           call GetEAFileSector to get the handle table sector
;           call RlseBuffr to free the buffer after reading it
;           if (offset table entry valid)
;             call GetEAFileSector to get the base table sector
;             call RlseBuffr to free the buffer after reading it
;             Offset = (EABaseTable[handle mod 256] + EAOffTable[handle])
;             if (byte offset requested)
;               call ClusterToBytes to convert offset into bytes
;           else
;             return (ERROR_INVALID_EA_HANDLE)
;         else
;           return (ERROR_INVALID_EA_HANDLE)

Procedure EAHandleToOffset,near
        assume  ds:nothing, es:nothing

LocalVar    hto_VPB,DWORD               ; VPB for volume of interest
LocalVar    hto_Handle,WORD             ; handle to be gotten
LocalVar    hto_GetBytes,WORD           ; word flag, get the offset in bytes
LocalVar    hto_HOffInSec,WORD          ; offset of the handle in sector
LocalVar    hto_Cluster,WORD            ; cluster offset of EAData

        CodeLock FATEA_GROUP

        EnterProc   <es,di,ax,cx>

        cmp     ax,es:[di].vpb_fsd.vpdFAT_eahandles ; past end of Offset table?
        LJAE    htoerr                  ;   Yes, return error

; figure out which sector and where in the sector the offset block is.

        shl     ax,1                    ; (ax) = byte offset into handle table
        add     ax,size EAFileHeader    ; (ax) = byte offset into file
        xor     dx,dx                   ; (dx:ax) = byte offset of handle
        div     es:[di].vpb_fsi.vpi_cbSector; (ax) = sector, (dx) = off in sec
        mov     [hto_HOffInSec],dx      ; save the offset of handle in sector
        xor     dx,dx                   ; (dx:ax) = sector in file for handle
        xor     bh,bh                   ; (bx) = (bl)
        mov     bl,es:[di].vpb_fsd.vpdFAT_cluster_mask ; (bl) = mask
        inc     bx                      ; (bx) = sectors per cluster
        div     bx                      ; (ax) = cluster, (dx) = sector

; we are now ready to read the sector.  read it.
; bugbug 20-Jul-89 peterwu - should get SFT from VPB, not TCB

        mov     si,[CurrTCB]            ; ss:si -> TCB
        lds     si,TASKAREA:[si].TCBThisSFT ; ds:si -> SFT for EA file
        mov     cx,ax                   ; (cx) = logical cluster with handle
        mov     ax,[si].sf_fsd.sfdFAT_firFILEclus ; (ax) = 1st cluster EA file
        mov     bx,dx                   ; (bx) = sector in cluster with handle
        xor     dx,dx                   ; (dx) = 0, no physical cluster passed
        les     di,[hto_VPB]            ; es:di -> VPB
        call    GetEAFileSector
        jc      htoerr                  ; error, failed to get cluster

; handle block is now in memory.  prepare to adjust the values in the block.

        mov     di,[CurrTCB]            ; ss:di -> TCB
        les     di,TASKAREA:[di].TCBCurBuf  ; es:di -> sector buffer
        add     di,BUFINSIZ             ; es:di -> beginning of buffer data
        add     di,[hto_HOffInSec]      ; es:di -> handle to read
        mov     si,word ptr es:[di]     ; (si) = value of handle
        mov     [hto_Cluster],si        ; save the handle offset
        CALLFAR RlseBuffr               ; read complete release the buffer
        cmp     [hto_Cluster],EA_INVALID; Is the handle valid?
        je      htoerr                  ; no, bail out

; handle block read.  now read the base table

        mov     si,[CurrTCB]            ; ss:si -> TCB
        lds     si,TASKAREA:[si].TCBThisSFT ; ds:si -> SFT for EA file
        mov     dx,[si].sf_fsd.sfdFAT_firFILEclus ; (dx) = first cluster of EA file
        xor     bx,bx                   ; (bx) = first sector of the file
        les     di,[hto_VPB]            ; es:di -> VPB
        call    GetEAFileSector
        jc      htoerr                  ; error, failed to get cluster

; base table is now in memory.  read the value for our handle.

        mov     di,[CurrTCB]            ; ss:di -> TCB
        les     di,TASKAREA:[di].TCBCurBuf  ; es:di -> sector buffer
        add     di,BUFINSIZ             ; es:di -> beginning of buffer data
        add     di,eaf_basetable        ; es:di -> beginning of base table
        mov     ax,[hto_Handle]         ; (ax) = first handle to change
        shr     ax,EA_HTOBASE           ; (ax) = entry number in base table
        shl     ax,1                    ; (ax) = byte offset in base table
        add     di,ax                   ; es:di -> first handle to adjust
        mov     ax,word ptr es:[di]     ; (ax) = base table entry value
        add     [hto_Cluster],ax        ; get the cluster offset of the EAData
        CALLFAR RlseBuffr               ; got the data, release the buffer

; got the cluster offset, convert it to a byte offset if necessary

        les     di,[hto_VPB]            ; es:di -> VPB
        mov     cx,[hto_Cluster]        ; (cx) = offset in clusters
        cmp     [hto_GetBytes],0        ; Get the offset in bytes?
        je      hto10                   ;   no, just get cluster offset

        call    ClusterToBytes          ; (dx:ax) = offset in bytes
hto10:  clc

htox:   LeaveProc
        CodeUnlock FATEA_GROUP
        ret

htoerr: mov     ax,ERROR_INVALID_EA_HANDLE
        stc
        les     di,[hto_VPB]            ; es:di -> VPB
        jmp     short htox

EndProc EAHandleToOffset


;***    DiskWriteFEAlist - Write out the FEAlist to disk
;
;       DiskWriteFEAlist is called to "replace" the existing FEAlist for
;       an object with the new list. It is important that the whole
;       operation be considered atomic, i.e. if there is any failure, then
;       CHKDSK can correctly complete the transaction or the old FEAlist
;       is not replaced.  Logging at the beginning of "EA DATA. SF" will
;       be set to allow CHKDSK enough information to complete the
;       replacement.
;
;       We achieve this by first writing the new list out to disk
;       in its own cluster chain.  If that was successful, we place
;       into the log at the beginning of "EA DATA. SF" a list of the
;       clusters we are planning to change and information about what
;       handles need to change.  We then change FAT, and update the
;       handle table.  We complete by zeroing out the log and writing
;       out EABaseTable into the file (this is atomic).  After the
;       new information is in place, be release the clusters in the
;       old chain.
;
;       This routine takes care of the case where the original FEAlist does
;       not exist.
;
;
;       ENTRY   ES:SI -> SFT
;               BX:DI -> Buffer with new FEAlist (NewFEA)
;               CX    =  Size of FEAlist to write out (ByteCount)
;               DX    =  Number of NEED EAs in the FEAlist
;               TCBThisSFT set to the SFT for the object being used
;
;       EXIT    'C' if some error
;                   Return error code
;                   Old FEAlist is still valid
;               'NC' if success
;                   SFT updated with new EA cluster number
;                   SFT marked "dirty"
;                   Clusters for old FEAlist released
;                   CX = number of bytes transferred
;
;       USES    All
;
;       ALGORITHM
;
;       DiskWriteFEAlist
;         call SetupFatEA(create flag set)
;         if new EA set size > 4 bytes then
;           call AddEA
;         else
;           call DeleteEA

Procedure DiskWriteFEAlist,near
        assume  ds:nothing, es:nothing

        cmp     cx,MINFEALISTSIZE       ; Will this call write out an EA?
        jbe     dwf10                   ; No EA to write, delete existing EA

        SaveReg <bx,cx,dx,si,di,es>
        mov     ax,EA_WRITE             ; setupfatea for a write
        mov     bx,es:[si].sf_fsi.sfi_hVPB  ; (bx) = hVPB
        call    SetupFatEA              ; open EA file if necessary
        RestoreReg <es,di,si,dx,cx,bx>
        jc      dwfx                    ; error, probably EA file corrupt

        call    AddEA                   ; place the FEAlist in the file
        jmp     short dwfx

dwf10:  SaveReg <bx,cx,dx,si,di,es>
        mov     ax,EA_READ              ; don't create the file, just open it.
        mov     bx,es:[si].sf_fsi.sfi_hVPB  ; (bx) = hVPB
        call    SetupFatEA              ; open EA file if necessary
        RestoreReg <es,di,si,dx,cx,bx>
        jc      dwf20                   ; error, probably EA file not there

        call    DeleteEA
dwfx:   ret

dwf20:  cmp     ax,ERROR_EA_FILE_CORRUPT; Is the EA file corrupt?
        je      dwferr                  ;   yes, big time trouble, return error

        cmp     es:[si].sf_fsd.sfdFAT_EAHandle,0; Is the old EA handle = 0?
        jne     dwf30                   ; no, SFT indicates EAS, none exist

        clc
        jmp     short dwfx

dwf30:  mov     ax,ERROR_EA_FILE_CORRUPT; The EA file is corrupt
dwferr: stc
        jmp     short dwfx

EndProc DiskWriteFEAlist


;***    AddEA - replace the EA currently in the "EA DATA. SF"
;
;       AddEA is a worker for DiskWriteFEAlist.
;
;       This routine does everything necessary to place a new
;       FEAlist into the "EA DATA. SF".  If necessary, it will
;       allocate a new handle and place it in the SFT.
;
;       The work this routine does is recoverable.  All changes
;       are made in a manner that allows the transaction to complete
;       or be entirely voided if the system crashes during this
;       routine.
;
;       For details of the recovery mechanism see DiskWriteFEAlist
;       and SwapClusterChain.
;
;       This routine takes care of the case where the original
;       FEAlist does not exist.
;
;
;       ENTRY   ES:SI -> SFT
;               BX:DI -> Buffer with new FEAlist (NewFEA)
;               CX    =  Size of FEAlist to write out (ByteCount)
;               DX    =  Number of NEED EAs in the FEAlist
;               TCBThisSFT set to the SFT for the object being used
;
;       EXIT    'C' if some error
;                   Return error code
;                   Old FEAlist is still valid
;               'NC' if success
;                   SFT updated with new EA handle number
;                   SFT marked "dirty"
;                   Clusters for old FEAlist released
;                   CX = number of bytes transferred
;
;       USES    All
;
;       ALGORITHM
;
;       AddEA
;         call SetEADataHeader to prepare the header
;         call CreateEAData to put the FEAlist on disk in EAData format
;         call EASerialize (write) to protect EA file access
;         get handle# from sft
;         if handle# is 0 then
;           call AllocEAHandle
;           adjust the EAData header information
;           call WriteEADataHeader to write the new handle to disk
;           if error
;             call Release to free the new EAData
;             call EAUnserialize (write) to allow access to the EA file
;             return error
;         else
;           call GetEADataInfo to get the size of the old EAData
;         call EAHandleToOffset to get the offset where the EAData should go
;         call SwapClusterChain to update EA file
;         if handle# was 0 then
;           call ChangeEAHandle to update the SFT and directory entry
;         call EAUnserialize (write) to allow access to the EA file
;         if old EAData to release
;           call Release to free up the disk space from the old EAData

Procedure AddEA,near
        assume  ds:nothing, es:nothing

LocalVar    aea_ByteCount,WORD          ; Size of transfer
LocalVar    aea_FileSFT,DWORD           ; SFT of file getting EAs added
LocalVar    aea_OldEASize,WORD          ; size of the old EAData in clusters
LocalVar    aea_OldEAChain,WORD         ; First cluster of old EA chain
LocalVar    aea_OldEAHand,WORD          ; EA handle on entry to AddEA
LocalVar    aea_NewEAChain,WORD         ; First cluster of new EA chain
LocalVar    aea_NewClusCount,WORD       ; number of clusters in new chain
LocalVar    aea_VPB,DWORD               ; VPB
LocalVar    aea_EADataHeader,<size EADataHeader>; Buffer for EAHeader

        EnterProc <cx,es,si,0>          ; Initialize local variables

        TaskContext <DS>,,noassume      ; DS = TASKAREA = SS
        lea     ax,[aea_EADataHeader]   ; DS:AX -> aea_EADataHeader
        call    SetEADataHeader         ; set up fields of EADataHeader
        call    CreateEAData            ; create new EAData on disk
        LJC     aeax                    ; error, failed to create new EAData

        mov     [aea_NewEAChain],bx     ; save return values from CreateEAData
        mov     [aea_NewClusCount],cx
        les     si,aea_FileSFT          ; es:di -> SFT for file getting EAs
        mov     bx,es:[si].sf_fsi.sfi_hVPB  ; (bx) = hVPB
        VPBContext ds,bx                ; DS:BX -> VPB
        mov     [aea_VPB].Offst,bx
        mov     [aea_VPB].Segmt,ds
        mov     ax,EA_WRITE
        call    EASerialize             ; get write serialization

        mov     ax,es:[si].sf_fsd.sfdFAT_EAHandle   ; (ax) = old EA handle
        mov     [aea_OldEAHand],ax      ; save the old EA handle
        les     di,[bx].vpb_fsd.vpdFAT_pEASFT   ; es:di -> SFT for "EA DATA. SF"
        mov     si,[CurrTCB]            ; ss:si -> TCB
        mov     TASKAREA:[si].TCBThisSFT.Offst,di
        mov     TASKAREA:[si].TCBThisSFT.Segmt,es   ; ThisSFT -> EA File SFT
        cmp     [aea_OldEAHand],0       ; Need to allocate handle?
        jne     aea10                   ; no, handle already allocted

        call    AllocEAHandle           ; allocate an EA handle
        LJC     aea80                   ; error, clean up and exit

; new handle.  write it into the EAData header and write the header to disk.

        mov     [aea_EADataHeader].ead_owneahand,ax ; save the handle
        mov     cx,[aea_ByteCount]      ; (cx) = number of bytes in FEAlist
        mov     dx,[aea_NewEAChain]     ; (dx) = first cluster of new chain
        les     di,[aea_VPB]            ; es:di -> VPB
        call    UpdateEADataHandle
        LJC     aea80                   ; Error doing write. Free the clusters.

; we have a cluster chain on disk and a handle.  change the EA file.

aea10:  xor     cx,cx                   ; get only the cluster offset
        mov     ax,[aea_EADataHeader].ead_owneahand ; (cx) = handle for EAData
        les     di,[aea_VPB]            ; es:di -> VPB
        call    EAHandleToOffset        ; (cx) = cluster offset of EAData
        LJC     aea80                   ; error, bail out

        cmp     [aea_OldEAHand],0       ; Are we replacing an existing EA?
        je      aea20                   ; no, no need to get info about it

; we are replacing and EAData.  Get the information about it.

        SaveReg <cx>
        mov     ax,[aea_OldEAHand]      ; (ax) = old EA handle
        call    GetEADataInfo           ; (ax) = old EAData size
        mov     [aea_OldEASize],ax      ; save the size of the old EAData
        RestoreReg  <cx>                ; (cx) = cluster offset of this EA data
        LJC     aea80                   ; error, go return it

; we have all the information we need to do the EAData swap.  Go do it.

aea20:  mov     ax,[aea_NewClusCount]   ; (ax) = number of clusters in new chain
        add     ax,cx                   ; (ax) = offset to next EAData
        push    ax                      ; handle for next EAData
        mov     ax,[aea_EADataHeader].ead_owneahand ; (ax) = handle for EAData
        inc     ax                      ; (ax) = handle after new EAData
        push    ax                      ; handle after the new EAData
        push    [aea_OldEASize]         ; number of clusters in old chain
        dec     cx                      ; cluster before EAData
        push    cx                      ; add EAData at correct offset
        push    [aea_NewClusCount]      ; adding 1 cluster of handles
        push    [aea_NewEAChain]        ; first physical cluster of new EAData

        call    SwapClusterChain        ; swap in the cluster chain
        LJC     aea90                   ; error, we're hosed, bigtime!

; new EAData in place.  update the SFT and directory as necessary.

        mov     [aea_OldEAChain],dx     ; save first cluster of old chain

ifdef   EASTRICT
        xor     cx,cx                   ; get only the cluster offset
        mov     ax,[aea_EADataHeader].ead_owneahand ; (cx) = handle for EAData
        les     di,[aea_VPB]            ; es:di -> VPB
        call    EAHandleToOffset        ; (cx) = cluster offset of EAData
        jnc     aea25                   ; error, bail out

        InternalError <AddEA: Handle table broken>

aea25:  mov     ax,[aea_EADataHeader].ead_owneahand ; (cx) = handle for EAData
        call    GetEADataInfo           ; (ax) = old EAData size
        jnc     aea27                   ; error, go return it

        InternalError <AddEA: EAData not correctly entered>

aea27:  cmp     cx,[aea_ByteCount]
        je      aea29

        InternalError <AddEA: EAData not correct size>

aea29:
endif   ; EASTRICT

        mov     bx,[CurrTCB]            ; ss:bx -> TCB
        les     di,[aea_FileSFT]
        mov     TASKAREA:[bx].TCBThisSFT.Offst,di
        mov     TASKAREA:[bx].TCBThisSFT.Segmt,es   ; ThisSFT -> File SFT
        cmp     [aea_OldEAHand],0       ; Did we allocate a handle?
        jne     aea30                   ; no, skip past handle update

        mov     ax,[aea_EADataHeader].ead_owneahand ; (ax) = new EA handle
        call    ChangeEAHandle          ; update handle in SFT and DIR
        jc      aea90                   ; error, return it to the caller

; work requiring serialization complete.  release serialization and clean up.

aea30:  lds     bx,[aea_VPB]
        mov     ax,EA_WRITE
        call    EAUnserialize

; now release the old cluster chain

        mov     bx,[aea_OldEAChain]     ; (bx) = first cluster of old chain
        or      bx,bx                   ; Is there a cluster chain to release?
        jz      aeax                    ; no, just exit

        SaveReg <bp>                    ; Save pointer to stack frame
        mov     bp,[CurrTCB]
        mov     bp,[bp].TCBThishVPB     ; ES:BP -> VPB
        VPBContext ES,BP
        TaskContext <DS>,,NoAssume
        call    ReleaseVL       ; try to release the old cluster chain
        RestoreReg <bp>
        clc                             ; return success
        jmp     short aeax              ; go exit

; error before the cluster swap was attempted.  free the new chain.

aea80:  SaveReg <AX>                    ; Save any error code
        mov     bx,[aea_NewEAChain]
        SaveReg <bp>
        mov     bp,[CurrTCB]
        mov     bp,[bp].TCBThishVPB     ; ES:BP -> VPB
        VPBContext ES,BP
        TaskContext <DS>,,NoAssume
        call    ReleaseVL       ; Release the cluster chain
        RestoreReg <bp>
        RestoreReg <AX>

; error occurred while serialized, release serialization and clean up.

aea90:  SaveReg <ax>                    ; save the error code
        les     di,[aea_FileSFT]        ; es:di -> SFT for file in question
        mov     bx,es:[di].sf_fsi.sfi_hVPB  ; (bx) = hVPB
        VPBContext ds,bx                ; ds:bx -> VPB
        mov     ax,EA_WRITE             ; write unserialize
        call    EAUnserialize           ; release serialization
        mov     si,[CurrTCB]            ; ss:si -> TCB
        mov     ax,[aea_FileSFT].Offst
        mov     TASKAREA:[si].TCBThisSFT.Offst,ax
        mov     ax,[aea_FileSFT].Segmt
        mov     TASKAREA:[si].TCBThisSFT.Segmt,ax ; restore the old TCBThisSFT
        RestoreReg  <ax>                ; (ax) = error code
        stc

aeax:   mov     cx,[aea_ByteCount]
        LeaveProc
        ret


EndProc AddEA


;***    SetEADataHeader - fill in the fields of the EAData header
;
;       This routine fills in the fields of EADataHeader
;
;       ENTRY   ES:SI -> SFT for the file
;               DS:AX -> header buffer
;               DX    =  Number of NEED EAs in the FEAlist
;
;       EXIT    AX = number of clusters necessary to hold input bytes
;
;       USES    FLAGS

Procedure SetEADataHeader,near
        assume  ds:nothing, es:nothing

        SaveReg <ax,bx,cx,si,di,ds,es>
        mov     di,ax
        mov     ds:[di].ead_signature,EA_DSIGNATURE
        mov     ax,es:[si].sf_fsd.sfdFAT_EAHandle   ; Update SFT
        mov     ds:[di].ead_owneahand,ax    ; EA handle that for this EAData
        mov     ds:[di].ead_needeacnt.loword,dx ; # of NEED EAs in FEAlist
        mov     ds:[di].ead_needeacnt.hiword,0  ; # of NEED EAs in FEAlist
        mov     ds:[di].ead_reserved.Offst,0; reserved dword
        mov     ds:[di].ead_reserved.Segmt,0

; pack and copy owner file name from SFT into EADataHeader

        push    ds                      ; DS <=> ES
        push    es
        pop     ds                      ; DS = SFT segment
        pop     es                      ; ES = TASKAREA

        add     si,sf_fsd + sfdFAT_name ; ds:si -> filename in directory format
        add     di,ead_filename         ; es:di -> filename buffer in header
        CALLFAR PackName

        RestoreReg  <es,ds,di,si,cx,bx,ax>
        ret

EndProc SetEADataHeader


;***    CreateEAData - create a cluster chain on disk containing new EAData
;
;       CreateEAData is a worker for AddEA
;
;       This routine allocates a cluster chain on disk and writes
;       the EA header information and the FEAList out to disk.
;
;       ENTRY   ES:SI -> SFT
;               BX:DI -> Buffer with new FEAlist (NewFEA)
;               SS:AX -> Buffer to be used for EADataHeader
;               CX    =  Size of FEAlist to write out (ByteCount)
;               TCBThisSFT set to the SFT for the object being used
;
;       EXIT    'C' if some error
;                   Return error code
;               'NC' if success
;                   BX = first cluster on newly allocated chain
;                   CX = number of clusters in new chain
;
;       USES    All
;
;       ALGORITHM
;
;       CreateEAData
;         compute number of clusters needed to hold EA plus header
;         call AllocateEA to allocate new physical clusters
;         call SetupForIO_EA to prepare to write EAheader to disk
;         call DoDiskWrite to write EAheader into new clusters
;         call SetupForIO_EA to prepare to write FEAlist to disk
;         call DoDiskWrite to write new FEAlist into new clusters
;         call FlushBuf to flush EAheader and new EA clusters

Procedure CreateEAData,near
        assume  ds:nothing, es:nothing

LocalVar    ced_ByteCount,WORD          ; Size of transfer
LocalVar    ced_FEAlist,DWORD           ; Buffer with FEAlist
LocalVar    ced_NewfirEAclus,WORD       ; First cluster of new EA chain
LocalVar    ced_EADataHeader,DWORD      ; Buffer for EAHeader
LocalVar    ced_ClusCount,WORD          ; number of cluster to new EA chain

        EnterProc <cx,bx,di,0,ss,ax>    ; Initialize local variables

        TaskContext <DS>,BX             ; Set DS = TASKAREA for call to AllocateEA
        mov     di,es:[si].sf_fsi.sfi_hVPB
        VPBContext es,di                ; ES:DI -> VPB

        mov     ax,cx                   ; Calculate the number of clusters that
        xor     dx,dx                   ; we will need for FEAlist + header.
        add     ax,SIZE EADataHeader
        adc     dx,0                    ; DX:AX = size of FEAlist + header
        call    ClustersFromBytes       ; AX = number of clusters (rounded up)
                                        ; that the FEAlist will occupy.
        mov     [ced_ClusCount],ax
        xchg    ax,cx                   ; (cx) = number of clusters
        xor     bx,bx                   ; (bx) = 0 => file does not exist
        SaveReg <bp>
        mov     bp,di                   ; ES:BP -> VPB (Required by AllocateEA)
        call    AllocateEAVL            ; Try to allocate the required clusters
        RestoreReg <bp>
        LJC     cedx                    ; Error. Could not allocate space.

        mov     [ced_NewfirEAclus],bx   ; Save first cluster in chain allocated

; EA chain has been allocated.  Set up and write out EAData header.

        mov     si,bx                   ; (si) = first cluster of new chain
        mov     ax,[ced_EADataHeader.Offst]
        mov     dx,[ced_EADataHeader.Segmt]
        call    WriteEADataHeader
        LJC     cederr                  ; Error doing write. Free the clusters.

; Set up and write out FEAlist.

        mov     di,[CurrTCB]
        mov     cx,[ced_FEAlist.Offst]
        mov     TASKAREA:[di].TCBDMAAdd.Offst,cx
        mov     cx,[ced_FEAlist.Segmt]
        mov     TASKAREA:[di].TCBDMAAdd.Segmt,cx; TCBDMAAdd -> FEAlist buffer
        mov     TASKAREA:[di].TCBLockHndl.HighWord,0 ; mark user seg not locked
        mov     TASKAREA:[di].TCBBytSecPos,size EADataHeader; write after header
        mov     TASKAREA:[di].TCBValSec.hiword,0
        mov     TASKAREA:[di].TCBValSec.loword,1; first sector has been written
        les     di,TASKAREA:[di].TCBThisSFT ; ES:DI -> SFT
        mov     cx,[ced_ByteCount]      ; (cx) = number of bytes to write
        SaveReg <bp>
        call    SetupForIO_EA           ; Destroys BP
        RestoreReg <bp>
        mov     si,[ced_NewfirEAclus]   ; SI = first cluster of EA chain
        jc      cederr                  ; Error. Free allocated clusters.

        mov     bx,IOTYPE_EA
        call    DoDiskWrite
        CALLFAR EndTransfer             ; Unlock the transfer segment
        jc      cederr                  ; Error doing write. Free the clusters.

; The FEAlist was successfully written out.

ifdef EASTRICT                          ; very strict EA checking

        mov     di,[CurrTCB]            ; ss:di -> TCB
        les     di,TASKAREA:[di].TCBThisSFT ; es:di -> SFT for EA file
        mov     dx,[ced_NewfirEAclus]
        xor     bx,bx                   ; (bx) = first sector in the cluster
        mov     di,es:[di].sf_fsi.sfi_hVPB
        VPBContext es,di                ; ES:DI -> VPB
        call    GetEAFileSector         ; get the sector
        jnc     ced10                   ; got the sector

        InternalError <CreateEAData: new sector not valid>

ced10:  mov     si,[CurrTCB]            ; ds:si -> TCB
        lds     si,TASKAREA:[si].TCBCurBuf  ; ds:si -> sector buffer
        add     si,BUFINSIZ             ; (si) = offset of sector data in buffer
        add     si,EA_CBLISTOFF         ; (si) -> cblist
        mov     ax,[si]                 ; (ax) = cblist
        cmp     ax,[ced_ByteCount]      ; Is the list the expected size?
        je      ced20

        InternalError <CreateEAData: new EAData not correct size>

ced20:  CALLFAR RlseBuffr

endif   ; EASTRICT

        mov     bx,[ced_NewfirEAclus]   ; (bx) = first cluster of new chain
        mov     cx,[ced_ClusCount]      ; (cx) = number of clusters in chain
cedx:   LeaveProc
        ret

; We release the allocated clusters and return error to the caller.

cederr: SaveReg <AX>                    ; Save any error code
        mov     bx,[ced_NewfirEAclus]
        SaveReg <bp>
        mov     bp,[CurrTCB]
        mov     bp,[bp].TCBThishVPB     ; ES:BP -> VPB
        VPBContext ES,BP
        TaskContext <DS>,,NoAssume
        call    ReleaseVL               ; Release the cluster chain
        RestoreReg <bp>
        RestoreReg <AX>
        STC                             ; Return with CY and error code in AX
        jmp     short cedx

EndProc CreateEAData


;***    WriteEADataHeader - write the EADataHeader to the EA data on disk
;
;       WriteEADataHeader is a worker for AddEA and CreateEAData
;
;       This routine writes out the EADataHeader to the beginning
;       of the newly allocated EA cluster chain.
;
;       ENTRY   DX:AX -> Buffer to be used for EADataHeader
;               SI    =  First cluster of new EA chain
;               TCBThisSFT set to the SFT for the object being used
;
;       EXIT    'C' if some error
;                   AX = Return error code
;               'NC' if success
;                   Header written to disk
;
;       USES    All except BP
;
;       ALGORITHM
;
;       WriteEADataHeader
;         call SetupForIO_EA to prepare to write EAheader to disk
;         call DoDiskWrite to write EAheader into new clusters

Procedure WriteEADataHeader,near
        assume  ds:nothing, es:nothing

        SaveReg <bp>
        TaskContext <DS>,,noassume      ; DS -> TASKAREA for SetupForIO_EA
        mov     bp,[CurrTCB]
        mov     [bp].TCBDMAAdd.Offst,ax
        mov     [bp].TCBDMAAdd.Segmt,dx ; TCBDMAAdd -> EADataHeader buffer
        mov     [bp].TCBLockHndl.HighWord,0 ; mark user segment not locked
        mov     [bp].TCBBytSecPos,0     ; write header at beginning of EA chain
        les     di,[bp].TCBThisSFT      ; ES:DI -> SFT
        mov     cx,size EADataHeader    ; (cx) = number of bytes to write
        SaveReg <si>
        call    SetupForIO_EA           ; Destroys BP
        RestoreReg <si>                 ; SI = first cluster of EA chain
        jc      wedhx                   ; Error, go return it.

        mov     bx,IOTYPE_EA
        call    DoDiskWrite
        CALLFAR EndTransfer             ; Unlock the transfer segment

wedhx:  RestoreReg  <bp>
        ret

EndProc  WriteEADataHeader


;***    UpdateEADataHandle - write the new handle into the EADataHeader
;
;       UpdateEADataHandle is a worker for AddEA
;
;       This routine writes out a new handle into the EADataHeader
;       of a newly allocated EA cluster chain.  It is assumed that
;       this routine will only be called if the handle in the EAData
;       is 0.
;
;       ENTRY   AX = handle number
;               DX = physical cluster number
;               CX = FEAlist size in bytes (used on FSSTRICT kernel only)
;               ES:DI -> VPB
;
;       EXIT    'C' if some error
;                   AX = Return error code
;               'NC' if success
;                   Handle written into header
;
;       USES    All except BP
;
;       ALGORITHM
;
;       UpdateEADataHandle

Procedure UpdateEADataHandle,near
        assume  ds:nothing, es:nothing

        xor     bx,bx                   ; (bx) = first sector in the cluster
        SaveReg <ax,cx>
        call    GetEAFileSector         ; get the sector
        RestoreReg  <cx,bx>             ; (cx) = fealist size, (bx) = handle
ifdef FSSTRICT
        jnc     ueh10                   ; got the sector
        InternalError <UpdateEADataHandle: new sector not valid>
else
        jc      uehx
endif

ueh10:  mov     si,[CurrTCB]            ; ds:si -> TCB
        lds     si,TASKAREA:[si].TCBCurBuf  ; ds:si -> sector buffer
        or      [si].buf_flags,BUF_DIRTY; mark the buffer dirty
        add     si,BUFINSIZ             ; (si) = offset of sector data in buffer
        xchg    bx,[si].ead_owneahand   ; set new handle, (bx) = old handle

ifdef  FSSTRICT
        or      bx,bx                   ; Is the old handle zero?
        jz      ueh20                   ; yes, everything is OK.

        InternalError <UpdateEADataHandle: old handle not zero>

ueh20:  add     si,EA_CBLISTOFF         ; (si) -> cblist
        cmp     cx,[si]                 ; Is the fealist the expected size?
        je      ueh30                   ; yes, everything is OK

        InternalError <CreateEAData: new EAData not correct size>
endif   ; FSSTRICT

ueh30:  CALLFAR RlseBuffr

uehx:   ret

EndProc  UpdateEADataHandle


;***    ClustersFromBytes - get the number of clusters needed to hold X bytes
;
;       This routine calculates the number of clusters that are needed
;       to hold the given number of bytes.
;
;       ENTRY   ES:DI -> file VPB
;               DX:AX = file size
;
;       EXIT    AX = number of clusters necessary to hold input bytes
;
;       USES    AX, CX, DX, FLAGS

Procedure ClustersFromBytes,near
        assume  ds:nothing, es:nothing

; Calculate the number of clusters that we will need for DX:AX bytes. We take
; the DX:AX and divide it by the number of bytes per cluster.

        mov     cx,es:[di].vpb_fsi.vpi_cbSector
        div     cx
        or      dx,dx                   ; Is there a remainder?
        jz      cfb10                   ;   No, no need to round up

        inc     ax

; AX = number of sectors (rounded up) that the FEAlist will occupy.

cfb10:  xor     cx,cx
        mov     dx,cx
        mov     cl, es:[di].vpb_fsd.vpdFAT_cluster_mask ; CX = sec/clus - 1
        inc     cx                      ; CX = sectors/cluster
        div     cx
        or      dx,dx
        jz      cfbx                    ; No need to round up

        inc     ax
cfbx:   ret

EndProc ClustersFromBytes


;***    ClusterToBytes - convert a cluster offset to a byte offset
;
;       This routine converts a cluster offset to a byte offset.
;
;       ENTRY   ES:DI -> file VPB
;               CX = cluster offset
;
;       EXIT    DX:AX = byte offset
;
;       USES    AX, DX, FLAGS

Procedure ClusterToBytes,near
        assume  ds:nothing, es:nothing

        xor     ah,ah
        mov     al,es:[di].vpb_fsd.vpdFAT_cluster_mask  ; (al) = sec/clus - 1
        inc     ax                      ; (ax) = sectors/cluster
        mul     es:[di].vpb_fsi.vpi_cbSector; (ax) = bytes per cluster
        mul     cx                      ; (dx:ax) = byte offset
        ret

EndProc ClusterToBytes


;***    AllocEAHandle - allocate an EA handle
;
;       AllocEAHandle first attempts to find an EA handle in the
;       table in "EA DATA. SF".  If it does not find one, it will
;       grow the table to provide space for a new handle.
;
;       Handle allocation should only fail if the handle table is
;       already at maximum size and full or if the disk is full.
;
;       ENTRY   [TCBThisSFT] -> SFT for "EA DATA. SF"
;               ES:DI -> SFT for "EA DATA. SF"
;               DS:BX -> VPB
;
;       EXIT    'C' if some error
;                   AX = Return error code
;               'NC' if success
;                   AX = new handle
;
;       USES    ALL except BP
;
;       ALGORITHM
;
;       AllocEAHandle
;         call FindFreeEAHandle
;         if handle table is full then
;           if handle table can be grown then
;             call AllocateEA to allocate one physical cluster
;             if successful then
;               set all handles in cluster to invalid
;               call SwapClusterChain to insert new cluster
;               cEAHandles += number of handles per cluster
;             else
;               return ERROR_DISK_FULL
;           else /* handle table reached max size */
;             return ERROR_EA_TABLE_BUSTED

Procedure AllocEAHandle,near
        assume  ds:nothing, es:nothing

LocalVar        aeh_VPB,DWORD           ; pVPB for drive in question
LocalVar        aeh_NewPhysClus,WORD    ; new physical cluster of handles
LocalVar        aeh_NewClusCount,WORD   ; number of new clusters of handles
LocalVar        aeh_NewHandCount,WORD   ; number of new handles

        EnterProc   <ds,bx>

        call    FindFreeEAHandle        ; Are there any free handles?
        LJNC    aehx                    ;   Yes, return the handle

; no free handles left in the file, allocate more handles if possible

        mov     ax,ERROR_EA_TABLE_FULL  ; preset error
        les     di,[aeh_VPB]            ; es:di -> VPB
        cmp     es:[di].vpb_fsd.vpdFAT_eahandles,MAXEAHANDLE; Can we grow table?
        jae     aeherr                  ; no, the table is already full

; we can grow the table.  grow it by max(1 cluster,1 handle block)

        call    CreateNewHandles
        jc      aeherr

        mov     [aeh_NewPhysClus],bx    ; first new cluster in handle chain
        mov     [aeh_NewClusCount],cx   ; number of new clusters of handles
        mov     [aeh_NewHandCount],dx   ; number of new handles

; data initialized.  prepare to insert the new handle cluster.

        mov     di,[CurrTCB]            ; ss:di -> TCB
        les     di,TASKAREA:[di].TCBThisSFT ; es:di -> SFT for EA file
        mov     dx,es:[di].sf_fsd.sfdFAT_firFILEclus ; (dx) = physical cluster
        xor     bx,bx                   ; (bx) = first sector in the cluster
        les     di,[aeh_VPB]            ; es:di -> VPB
        call    GetEAFileSector         ; get the sector
        jc      aeherr

        mov     si,[CurrTCB]            ; ds:si -> TCB
        lds     si,TASKAREA:[si].TCBCurBuf  ; ds:si -> sector buffer
        add     si,BUFINSIZ             ; (si) = offset of sector data in buffer
        mov     ax,[si].eaf_basetable   ; (ax) = offset of first EAData
        CALLFAR RlseBuffr
        mov     cx,ax                   ; (cx) = old offset of first EAData
        add     cx,[aeh_NewClusCount]   ; (cx) = new offset of first EAData

; all parameters ready.  insert the new handle cluster.

        push    cx                      ; handle 0 has offset to first EAData
        push    0                       ; handle 0 is the first one to change
        push    0                       ; delete no clusters from the file
        dec     ax                      ; sector before the addition
        push    ax                      ; add after existing handle table
        push    [aeh_NewClusCount]      ; adding 1 cluster of handles
        push    [aeh_NewPhysClus]       ; first physical cluster of new handles

        call    SwapClusterChain        ; swap in the cluster

; handle table grown.  adjust VPB data and allocate the first handle

        lds     bx,[aeh_VPB]            ; ds:bx -> VPB
        mov     ax,[aeh_NewHandCount]   ; (ax) = number of new handles
        add     ds:[bx].vpb_fsd.vpdFAT_eahandles,ax ; account for new handles

        mov     di,[CurrTCB]
        les     di,TASKAREA:[di].TCBThisSFT ; es:di -> SFT for EA file
        call    FindFreeEAHandle        ; Get one of the free handles

aehx:   LeaveProc
        ret

aeherr: stc
        jmp     short aehx

EndProc AllocEAHandle


;***    CreateNewHandles - create a new cluster chain full of handles
;
;       CreateNewHandles is a worker for AllocEAHandle.
;
;       CreateNewHandles creates a number of clusters full of handles
;       equal to max(1,clusters/handle block).  In most cases, this
;       will be one cluster.
;
;       ENTRY   [TCBThisSFT] -> SFT for "EA DATA. SF"
;               ES:DI -> VPB
;
;       EXIT    'C' if some error
;                   AX = Return error code
;               'NC' if success
;                   BX = first cluster of handle chain
;                   CX = number of clusters of handles
;                   DX = number of handles
;
;       USES    ALL except BP
;
;       ALGORITHM
;
;       CreateNewHandles
;           while ( handles allocated < handles per block)
;             call AllocateEA to allocate one physical cluster
;             if successful then
;               call FillCluster to set all handles in cluster to invalid
;             else
;               return error
;
;           else /* handle table reached max size */
;             return ERROR_EA_TABLE_BUSTED

Procedure CreateNewHandles,near
        assume  ds:nothing, es:nothing

LocalVar        cnh_VPB,DWORD           ; pVPB for drive in question
LocalVar        cnh_FirClus,WORD        ; first cluster in new chain
LocalVar        cnh_ClusCount,WORD      ; number of clusters in new chain
LocalVar        cnh_HCount,WORD         ; number of handles in new chain
LocalVar        cnh_BytesPerClus,WORD   ; number of bytes per cluster

        EnterProc   <es,di,0,0,0>

        mov     ax,es:[di].vpb_fsi.vpi_cbSector ; (ax) = bytes per sector
        mov     cl,es:[di].vpb_fsd.vpdFAT_cluster_shift ; (cl) = log2 sec/clus
        shl     ax,cl                   ; (ax) = bytes per cluster
        mov     [cnh_BytesPerClus],ax   ; save the number of bytes per cluster

cnh10:  TaskContext <DS>,,NoAssume
        mov     cx,1                    ; (cx) = number of clusters
        xor     bx,bx                   ; (bx) = 0 => file does not exist
        SaveReg <bp>
        mov     bp,di                   ; es:bp -> VPB
        call    AllocateEAVL            ; Try to allocate the required clusters
        mov     di,bp                   ; es:di -> VPB
        RestoreReg <bp>
        jc      cnhx                    ; Error. Could not allocate space.

        assume  ds:nothing

; got the space on disk, initialize the data in it

        cmp     [cnh_FirClus],0         ; Is the the first new cluster?
        je      cnh20                   ; no, continue

        TaskContext <DS>,,noassume      ; ds = ss
        mov     dx,[cnh_FirClus]        ; (dx) = beginning of existing chain
        SaveReg <bx>                    ; save the cluster
        call    DoPack                  ; write the FAT entry
        RestoreReg  <bx>                ; (bx) = newly allocated cluster
        jc      cnhx                    ; big trouble.  exit with error.

cnh20:  mov     [cnh_FirClus],bx        ; save cluster number
        inc     [cnh_ClusCount]         ; one more cluster allocated
        mov     ax,EA_INVALID           ; (ax) = fill value
        call    FillCluster             ; fill the cluster with EA_INVALID
        jc      cnhx                    ; failed to fill cluster

        mov     dx,[cnh_HCount]         ; (dx) = previous bytes of handles
        add     dx,[cnh_BytesPerClus]   ; (dx) = current bytes of handles
        cmp     dx,EA_BLOCKSIZE         ; Do we have enough handles yet?
        jae     cnh30                   ; yes, time to exit

        mov     [cnh_HCount],dx         ; save the number of handles
        jmp     short cnh10             ; go get another cluster of handles

cnh30:  mov     cx,[cnh_ClusCount]      ; (cx) = number of clusters in chain
        mov     bx,[cnh_FirClus]        ; (bx) = first cluster in new chain
        shr     dx,1                    ; (dx) = number of handles

cnhx:   LeaveProc
        ret

EndProc CreateNewHandles


;***    FillCluster - fill a cluster with a value
;
;       This routine fills an entire cluster with a value.
;
;       ENTRY   [TCBThisSFT] - SFT for "EA DATA. SF"
;               BX = physical cluster to be filled
;               AX = fill value
;               ES:DI -> VPB
;
;       EXIT    'C' if some error
;                   AX = Return error code
;               'NC' if success
;                   cluster on disk filled with value
;
;       USES    All except BP
;
;       ALGORITHM
;
;       FillCluster
;         for each sector in the cluster
;           call GetEAFileSector to get the sector into a buffer
;           mark the buffer dirty
;           copy fill value into the buffer
;           call RlseBuffr to get the sector placed on disk

Procedure FillCluster,near
        assume  ds:nothing, es:nothing

LocalVar    fcl_VPB,DWORD               ; VPB
LocalVar    fcl_PhysCluster,WORD        ; physical cluster being filled
LocalVar    fcl_FillValue,WORD          ; value to fill the cluster with
LocalVar    fcl_WordsPerSec,WORD        ; number of words per sector
LocalVar    fcl_CurSector,WORD          ; current sector being filled

        EnterProc <es,di,bx,ax>

        xor     ax,ax                   ; (ax) = (al)
        mov     al,es:[di].vpb_fsd.vpdFAT_cluster_mask  ; (ax) = sec / cluster
        mov     [fcl_CurSector],ax      ; sector currently being filled
        mov     ax,es:[di].vpb_fsi.vpi_cbSector ; (ax) = bytes per sector
        shr     ax,1                    ; (ax) = number of words per sector
        mov     [fcl_WordsPerSec],ax    ; save the number of words per sector

fcl10:  mov     dx,[fcl_PhysCluster]    ; (dx) = physical cluster
        mov     bx,[fcl_CurSector]      ; (bx) = sector in that cluster
        les     di,[fcl_VPB]            ; es:di -> VPB
        call    GetEAFileSector         ; get the sector
        jc      fclx                    ; exit with the error

        mov     di,[CurrTCB]            ; ss:di -> TCB
        les     di,TASKAREA:[di].TCBCurBuf ; es:di -> sector buffer
        or      es:[di].buf_flags,BUF_DIRTY ; mark the buffer dirty
        add     di,BUFINSIZ             ; (di) = offset of sector data in buffer
        mov     ax,[fcl_FillValue]      ; (ax) = sector fill value
        mov     cx,[fcl_WordsPerSec]    ; (cx) = words per sector
        rep stosw                       ; copy the value into the buffer
        CALLFAR RlseBuffr               ; put the data on disk

        sub     [fcl_CurSector],1       ; Is there another sector to fill
        jnc     fcl10                   ;   Yes, go fill it

        clc                             ; clear the carry
fclx:   LeaveProc                       ; exit the routine
        ret

EndProc FillCluster


;***    FindFreeEAHandle - search the EAOffTable for a free handle
;
;       FindFreeEAHandle searches EAOffTable in "EA DATA. SF" for
;       an invalid handle.  If it does not find one it will return
;       an error.
;
;       The search for the free handle is done from the end of
;       the EA file to the beginning.  Searches from the end of
;       the handle table are most likely to be fruitful as the
;       sets of new handles are allocated at the end of the table.
;
;       ENTRY   [TCBThisSFT] -> SFT for "EA DATA. SF"
;               ES:DI -> SFT for "EA DATA. SF"
;               DS:BX -> VPB
;
;       EXIT    'C' if some error
;                   AX = Return error code
;               'NC' if success
;                   AX = free handle to be used
;
;       USES    All
;
;       ALGORITHM
;
;       FindFreeEAHandle
;         start with the first sector of last cluster of the EAOffTable
;         do
;           do
;             call FigRec to get the physical sector from logical sector
;             call GetBuffr to get the sector into memory
;             if (first sector of file)
;               skip past the file header and EABaseTable
;             while (not at end of sector) && (handle not free)
;               step to next handle
;             if (free handle not found)
;               advance to the next logical sector
;               call ReleaseBuffr to free the buffer
;           while (free handle not found) && (more sectors in cluster)
;           if (free handle not found)
;              back up to previous cluster of EAOffTable
;         while (free handle not found) && (more cluster in EAOffTable)
;         if (handle found)
;           return handle
;         else
;           return ERROR_NO_FREE_EA_HANDLES

Procedure FindFreeEAHandle,near
        assume  ds:nothing, es:nothing

LocalVar    ffe_EAFileSFT,DWORD         ; sft for the EA file
LocalVar    ffe_curVPB,DWORD            ; VPB
LocalVar    ffe_SkipCount,WORD          ; number of bytes to skip in sector
LocalVar    ffe_FirstClus,WORD          ; first cluster of EA file
LocalVar    ffe_PhysCluster,WORD        ; physical cluster being searched
LocalVar    ffe_CurCluster,WORD         ; current cluster being searched
LocalVar    ffe_CurSector,WORD          ; current sector being searched
LocalVar    ffe_BytesPerSec,WORD        ; number of bytes per sector
LocalVar    ffe_SecPerClus,WORD         ; number of bytes per sector
LocalVar    ffe_OffsetEOF,WORD          ; cluster offset of EOF
LocalVar    ffe_FirstHClus,WORD         ; first cluster with handles
LocalVar    ffe_FirstHSec,WORD          ; first sector with handles
LocalVar    ffe_cbScan,WORD             ; number of bytes scan since backskip
LocalVar    ffe_BackSkip,WORD           ; number of clusters to backskip

        EnterProc <es,di,ds,bx,0>

        mov     ax,ds:[bx].vpb_fsd.vpdFAT_eahandles ; (ax) = number of handles
        or      ax,ax                   ; Are there any handles?
        LJZ     ffeerr                  ;   No, do not bother to search

        mov     cx,es:[di].sf_fsd.sfdFAT_firFILEclus ; (bx) = first cluster of file
        mov     [ffe_FirstClus],cx      ; save the first cluster
        mov     cx,ds:[bx].vpb_fsi.vpi_cbSector; (cx) = bytes per sector
        mov     [ffe_BytesPerSec],cx    ; save the number of bytes per sector
        xor     ch,ch
        mov     cl,ds:[bx].vpb_fsd.vpdFAT_cluster_mask ; (cx) = mask
        inc     cx                      ; (cx) = sectors per cluster
        mov     [ffe_SecPerClus],cx     ; save sectors per cluster

        mov     dx,es:[di].sf_fsi.sfi_size.hiword  ;
        mov     ax,es:[di].sf_fsi.sfi_size.loword  ; (dx:ax) = bytes in the file
        les     di,[ffe_curVPB]         ; es:di -> VPB
        call    OffsetToClusSec         ; (ax) = cluster, (dx) = sector
                                        ; (bx) = offset of first byte after file
        mov     [ffe_OffsetEOF],ax      ; save the cluster offset of EOF
ifdef   EASTRICT
        or      bx,dx                   ; are sector and offset both zero?
        jz      ffe05                   ; yes, EOF is on a cluster boundry

        InternalError <FindFreeEAHandle: EOF is not on cluster boundry>
ffe05:
endif   ;EASTRICT

        mov     ax,es:[di].vpb_fsd.vpdFAT_eahandles ; (ax) = number of handles
        shl     ax,1                    ; (ax) = number of bytes of handles
        add     ax,size EAFileHeader-EA_BLOCKSIZE ; (ax) = 1st handle,last block
        xor     dx,dx                   ; (dx:ax) = offset of last handle
        call    OffsetToClusSec         ; (ax) = cluster, (dx) = sector
                                        ; (bx) = offset of handle in sector
        mov     [ffe_CurCluster],ax     ; save the cluster index
        mov     ax,size EAFileHeader    ; (ax) = offset of first handle
        xor     dx,dx                   ; (dx:ax) = offset of first handle
        call    OffsetToClusSec         ; (ax) = cluster, (dx) = sector
                                        ; (bx) = offset of handle in sector
        mov     [ffe_FirstHClus],ax     ; save the cluster index
        mov     [ffe_FirstHSec],dx      ; save the sector index
        mov     [ffe_cbScan],0          ; no bytes scanned yet

; calculate backskip

        mov     ax,EA_BLOCKSIZE         ; (ax) = bytes per handle block
        xor     dx,dx                   ; (dx:ax) = bytes per handle block
        div     [ffe_BytesPerSec]       ; (ax) = sectors per handle block
        xor     dx,dx                   ; (dx:ax) = sectors per handle block
        div     [ffe_SecPerClus]        ; (ax) = cluster per handle block
        or      ax,ax                   ; At least 1 cluster per handle block?
        jnz     ffe07                   ; yes, calculate backskip

        inc     ax                      ; (ax) = 1, at least one cluster
ffe07:  shl     ax,1                    ; (ax) *= 2
        dec     ax                      ; (ax) = number of cluster to backskip
        mov     [ffe_BackSkip],ax       ; save the backskip

; at this point we have the preliminaries.  we know where the handle table
; begins and ends (in terms of cluster/sector pairs).

ffe10:  xor     bx,bx                   ; start with first sector of cluster
        mov     [ffe_CurSector],bx      ; save the current sector
        mov     cx,[ffe_CurCluster]     ; (cx) = logical cluster in file
        cmp     cx,[ffe_FirstHClus]     ; first cluster with handles?
        ja      ffe15                   ; not the first cluster with handles

        mov     [ffe_SkipCount],2       ; skip only handle 0
        mov     bx,[ffe_FirstHSec]      ; first sector with handles?
        mov     [ffe_CurSector],bx      ; save the current sector
        or      bx,bx                   ; Is this the first sector in cluster?
        jnz     ffe15                   ; no, everything is OK

        or      cx,cx                   ; Is this the first cluster in EA file?
        jnz     ffe15                   ; no, everything is OK
                                        ; yes, first cluster and first sector.
        mov     [ffe_SkipCount],size EAFileHeader+2 ; skip header and handle 0

ffe15:  mov     [ffe_PhysCluster],0     ; (dx) = 0 physical cluster not provided
ffe20:  mov     ax,[ffe_FirstClus]      ; (ax) = first physical cluster of file
        mov     bx,[ffe_CurSector]      ; (bx) = sector being searched
        cmp     bx,[ffe_SecPerClus]     ; Have we completed the cluster?
        jae     ffe40                   ;   Yes, go get the next cluster

        mov     cx,[ffe_CurCluster]     ; (cx) = logical cluster being searched
        mov     dx,[ffe_PhysCluster]    ; (dx) = physical cluster being searched
        call    GetEAFileSector         ; get the sector into a buffer
        jc      ffeerr                  ; exit on error

        mov     [ffe_PhysCluster],bx    ; save the physical cluster

; sector is now in memory.

        mov     di,[CurrTCB]
        les     di,TASKAREA:[di].TCBCurBuf ; es:di -> beginning of buffer
        add     di,BUFINSIZ             ; es:di -> beginning of buffer data
        add     di,[ffe_SkipCount]      ; es:di -> offset to begin search
        mov     cx,[ffe_BytesPerSec]    ; (cx) = number of bytes per sector
        sub     cx,[ffe_SkipCount]      ; (cx) = number of byte to search

; we are ready to search the buffer for a free handle.  Search it.

ffe30:  shr     cx,1                    ; (cx) = number of words of handles
        mov     ax,EA_INVALID           ; (ax) = search value
        repne   scasw                   ; search for free handle
        je      ffe50                   ; found a free handle, go return it

        CALLFAR RlseBuffr               ; handle not found, release buffer
        inc     [ffe_CurSector]         ; get next sector in this cluster
        les     di,[ffe_curVPB]         ; es:di -> VPB
        mov     ax,[ffe_BytesPerSec]    ; (ax) = bytes per sector
        add     [ffe_cbScan],ax         ; number of bytes scanned this time.
        jmp     short ffe20             ; try this sector

; done searching cluster, get the next one

ffe40:  mov     [ffe_SkipCount],0       ; assume no handle skipping
        mov     [ffe_cbScan],EA_BLOCKSIZE   ; Have we scanned a full block yet? far
        jae     ffe45                   ; yes, scan backwards some more

        inc     [ffe_CurCluster]        ; next cluster
        mov     cx,[ffe_CurCluster]     ; (cx) = current cluster to search
        jmp     ffe10

ffe45:  mov     [ffe_cbScan],0          ; reset the scan counter
        mov     ax,[ffe_BackSkip]       ; (ax) = backward cluster skip count
        sub     [ffe_CurCluster],ax     ; get the previous cluster in file
        mov     cx,[ffe_CurCluster]     ; (cx) = current cluster to search
        cmp     cx,[ffe_FirstHClus]     ; cluster left to search?
        jl      ffe47                   ; no, done searching (note signed jump)

        jmp     ffe10                   ; not the first cluster with handles

ffe47:  mov     ax,ERROR_EA_TABLE_FULL  ; (ax) = error code
        stc                             ; failed to find the cluster
        jmp     short ffex              ; exit in failure

; we found a free entry.  set the value as appropriate.

ffe50:  sub     di,2                    ; undo the scasw that found the handle
        mov     bx,[ffe_OffsetEOF]      ; (bx) = cluster offset of EOF
        mov     ax,[ffe_CurCluster]     ; (ax) = cluster being searched
        mov     cx,[ffe_CurSector]      ; (cx) = sector being searched
        mov     si,[ffe_curVPB.Offst]
        mov     dx,[ffe_curVPB.Segmt]   ; dx:si -> VPB
        call    MarkHandleAllocated     ; mark the handle allocated
                                        ; exit with return value from MHA
ffex:   LeaveProc
        ret

ffeerr: mov     ax,ERROR_EA_TABLE_FULL
        stc
        jmp     short ffex

EndProc FindFreeEAHandle


;***    MarkHandleAllocated - mark an EA handle allocated
;
;       MarkHandleAllocated takes an invalid EA handle and sets it
;       to the offset that an EAData associated with that handle
;       should have.
;
;       NOTE:  This procedure relies on the fact that the handle
;       passed in is the first free handle in its cluster.  The
;       only exception to this is handle 1.
;
;       ENTRY   [TCBCurBuf] -> beginning of FAT buffer cache buffer
;               [TCBThisSFT] -> SFT for "EA DATA. SF"
;               ES:DI -> offset in buffer of handle to be allocated
;               DX:SI -> VPB
;               BX = Cluster offset of EOF
;               AX = Cluster being searched
;               CX = Sector being searched
;
;       EXIT    'C' if some error
;                   AX = Return error code
;               'NC' if success
;                   AX = free handle to be used
;
;       USES    AX,BX,CX,DX,SI,DI
;
;       ALGORITHM
;
;       MarkHandleAllocated
;
;       if (new handle is first in block)
;         handle value = 0
;       else
;         if (handle not last in block)
;           handle value = value of next handle
;         else
;           call GetEAFileSector to get the header sector
;           if (handle in last block of file)
;             handle value = (next base value - this base value)
;           else
;             handle value = (EOF offset - this base value
;           call RlseBuffr to free the header sector
;           call GetEAFileSector to get the sector with the handle again
;       call RlseBuffr to commit it to disk

Procedure MarkHandleAllocated,near
        assume  ds:nothing, es:nothing

LocalVar    mha_VPB,DWORD               ; VPB
LocalVar    mha_CurrOff,WORD            ; offset of newly allocated handle
LocalVar    mha_EOFOff,WORD             ; cluster offset of EOF
LocalVar    mha_Cluster,WORD            ; cluster with handle
LocalVar    mha_Sector,WORD             ; sector with handle
LocalVar    mha_CurClus,WORD            ; cluster being searched
LocalVar    mha_CurSec,WORD             ; sector being searched
LocalVar    mha_FirClus,WORD            ; first physical cluster of EA file
LocalVar    mha_NewHandle,WORD          ; number of handle being allocated

        EnterProc <dx,si,di,bx,ax,cx,ax,cx>

; see if there are any allocated handles before us

        mov     si,[CurrTCB]            ; ss:si -> TCB
        mov     si,TASKAREA:[si].TCBCurBuf.Offst; (si) = offset of buffer
        add     si,BUFINSIZ             ; (si) = offset of buffer data
        mov     dx,di                   ; (dx) = offset of handle
        sub     dx,si                   ; (dx) = offset from beginning of buffer
        mov     [mha_CurrOff],dx        ; save the offset in the sector
        mov     si,di                   ; (si) = offset handle being allocated
        les     di,[mha_VPB]            ; es:di -> VPB
        mov     cl,es:[di].vpb_fsd.vpdFAT_cluster_shift ; (cl) = log2 sec/clus
        shl     ax,cl                   ; (ax) = sector to beginning of cluster
        add     ax,[mha_Sector]         ; (ax) = sectors before current sector
        mul     es:[di].vpb_fsi.vpi_cbSector; (ax) = byte offset of sector
        add     ax,[mha_CurrOff]        ; (ax) = offset of handle in file
        sub     ax,size EAFileHeader    ; (ax) = offset of handle in table
        shr     ax,1                    ; (ax) = handle number
        mov     [mha_NewHandle],ax      ; save the handle number
        test    ax,NOT EA_BLKHMASK      ; Is the handle the first in block?
        jnz     mha20                   ; no, not the first handle in block

mha10:  xor     ax,ax                   ; (ax) = 0, first handle in block
        jmp     mha70                   ; go write it into the buffer

mha20:  cmp     ax,EA_FIRSTHANDLE       ; Is this the first EA handle in file?
        je      mha10                   ; yes, it will be 0 as well

; we are not the first, see if we are the last

        mov     cx,ax                   ; (cx) = handle number
        add     cx,EA_BLOCKCOUNT        ; (cx) = handle in next block
        and     cx,EA_BLKHMASK          ; (cx) = first handle in next block
        sub     cx,ax                   ; (cx) = handles left in this block
        xor     dx,dx                   ; (dx) = 0, buffer contains new handle

mha30:  mov     bx,es:[di].vpb_fsi.vpi_cbSector ; (ax) = bytes per sector
        mov     di,[CurrTCB]
        lds     di,TASKAREA:[di].TCBCurBuf  ; ds:di -> buffer with sector
        add     di,BUFINSIZ             ; ds:di -> beginning of buffer data
        add     di,bx                   ; ds:di -> end of buffer data
        mov     bx,EA_INVALID           ; (bx) = invalid handle

mha40:  cmp     si,di                   ; Is there an allocated handle after us?
        jae     mha43                   ;   no, last allocated handle in block

        dec     cx                      ; Any more handles to examine?
        js      mha50                   ;   no, no handles left to scan

        lodsw                           ; (ax) = handle value
        cmp     ax,bx                   ; Is the handle allocated?
        jne     mha45                   ;   yes, (ax) = value for new handle

        jmp     short mha40             ; not allocated, go do the next one

mha43:  jcxz    mha50                   ; No handles left to scan, use base tbl

        CALLFAR RlseBuffr               ; ditch the buffer, we don't need it

; determine which sector to get next

        les     di,[mha_VPB]            ; es:di -> VPB
        xor     ax,ax                   ; (ax) = 0
        mov     al,es:[di].vpb_fsd.vpdFAT_cluster_mask  ; (ax) = mask sec/clus
        inc     [mha_CurSec]            ; get the next sector
        and     [mha_CurSec],ax         ; mask the sector to number per cluster
        jnz     mha44                   ; sector is in the same cluster

        inc     [mha_CurClus]           ; update goes into the next cluster

mha44:  SaveReg <cx>                    ; number of words left to search
        mov     cx,[mha_CurClus]        ; (cx) = logical cluster to get
        mov     bx,[mha_CurSec]         ; (bx) = logical sector to get
        mov     si,[CurrTCB]
        lds     si,TASKAREA:[si].TCBThisSFT ; ds:si -> SFT for EA file
        mov     ax,[si].sf_fsd.sfdFAT_firFILEclus ; (ax) = first cluster of EA file
        mov     [mha_FirClus],ax        ; save the first cluster of EA file
        xor     dx,dx                   ; (dx) = 0, no physical cluster passed
        call    GetEAFileSector
        RestoreReg  <cx>                ; (cx) = number of words left to search
        LJC     mhax                    ; error, failed to get cluster

; sector with word is now in memory, adjust the values in the sector

        mov     si,[CurrTCB]            ; ss:di -> TCB
        mov     si,TASKAREA:[si].TCBCurBuf.Offst; (si) = offset of sector buffer
        add     si,BUFINSIZ             ; (si) = beginning of buffer data
        mov     dx,1                    ; (dx) != 0, new handle not in buffer
        jmp     short mha30             ; go process the sector

; we are not the last, figure out if the buffer contains our handle

mha45:  or      dx,dx                   ; Is the new handle in the buffer?
        LJZ     mha70                   ; yes, go write the handle and exit

        mov     cx,ax                   ; (cx) = value for new handle
        jmp     mha65                   ; go get the buffer and write the handle

; we are the last, figure out our offset from data in the Base table

mha50:  CALLFAR RlseBuffr               ; release the Offset table buffer

        mov     ax,[mha_NewHandle]      ; (ax) = handle being allocated
        shr     ax,EA_HTOBASE           ; (ax) = entry number in base table
        shl     ax,1                    ; (ax) = byte offset in base table
        add     ax,eaf_basetable        ; (ax) = offset of base entry in file
        xor     dx,dx                   ; (dx:ax) = (ax)
        les     di,[mha_VPB]            ; es:di -> VPB
        call    OffsetToClusSec         ; (ax) = cluster, (dx) = sector
                                        ; (bx) = offset of handle in sector
        mov     si,[CurrTCB]            ; ss:si -> TCB
        lds     si,TASKAREA:[si].TCBThisSFT ; ds:si -> SFT for EA file
        mov     cx,ax                   ; (cx) = logical cluster with handle
        mov     ax,[si].sf_fsd.sfdFAT_firFILEclus ; (ax) = first cluster of EA file
        mov     [mha_FirClus],ax        ; save the first cluster of EA file
        SaveReg <bx>                    ; save offset in sector
        mov     bx,dx                   ; (bx) = sector in cluster with handle
        xor     dx,dx                   ; (dx) = 0, no physical cluster passed
        call    GetEAFileSector
        RestoreReg  <dx>                ; (dx) = offset in sector
        LJC     atberr                  ; error, failed to get cluster

        mov     si,[CurrTCB]            ; ss:si -> TCB
        lds     si,TASKAREA:[si].TCBCurBuf  ; ds:si -> sector buffer
        add     si,BUFINSIZ             ; ds:si -> beginning of buffer data
        add     si,dx                   ; ds:si -> base entry of interest
        lodsw                           ; (ax) = value of current base entry
        mov     bx,ax                   ; (bx) = value of current base entry
        mov     cx,[mha_EOFOff]         ; (cx) = offset of EOF
        mov     ax,[mha_NewHandle]      ; (ax) = handle being allocated
        shr     ax,EA_HTOBASE           ; (ax) = entry number in base table
        shl     ax,1                    ; (ax) = byte offset in base table
        cmp     ax,EA_LASTBASE          ; Did we read the last entry in table?
        jae     mha60                   ;   yes, do not read further

        cmp     dx,es:[di].vpb_fsi.vpi_cbSector ; Is next entry in same sector?
        jb      mha55                   ; yes, do not need to read next sector

        CALLFAR RlseBuffr               ; release the old base table sector
        mov     ax,[mha_NewHandle]      ; (ax) = handle being allocated
        shr     ax,EA_HTOBASE           ; (ax) = entry number in base table
        shl     ax,1                    ; (ax) = byte offset in base table
        add     ax,eaf_basetable        ; (ax) = offset of base entry in file
        add     ax,2                    ; (ax) = next base table entry
        xor     dx,dx                   ; (dx:ax) = (ax)
        SaveReg <bx>                    ; save the current base entry value
        call    OffsetToClusSec         ; (ax) = cluster, (dx) = sector
                                        ; (bx) = offset of handle in sector
        mov     si,[CurrTCB]            ; ss:si -> TCB
        lds     si,TASKAREA:[si].TCBThisSFT ; ds:si -> SFT for EA file
        mov     cx,ax                   ; (cx) = logical cluster with handle
        mov     ax,[si].sf_fsd.sfdFAT_firFILEclus ; (ax) = first cluster of EA file
        SaveReg <bx>                    ; save offset in sector
        mov     bx,dx                   ; (bx) = sector in cluster with handle
        xor     dx,dx                   ; (dx) = 0, no physical cluster passed
        call    GetEAFileSector
        RestoreReg  <dx,bx>             ; (dx) = entry offset, (bx) = base value
        LJC     atberr                  ; error, failed to get cluster

        mov     si,[CurrTCB]            ; ss:si -> TCB
        lds     si,TASKAREA:[si].TCBCurBuf  ; ds:si -> sector buffer
        add     si,BUFINSIZ             ; ds:si -> beginning of buffer data
        add     si,dx                   ; ds:si -> base entry of interest

mha55:  mov     cx,[si]                 ; (cx) = value of next entry in table
mha60:  sub     cx,bx                   ; (cx) = value handle should have
mha65:  CALLFAR RlseBuffr               ; get rid of the file header
        SaveReg <cx>                    ; save the value the handle should have
        mov     ax,[mha_FirClus]        ; (ax) = first cluster of EA file
        mov     cx,[mha_Cluster]        ; (cx) = logical cluster with new handle
        mov     bx,[mha_Sector]         ; (bx) = logical sector with new handle
        xor     dx,dx                   ; (dx) = 0, no physical cluster passed
        les     di,[mha_VPB]            ; es:di -> VPB
        call    GetEAFileSector         ; get the sector into the buffer
        RestoreReg  <ax>                ; (ax) = new value handle should have
        jc      mhax

; we have the new handle value, set it and exit

mha70:  mov     si,[CurrTCB]            ; ss:si -> TCB
        lds     si,TASKAREA:[si].TCBCurBuf; ds:si -> sector buffer
        or      [si].buf_flags,BUF_DIRTY; mark the buffer dirty
        add     si,BUFINSIZ             ; ds:si -> beginning of buffer data
        add     si,[mha_CurrOff]        ; ds:si -> allocated handle
        mov     [si],ax                 ; set handle = ax
        CALLFAR RlseBuffr               ; release the buffer to the system
        clc                             ; set good error code
        mov     ax,[mha_NewHandle]      ; (ax) = handle being allocated
mhax:   LeaveProc
        ret

EndProc MarkHandleAllocated


;***    MarkEAHandleFree - mark an EA handle free
;
;       MarkEAHandleFree marks the entry in EAOffTable invalid.
;       It does not recalculate the handle table.  That is done
;       in SwapClusterChain.
;
;       ENTRY   [TCBThisSFT] -> SFT for "EA DATA. SF"
;               ES:DI -> VPB
;               AX -  handle
;
;       EXIT    'NC' if success
;                   AX - old value of the handle entry
;
;               'C' if error
;                   AX = error code
;
;       USES    AX, BX, CX, DX, SI, DS, Flags
;
;
;       ALGORITHM
;
;       MarkEAHandleFree
;         if (handle < cEAHandles)
;           call OffsetToClusSec to compute the sector with the handle
;           call GetEAFileSector to get the handle table sector
;           mark the buffer dirty
;           mark the handle to be freed invalid
;           call RlseBuffr to commit the buffer to disk
;         else
;           return (ERROR_INVALID_EA_HANDLE)

Procedure MarkEAHandleFree,near
        assume  ds:nothing, es:nothing

        cmp     ax,es:[di].vpb_fsd.vpdFAT_eahandles ; past end of Offset table?
        jae     mhferr                  ;   Yes, return error

; figure out which sector and where in the sector the offset block is.

        shl     ax,1                    ; (ax) = byte offset into handle table
        add     ax,size EAFileHeader    ; (ax) = byte offset into file
        xor     dx,dx                   ; (dx:ax) = byte offset of handle
        call    OffsetToClusSec         ; (ax) = cluster, (dx) = sector
                                        ; (bx) = offset in sector
; we are now ready to read the sector.  read it.

        SaveReg <bx>                    ; save the offset in the sector
        mov     si,[CurrTCB]            ; ss:si -> TCB
        lds     si,TASKAREA:[si].TCBThisSFT ; ds:si -> SFT for EA file
        mov     cx,ax                   ; (cx) = logical cluster with handle
        mov     ax,[si].sf_fsd.sfdFAT_firFILEclus ; (ax) = first cluster of EA file
        mov     bx,dx                   ; (bx) = sector in cluster with handle
        xor     dx,dx                   ; (dx) = 0, no physical cluster passed
        call    GetEAFileSector
        RestoreReg <bx>                 ; (bx) = offset in the sector
        jc      mhferr                  ; error, failed to get cluster

; handle block is now in memory.  prepare to adjust the values in the block.

        mov     di,[CurrTCB]            ; ss:di -> TCB
        les     di,TASKAREA:[di].TCBCurBuf  ; es:di -> sector buffer
        or      es:[di].buf_flags,BUF_DIRTY ; mark the buffer dirty
        add     di,BUFINSIZ             ; es:di -> beginning of buffer data
        add     di,bx                   ; es:di -> handle to free
        mov     ax,EA_INVALID           ; (ax) = invalid handle
        xchg    es:[di],ax              ; mark handle invalid, (ax) = old value
        CALLFAR RlseBuffr               ; read complete release the buffer

mhfx:   ret

mhferr: mov     ax,ERROR_INVALID_EA_HANDLE
        stc
        jmp     short mhfx

EndProc MarkEAHandleFree


;***    GetEADataInfo - get the info from the beginning of the EAData
;
;       GetEADataInfo reads the first sector of the EAData from the
;       disk.  It verifies the signature and caller handle and then
;       returns the necessary information about the data.
;
;       ENTRY   [TCBThisSFT] -> SFT for "EA DATA. SF"
; bugbug 20-Jul-89 peterwu - should not pass parameter in TCB variable
;               ES:DI -> VPB
;               CX -  cluster offset of the EAData
;               AX -  handle of the EAData
;
;       EXIT    'NC' if success
;                   AX - size of the EAData in clusters
;                   CX - size of the FEAList in bytes
;                   BX - number of NEED EAs
;
;               'C' if error
;                   AX = error code
;
;       USES    AX, BX, CX, DX, SI, DS, Flags
;
;
;       ALGORITHM
;
;       GetEADataInfo
;         call GetEAFileSector to get the first sector of the EAData
;         call RlseBuffr to free the buffer after reading it
;         if (signature or handle is bad)
;           return (ERROR_EA_FILE_CORRUPT)
;         call ClustersFromBytes to get the cluster size of the EAData
;         return the number of clusters, FEAlist size and NEED EA count

Procedure GetEADataInfo,near
        assume  ds:nothing, es:nothing

LocalVar    edi_VPB,DWORD               ; VPB for volume of interest
LocalVar    edi_Handle,WORD             ; handle to be gotten
LocalVar    edi_Cluster,WORD            ; offset of the handle in sector
LocalVar    edi_FEASize,WORD            ; size of the FEAList in bytes

        CodeLock FATEA_GROUP
        EnterProc   <es,di,ax,cx>

; read in the sector

        mov     si,[CurrTCB]            ; ss:si -> TCB
        lds     si,TASKAREA:[si].TCBThisSFT ; ds:si -> SFT for EA file
        mov     ax,[si].sf_fsd.sfdFAT_firFILEclus ; (ax) = first cluster of EA file
        xor     bx,bx                   ; (bx) = first sector in cluster
        xor     dx,dx                   ; (dx) = 0, no physical cluster passed
        call    GetEAFileSector
        jc      edix                    ; error, failed to get cluster

; handle block is now in memory.  prepare to adjust the values in the block.

        mov     si,[CurrTCB]            ; ss:di -> TCB
        lds     si,TASKAREA:[si].TCBCurBuf  ; es:di -> sector buffer
        add     si,BUFINSIZ             ; es:di -> beginning of buffer data
        cmp     [si].ead_signature,EA_DSIGNATURE; Is the EAData valid?
        jne     edierr                  ;  no, EA file corrupt, return error

        mov     ax,[si].ead_owneahand   ; (ax) = EAData owner handle
        cmp     ax,[edi_Handle]         ; Is the owner handle correct?
        jne     edierr                  ;  no, EA file corrupt, return error

        mov     bx,[si].ead_needeacnt.hiword; (bx) = high word of NEED EA count
        or      bx,bx                   ; Are there > 64K NEED EAs?
        jnz     edierr                  ; yes, we're busted, return error

        mov     bx,[si].ead_needeacnt.loword; (bx) = number of NEED EAs
        mov     ax,[si].EA_CBLISTOFF    ; (ax) = size of the FEAlist
        mov     [edi_FEASize],ax        ; save the size of the FEAlist
        xor     dx,dx                   ; (dx:ax) = size of FEAlist
        add     ax,SIZE EADataHeader
        adc     dx,0                    ; (dx:ax) = size of EAData
        call    ClustersFromBytes       ; (ax) = size of EAData in clusters
        mov     cx,[edi_FEASize]        ; (cx) = size of the FEAlist

edi10:  CALLFAR RlseBuffr               ; read complete release the buffer

edix:   LeaveProc
        CodeUnlock FATEA_GROUP
        ret

edierr:
ifdef   FSSTRICT
        InternalError <GetEADataInfo: EA file corrupt>
endif
        CALLFAR RlseBuffr               ; read complete release the buffer
        mov     ax,ERROR_EA_FILE_CORRUPT
        stc
        jmp     short edix

EndProc GetEADataInfo


;***    GetEAFileSector - read a sector from the EA file
;
;       GetEAFileSector gets a FAT buffer cache buffer with
;       the indicated sector from the disk.  The sector is
;       specified in terms of a logical or physical cluster
;       and a sector in the cluster.
;
;       This routine may only be called for the EA file if
;       the physical cluster is 0.  It may be used for any
;       sector on the disk if the physical cluster is provided.
;
;       Note: The buffer must be released with RlseBuffr.
;
;       ENTRY   ES:DI -> VPB
;               [TCBThisSFT] -> SFT for "EA DATA. SF"
;               DX = physical cluster or 0 if not provided
;               CX = logical cluster  (if DX = 0)
;               BX = sector in cluster
;               AX = first physical cluster in file (if DX = 0)
;
;       EXIT    'C' if some error
;                   AX = error code
;               'NC' if success
;                   [TCBCurBuf] -> buffer with sector
;                   BX = physical cluster
;
;       USES    AX,BX,CX,DX,SI,DS
;
;       ALGORITHM
;
;       GetEAFileSector
;         if (physical cluster not passed in)
;           call SKPCLP to get the physical cluster
;         call FigRec to get the physical sector from logical sector
;         call GetBuffr to get the sector into memory

Procedure GetEAFileSector,near
        assume  ds:nothing, es:nothing

LocalVar    gfs_LogClus,WORD            ; logical cluster in file, 0 based
LocalVar    gfs_LogSec,WORD             ; logical sector in cluster, 0 based
LocalVar    gfs_PhysClus,WORD           ; physical cluster
LocalVar    gfs_FirstPhys,WORD          ; first physical cluster in file

        EnterProc <cx,bx,dx,ax>

        or      dx,dx                   ; did we get the physical cluster?
        jnz     gfs90                   ;   yes, no need to get it again

        mov     bx,ax                   ; (bx) = first physical cluster in file
        xor     dx,dx                   ; (dx) = 0, bx is first cluster in file
        call    DoSKPCLP
        jc      gfserr                  ; error, return it to caller

        mov     [gfs_PhysClus],bx       ; save physical cluster number
        mov     dx,bx                   ; (dx) = physical cluster being searched

; we now have a physical cluster and logical sector, get the sector off disk

gfs90:  mov     bx,[gfs_LogSec]         ; (bl) = sector being searched
        SaveReg <bp>
        mov     bp,di                   ; es:bp -> VPB
        CALLFAR FigRec                  ; get the physical sector number
        mov     cx,si                   ; (cx:dx) = physical sector number
        xor     al,al                   ; (al) = 0, read data from disk

        ; may need to set [Allowed] for GetBuffr

        CALLFAR GetBuffr                ; get the sector into the buffer
        mov     di,bp                   ; es:di -> VPB
        RestoreReg  <bp>
        mov     bx,[gfs_PhysClus]       ; (bx) = physical cluster

gfsx:   LeaveProc
        ret

gfserr: mov     ax,ERROR_NO_CLUSTER
        stc
        jmp     short gfsx

EndProc GetEAFileSector


;***    ChangeEAHandle - update the EA handle in the SFT and DIR
;
;       ChangeEAHandle is a worker for AddEA and DeleteEA.
;
;       This routine updates the EA handle information in the SFT
;       and directory entry.
;
;       ENTRY   [TCBThisSFT] -> SFT for file in question
;               ES:DI -> SFT for file in question
;               AX = new EA handle
;
;       EXIT    'C' if some error
;                   AX = error code
;               'NC' if success
;                   SFT and directory entry updated
;                   AX = old EA handle
;
;       USES    AX, BX, CX, DX, SI, DS, Flags
;
;       ALGORITHM
;
;         mark the SFT dirty
;         call DirFromSFT to get the directory entry
;         mark the kernel buffer dirty
;         update the EA handle in the directory entry
;         call RlseBuffr to release the buffer
;         call FlushBuf to flush the changes to disk
;         call FNotify_Handle_Wake to notify waiters of changes
;         update the EA handle in the SFT
;         call ShSU to propigate the changes to other SFTs

Procedure ChangeEAHandle,near
        assume  ds:nothing, es:nothing

LocalVar    ush_NewEAHandle,WORD        ; new value for EA handle
LocalVar    ush_OldEAHandle,WORD        ; old value for EA handle

        EnterProc  <ax>
        SaveReg <di,es>

        and     es:[di].sf_flags,NOT DEVID_FILE_CLEAN   ; Mark file as dirty
        xchg    es:[di].sf_fsd.sfdFAT_EAHandle,ax   ; Update SFT
        mov     [ush_OldEAHandle],ax

        TaskContext <DS>,,NoAssume
        SaveReg <BP>
        call    DirFromSFT              ; Trashes BP
        RestoreReg <BP>
        jc      ushx                    ; Error. return error to caller

        assume ds:nothing, es:nothing   ; Blown away by DirFromSFT

; got the directory entry in a kernel buffer.  update and mark it dirty.

        or      es:[bx].buf_flags,BUF_DIRTY ; Mark buffer as dirty
        mov     ax,[ush_NewEAHandle]    ; (ax) = new EA handle
        mov     es:[di].dir_EAhandle,ax ; save the new EA handle

        CALLFAR RlseBuffr               ; Release the kernel buffer

        mov     si,[CurrTCB]
        mov     ax,TASKAREA:[si].TCBThishVPB; (ax) = hVPB
        SaveReg <ax>
        CALLFAR FlushBuf                ; Flush the directory buffer
        RestoreReg <ax>
        SaveReg <ax,bx>
        mov     bx,ax
        call    UseCache
        RestoreReg <bx,ax>
        jc      ceah1
CpuMode 386
        SaveReg <ds,es>
        movzx   eax,ax
        pushad
        FlatContext <ds,es>
        CCALL32 _BufFlushFATDir,<eax>
        popad
        RestoreReg <es,ds>
        assume  ds:nothing, es:nothing
CpuMode reset
ceah1:  CALLFAR FNotify_Handle_Wake

; We now update the SFT entry for this object.

        mov     di,TASKAREA:[CurrTCB]
        les     di,TASKAREA:[di].TCBThisSFT ; ES:DI -> SFT
        mov     bx,[ush_NewEAHandle]
        mov     es:[di].sf_fsd.sfdFAT_EAHandle,bx   ; Update SFT
        mov     ax,2
        CALLFAR ShSU                    ; Update old SFTs from new
        mov     ax,[ush_OldEAHandle]    ; (ax) = old EA handle
        clc

ushx:   RestoreReg <es,di>
        LeaveProc
        ret                             ; Return any error indications.

EndProc ChangeEAHandle


;***    DeleteEA - remove and EAData from "EA DATA. SF"
;
;       DeleteEA is a worker for DiskWriteFEAlist.
;
;       This routine does everything necessary to remove an
;       FEAlist from "EA DATA. SF".  It will correctly deallocate
;       the handle and zero it out in the SFT.
;
;       The work this routine does is recoverable.  All changes
;       are made in a manner that allows the transaction to complete
;       or be entirely voided if the system crashes during this
;       routine.
;
;       For details of the recovery mechanism see DiskWriteFEAlist
;       and SwapClusterChain.
;
;       This routine takes care of the case where the original
;       FEAlist does not exist.
;
;
;       ENTRY   ES:SI -> SFT
;               BX:DI -> Buffer with new FEAlist (NewFEA)
;               CX    =  Size of FEAlist to write out (ByteCount)
;               TCBThisSFT set to the SFT for the object being used
;
;       EXIT    'C' if some error
;                   Return error code
;                   Old FEAlist is still valid
;               'NC' if success
;                   SFT updated with new EA cluster number
;                   SFT marked "dirty"
;                   Clusters for old FEAlist released
;                   CX = number of bytes transferred
;
;       ALGORITHM
;
;       DeleteEA
;         call EASerialize (write) to get exclusive access to EA file
;         if handle# is 0 then  /* is this possible? */
;           do nothing
;         else
;           call FreeEAHandle to mark the handle invalid
;           call GetEADataInfo to find the size of the EAData
;           call EAHandleToOffset to get the cluster offset of the EAData
;           call SwapClusterChain to remove the EAData
;           call ChangeEAHandle to clear the EA handle in the SFT and directory
;         call EAUnserialize (write) to allow other thread access to EA file
;         if (there was an EAData to release)
;           call Release to deallocate the old EAData

Procedure DeleteEA,near
        assume  ds:nothing, es:nothing

LocalVar    dea_ByteCount,WORD          ; Size of transfer
LocalVar    dea_FileSFT,DWORD           ; SFT of file getting EAs added
LocalVar    dea_OldEAChain,WORD         ; First cluster of old EA chain
LocalVar    dea_OldEASize,WORD          ; size of the old EAData in clusters
LocalVar    dea_OldEAOff,WORD           ; offset of the old EAData in clusters
LocalVar    dea_OldEAHand,WORD          ; EA handle on entry to AddEA
LocalVar    dea_VPB,DWORD               ; VPB

        EnterProc <cx,es,si,0>          ; Initialize local variables

        mov     bx,es:[si].sf_fsi.sfi_hVPB  ; (bx) = hVPB
        VPBContext ds,bx                ; ds:bx -> VPB
        mov     [dea_VPB].Offst,bx
        mov     [dea_VPB].Segmt,ds      ; save the VPB
        mov     ax,EA_WRITE
        call    EASerialize             ; get write serialization

        mov     ax,es:[si].sf_fsd.sfdFAT_EAHandle   ; (ax) = old EA handle
        or      ax,ax                   ; Is there an EAData for the file?
        jz      dea10                   ; no, do nothing

        mov     [dea_OldEAHand],ax      ; save the old EA handle
        les     di,[bx].vpb_fsd.vpdFAT_pEASFT   ; es:di -> SFT for "EA DATA. SF"
        mov     si,[CurrTCB]            ; ss:si -> TCB
        mov     TASKAREA:[si].TCBThisSFT.Offst,di
        mov     TASKAREA:[si].TCBThisSFT.Segmt,es   ; ThisSFT -> EA File SFT

; we are set up to do IO to the EA file.  set up and swap a information.

        les     di,[dea_VPB]            ; es:di -> VPB
        xor     cx,cx                   ; get only the cluster offset
        mov     ax,[dea_OldEAHand]      ; (ax) = handle for EAData
        call    EAHandleToOffset        ; (cx) = cluster offset of EAData
        jc      dea20                   ; error, bail out

        mov     [dea_OldEAOff],cx       ; save the offset of EAData
        mov     ax,[dea_OldEAHand]      ; (ax) = old EA handle
        call    GetEADataInfo           ; (ax) = old EAData size
        jc      dea20                   ; error, go return it

        mov     [dea_OldEASize],ax      ; save the size of the old EAData
        SaveReg <cx>
        mov     ax,[dea_OldEAHand]      ; (ax) = old EA handle
        call    MarkEAHandleFree        ; mark the EA handle invalid
        RestoreReg  <cx>                ; (cx) = cluster offset of EAData

        les     di,[dea_VPB]            ; es:di -> VPB
        mov     cx,[dea_OldEAOff]       ; (cx) = the offset of EAData
        push    cx                      ; where this EAData was
        mov     ax,[dea_OldEAHand]      ; (ax) = handle for EAData
        inc     ax                      ; (ax) = handle after one being freed
        push    ax                      ; change the next handle
        push    [dea_OldEASize]         ; number of clusters in old chain
        dec     cx                      ; cluster before EAData
        push    cx                      ; delete EAData at correct offset
        push    0                       ; adding no new clusters
        push    0                       ; no new clusters being added

        call    SwapClusterChain        ; swap in the cluster chain
        jc      dea20                   ; error, we're hosed, bigtime!

; new EAData in place.  update the SFT and directory as necessary.

        mov     [dea_OldEAChain],dx     ; save first cluster of old chain
        mov     bx,[CurrTCB]            ; ss:bx -> TCB
        les     di,[dea_FileSFT]
        mov     TASKAREA:[bx].TCBThisSFT.Offst,di
        mov     TASKAREA:[bx].TCBThisSFT.Segmt,es   ; ThisSFT -> File SFT
        xor     ax,ax                   ; (ax) = 0, new EA handle
        call    ChangeEAHandle          ; update handle in SFT and DIR
        jc      dea20                   ; error, return it to the caller

; work requiring serialization complete.  release serialization and clean up.

dea10:  lds     bx,[dea_VPB]            ; ds:bx -> VPB
        mov     ax,EA_WRITE
        call    EAUnserialize

; now release the old cluster chain

        mov     bx,[dea_OldEAChain]     ; (bx) = first cluster of old chain
        or      bx,bx                   ; Is there an EA chain to release?
        jz      deax                    ; no, just exit

        SaveReg <bp>                    ; Save pointer to stack frame
        mov     bp,[CurrTCB]
        mov     bp,[bp].TCBThishVPB     ; ES:BP -> VPB
        VPBContext ES,BP
        TaskContext <DS>,,NoAssume
        call    ReleaseVL               ; try to release the old cluster chain
        RestoreReg <bp>
        clc                             ; return success
        jmp     short deax              ; go exit

; error occurred while serialized, release serialization and clean up.

dea20:  SaveReg <ax>                    ; save the error code
        lds     bx,[dea_VPB]            ; ds:bx -> VPB
        mov     ax,EA_WRITE
        call    EAUnserialize
        mov     si,[CurrTCB]            ; ss:si -> TCB
        mov     ax,[dea_FileSFT].Offst
        mov     TASKAREA:[si].TCBThisSFT.Offst,ax
        mov     ax,[dea_FileSFT].Segmt
        mov     TASKAREA:[si].TCBThisSFT.Segmt,ax ; restore the old TCBThisSFT
        RestoreReg  <ax>                ; (ax) = error code
        stc

deax:   mov     cx,[dea_ByteCount]
        LeaveProc
        ret

EndProc DeleteEA


;***    ReleaseEA - release an EAData from "EA DATA. SF"
;
;       ReleaseEA replaces the old routines that just simple
;       deleted the old physical EA chain.
;
;       This routine does everything necessary to remove an
;       FEAlist from "EA DATA. SF".  It does no SFT updates.
;
;       The work this routine does is recoverable.  All changes
;       are made in a manner that allows the transaction to complete
;       or be entirely voided if the system crashes during this
;       routine.
;
;       For details of the recovery mechanism see DiskWriteFEAlist
;       and SwapClusterChain.
;
;       This routine takes care of the case where the original
;       FEAlist does not exist.
;
;
;       ENTRY   ES:SI -> VPB
;               BX = EA handle to delete
;
;       EXIT    'C' if some error
;                   ax = Return error code
;               'NC' if success
;
;       ALGORITHM
;
;       ReleaseEA
;         call EASerialize (write) to get exclusive access to EA file
;         if handle# is 0 then  /* is this possible? */
;           do nothing
;         else
;           call FreeEAHandle to mark the handle invalid
;           call GetEADataInfo to find the size of the EAData
;           call EAHandleToOffset to get the cluster offset of the EAData
;           call SwapClusterChain to remove the EAData
;           call ChangeEAHandle to clear the EA handle in the SFT and directory
;         call EAUnserialize (write) to allow other thread access to EA file
;         if (there was an EAData to release)
;           call Release to deallocate the old EAData

Procedure ReleaseEA,near
        assume  ds:nothing, es:nothing

LocalVar    rea_VPB,DWORD               ; VPB
LocalVar    rea_Handle,WORD             ; Handle to release
LocalVar    rea_EASize,WORD             ; Size of the EAData to be removed
LocalVar    rea_EAOffset,WORD           ; Offset of the EAData to be removed
LocalVar    rea_OldThisSFT,DWORD        ; old TCBThisSFT

        CodeLock FATEA_GROUP

        EnterProc <es,si,bx>            ; Initialize local variables

        or      bx,bx                   ; Is the handle valid?
        LJC     reax                    ; no, exit

        mov     ax,EA_READ              ; do not create file
        mov     bx,si                   ; (bx) = hVPB
        call    SetupFatEA              ; open EA file if necessary
        LJC     reax                    ; error, exit

        lds     bx,[rea_VPB]            ; ds:bx -> VPB
        mov     ax,EA_WRITE
        call    EASerialize             ; get write serialization

        les     di,[bx].vpb_fsd.vpdFAT_pEASFT   ; es:di -> SFT for "EA DATA. SF"
        mov     si,[CurrTCB]            ; ss:si -> TCB
        mov     ax,TASKAREA:[si].TCBThisSFT.Offst
        mov     [rea_OldThisSFT].Offst,ax
        mov     ax,TASKAREA:[si].TCBThisSFT.Segmt
        mov     [rea_OldThisSFT].Segmt,ax   ; save the old TCBThisSFT
        mov     TASKAREA:[si].TCBThisSFT.Offst,di
        mov     TASKAREA:[si].TCBThisSFT.Segmt,es   ; ThisSFT -> EA File SFT

; we are set up to do IO to the EA file.  set up and swap a information.

        les     di,[rea_VPB]            ; es:di -> VPB
        xor     cx,cx                   ; get only the cluster offset
        mov     ax,[rea_Handle]         ; (ax) = handle for EAData
        call    EAHandleToOffset        ; (cx) = cluster offset of EAData
        jc      rea20                   ; error, bail out

        mov     [rea_EAOffset],cx       ; save the cluster offset
        mov     ax,[rea_Handle]         ; (ax) = old EA handle
        call    GetEADataInfo           ; (ax) = old EAData size
        jc      rea20                   ; error, go return it

        mov     [rea_EASize],ax         ; save the size of the old EAData
        mov     ax,[rea_Handle]         ; (ax) = old EA handle
        call    MarkEAHandleFree        ; mark the EA handle invalid

        les     di,[rea_VPB]            ; es:di -> VPB
        mov     cx,[rea_EAOffset]       ; (cx) = the cluster offset
        push    cx                      ; where this EAData was
        mov     ax,[rea_Handle]         ; (ax) = handle for EAData
        inc     ax                      ; (ax) = handle after one being freed
        push    ax                      ; change the next handle
        push    [rea_EASize]            ; number of clusters in old chain
        dec     cx                      ; cluster before EAData
        push    cx                      ; delete EAData at correct offset
        push    0                       ; adding no new clusters
        push    0                       ; no new clusters being added

        call    SwapClusterChain        ; swap in the cluster chain
        jc      rea20                   ; error, we're hosed, bigtime!

; work requiring serialization complete.  release serialization and clean up.

rea10:  lds     bx,[rea_VPB]            ; ds:bx -> VPB
        mov     ax,EA_WRITE
        call    EAUnserialize

        mov     si,[CurrTCB]            ; ss:si -> TCB
        mov     ax,[rea_OldThisSFT].Offst
        mov     TASKAREA:[si].TCBThisSFT.Offst,ax
        mov     ax,[rea_OldThisSFT].Segmt
        mov     TASKAREA:[si].TCBThisSFT.Segmt,ax ; restore the old TCBThisSFT
        or      dx,dx                   ; Is there an EA chain to release?
        jz      reax                    ; no, just exit

; now release the old cluster chain

        SaveReg <bp>                    ; Save pointer to stack frame
        les     bp,[rea_VPB]            ; ES:BP -> VPB
        TaskContext <DS>,,NoAssume
        mov     bx,dx                   ; (bx) = first cluster of old chain
        call    ReleaseVL               ; try to release the old cluster chain
        RestoreReg <bp>
        clc                             ; return success
        jmp     short reax              ; go exit

; error occurred while serialized, release serialization and clean up.

rea20:  SaveReg <ax>                    ; save the error code
        lds     bx,[rea_VPB]            ; ds:bx -> VPB
        mov     ax,EA_WRITE
        call    EAUnserialize
        mov     si,[CurrTCB]            ; ss:si -> TCB
        mov     ax,[rea_OldThisSFT].Offst
        mov     TASKAREA:[si].TCBThisSFT.Offst,ax
        mov     ax,[rea_OldThisSFT].Segmt
        mov     TASKAREA:[si].TCBThisSFT.Segmt,ax ; restore the old TCBThisSFT
        RestoreReg  <ax>                ; (ax) = error code
        stc

reax:   LeaveProc
        CodeUnlock FATEA_GROUP
        ret

EndProc ReleaseEA


;***    SwapClusterChain - Swap a chain of clusters into "EA DATA. SF"
;
;       SwapClusterChain is called to "replace" an existing chain in
;       "EA DATA. SF" with a new chain. It is important that the whole
;       operation be considered atomic, i.e. if there is any failure, then
;       CHKDSK can correctly complete the transaction or the old chain
;       is not replaced.  Logging at the beginning of "EA DATA. SF" will
;       be set to allow CHKDSK enough information to complete the
;       replacement.
;
;       This routine is passed the SFT for the EA file to be changed
;       and a cluster chain to be swapped in.  All data destined for
;       the new chain must already be in place when this routine is
;       called.
;
;       At the beginning of a transaction, enough informtion to recreate
;       the transaction is written out to the log at the beginning of
;       "EA DATA. SF".  The information will include a list of FAT
;       entries that need to be changed and their new values and
;       the first handle that has become invalid and what its new
;       value should be.  We the proceed to change the FAT and update
;       the handle information.  When this is done, all of it is flushed
;       out to disk.  The transaction is concluded when the log is
;       zeroed out and EABaseTable is updated.  (Note that this can
;       be done atomically as they are both in the same sector.)
;
;       This routine will correctly deal with swapping chains of
;       equal, different and zero sizes.
;
;       ENTRY   ES:DI -> VPB
;               [TCBThisSFT] -> SFT for "EA DATA. SF"
;               On entry the stack contains:
;                 scc_FirstAdd  - first PHYSICAL cluster of chain to add
;                 scc_AddCount  - number of clusters in add chain
;                 scc_ChangePt  - LOGICAL cluster after which to swap
;                 scc_DelCount  - number of clusters to delete
;                 scc_Handle    - first handle that will change
;                 scc_NewHValue - value that handle should have
;
;
;       EXIT    'C' if some error
;                   AX = error code
;               'NC' if success
;                   DX  - first cluster of chain swapped out
;                   Cluster map invalidated beyond chain
;
;       ALGORITHM
;
;       SwapClusterChain
;         if (changept invalid)
;           return error
;         ls_cluster3 = phys(changept)
;         if  (firstadd == 0)     /* cluster delete */
;           if (delcount == 0)
;             return error
;           clusval3 = phys (changept + delcount + 1)
;           cluster2 = phys (changept + delcount)
;           clusval2 = EOF
;           cluster1 = 0
;           clusval1 = 0
;         else
;           clusval3 = firstadd
;           cluster2 = phys (firstadd + addcount - 1)
;           clusval2 = phys (changept + delcount + 1)
;           if (delcount == 0)
;             cluster1 = 0
;             clusval1 = 0
;           else
;             cluster1 = phys (changept + delcount)
;             clusval1 = EOF
;         call DoClusterSwap to swap the disk changes and handle updates


Procedure  SwapClusterChain,near
        assume  ds:nothing, es:nothing

ArgVar      scc_FirstAdd,WORD           ; first cluster of chain to add
ArgVar      scc_AddCount,WORD           ; number of clusters in add chain
ArgVar      scc_ChangePt,WORD           ; cluster after which to swap
ArgVar      scc_DelCount,WORD           ; number of clusters to delete
ArgVar      scc_Handle,WORD             ; first handle that will change
ArgVar      scc_NewHValue,WORD          ; value that handle should have
LocalVar    scc_VPB,DWORD               ; pointer to VPB
LocalVar    scc_BytesPerClus,WORD       ; number of bytes per cluster
LocalVar    scc_FirstClus,WORD          ; first physical cluster of EA file
LocalVar    scc_LastValid,WORD          ; last valid logical cluster in map
LocalVar    scc_FirstDel,WORD           ; first physical cluster of del chain
LocalVar    scc_EOF,WORD                ; value to use for EOF
LocalVar    scc_LogData,<size LogStruc> ; data area for logging data

        EnterProc   <es,di>

; compute the values to put in the log.

        mov     bx,es:[di].vpb_fsd.vpdFAT_minEOF; (bx) = value to use for EOF
        mov     [scc_EOF],bx            ; save the EOF value
        mov     si,[CurrTCB]            ; ss:si -> TCB
        lds     si,TASKAREA:[si].TCBThisSFT ; ds:si -> SFT for EA file
        mov     bx,ds:[si].sf_fsd.sfdFAT_firFILEclus ; (bx) = first cluster of file
        mov     [scc_FirstClus],bx      ; save the first cluster
        xor     dx,dx                   ; (dx) = cluster is in EA file
        mov     cx,[scc_ChangePt]       ; (cx) = logical cluster in file
        mov     [scc_LastValid],cx      ; save the last valid cluster
        call    DoSKPCLP
        jc      sccerr                  ; error, bail out

        mov     [scc_LogData.ls_cluster3],bx; save physical cluster number
        cmp     [scc_FirstAdd],0        ; Is this a delete?
        jne     scc20                   ;   No, it is an add or change

; this is a simple cluster delete.  set up the first cluster before the delete.

        mov     cx,[scc_DelCount]       ; (cx) = number of clusters to remove
        jcxz    sccerr                  ; error, none to add, none to remove

        add     cx,[scc_ChangePt]       ; (cx) = logical last cluster offset
        inc     cx                      ; (cx) = last cluster after del chain
        mov     bx,[scc_FirstClus]      ; (bx) = first cluster of file
        call    DoSKPCLP                ; get the physical cluster number
        mov     [scc_LogData.ls_clusval3],bx; where the file continues
        jnc     scc10                   ; got the cluster correctly

        or      ax,ax                   ; Was there really an error?
        jnz     sccerr                  ;   yes it was a real error

        mov     ax,[scc_EOF]            ; (ax) = value of EOF
        mov     [scc_LogData.ls_clusval3],ax; end of file

; set the end of the chain being removed is set to EOF

scc10:  mov     bx,[scc_FirstClus]      ; (bx) = first cluster of file
        mov     cx,[scc_ChangePt]       ; (cx) = logical cluster in file
        add     cx,[scc_DelCount]       ; (cx) = last cluster of del chain
        call    DoSKPCLP                ; get the physical cluster number
        jc      sccerr                  ; error, bail out

        mov     [scc_LogData.ls_cluster2],bx; where the file continues
        mov     ax,[scc_EOF]            ; (ax) = value of EOF
        mov     [scc_LogData.ls_clusval2],ax; end of file
        jmp     short scc50

; error has occurred.  set the error code and exit

sccerr:
ifdef  FSSTRICT
        InternalError <SwapClusterChain: Error in EA file cluster chain>
endif
        mov     ax,ERROR_EA_FILE_CORRUPT
        stc
        jmp     sccx

; this is an add or a change.  the change point cluster points to the new chain.

scc20:  mov     bx,[scc_FirstAdd]       ; first cluster of chain to add
        mov     [scc_LogData.ls_clusval3],bx; add the chain here

        mov     dx,0ffffh               ; (dx) = not a cluster in EA file yet
        mov     bx,[scc_FirstAdd]       ; (bx) = first physical cluster to add
        mov     cx,[scc_AddCount]       ; (cx) = number of clusters to add
        dec     cx                      ; (cx) = ordinal of last add cluster
        call    DoSKPCLP                ; get the physical cluster number
        jc      sccerr                  ; error, bail out

        mov     [scc_LogData.ls_cluster2],bx; where the file continues
        mov     cx,[scc_DelCount]       ; (cx) = number of clusters to remove
        add     cx,[scc_ChangePt]       ; (cx) = logical last cluster offset
        inc     cx                      ; (cx) = last cluster after del chain
        mov     bx,[scc_FirstClus]      ; (bx) = first cluster of file
        xor     dx,dx                   ; (dx) = cluster is in EA file
        call    DoSKPCLP                ; get the physical cluster number
        mov     [scc_LogData.ls_clusval2],bx; where the file continues
        jnc     scc30                   ; got the cluster, continue

        or      ax,ax                   ; Is there really an error?
        jnz     sccerr                  ; really an error, return it.

        mov     ax,[scc_EOF]            ; (ax) = value of EOF
        mov     [scc_LogData.ls_clusval2],ax; end of file
scc30:  mov     cx,[scc_DelCount]       ; (cx) = number of cluster to remove
        jcxz    scc50                   ; no clusters to delete

; chain swap.  the last cluster of the new chain points into the original file.

        mov     bx,[scc_FirstClus]      ; (bx) = first cluster of file
        add     cx,[scc_ChangePt]       ; (cx) = logical cluster in file
        call    DoSKPCLP                ; get the physical cluster number
        jc      sccerr                  ; error, bail out

        mov     [scc_LogData.ls_cluster1],bx; where the file continues
        mov     ax,[scc_EOF]            ; (ax) = value of EOF
        mov     [scc_LogData.ls_clusval1],ax; end of file
        jmp     short scc60

; chain add.  there is no delete chain to worry about.

scc50:  mov     [scc_LogData.ls_cluster1],0 ; unused
        mov     [scc_LogData.ls_clusval1],0 ; unused

scc60:  mov     [scc_FirstDel],0        ; assume no delete
        cmp     [scc_DelCount],0        ; Are we deleting any clusters?
        je      scc70                   ; not deleting, don't get del chain

        mov     cx,[scc_ChangePt]       ; (cx) = logical last cluster offset
        inc     cx                      ; (cx) = first cluster of del chain
        mov     bx,[scc_FirstClus]      ; (bx) = first cluster of file
        call    DoSKPCLP                ; get the physical cluster number
        LJC     sccerr                  ; error, bail out

        mov     [scc_FirstDel],bx       ; first cluster of delete chain

scc70:  mov     [scc_LogData.ls_logtype],EA_LOGTYPE ; set the log type
        mov     si,[scc_LastValid]      ; (si) = last valid logical cluster
        mov     ax,[scc_Handle]         ; (ax) = first handle to change
        mov     [scc_LogData.ls_handle],ax  ; set the handle in the log
        mov     ax,[scc_NewHValue]      ; (ax) = value of first change handle
        mov     [scc_LogData.ls_handval],ax ; set the handle value in the log
        mov     cx,[scc_AddCount]       ; (cx) = size of chain being added
        sub     cx,[scc_DelCount]       ; (cx) = amount of file size change
        lea     ax,[scc_LogData]

        call    DoClusterSwap           ; do the swap, any error is returned

        mov     dx,[scc_FirstDel]       ; (dx) = first cluster of delete chain

sccx:
ifdef  FSSTRICT
        jnc     sccxt                   ; no problem go to the temporary exit
        InternalError <SwapClusterChain: routine about to return error>
sccxt:
endif
        LeaveProc
        ret     12                      ; This must be kept in sync with args

EndProc  SwapClusterChain


;***    DoClusterSwap - do the cluster swap
;
;       DoClusterSwap is a worker for SwapClusterChain
;
;       This routine executes the FAT changes and recalculates
;       and updates the EABase and EAOff tables.  It also writes
;       the change log before and after the changes.
;
;       For more information, see SwapClusterChain
;
;       ENTRY   [TCBThisSFT] -> SFT
;               ES:DI -> VPB
;               SS:AX -> LogStruc
;               CX = length(new chain) - length(old chain)
;               SI = last valid logical cluster
;
;       EXIT    'C' if some error
;                   AX = error code
;               'NC' if success
;                   Cluster map invalidated beyond chain
;
;       ALGORITHM
;
;       SwapClusterChain
;         call FSD_ChgFilePtr to beginning of log
;         call DOS_Write to write log
;         call FSD_FLUSHBUF to flush log
;         call Pack multi times to swap cluster chain
;         if replacing or deleting cluster chain then
;           call Pack to mark end of old cluster chain EOF
;         call FixBaseOffTables to update Base and Off tables
;         update directory size of "EA DATA. SF" in sft
;         call FSD_COMMIT to flush dir size, FAT, and handle tables
;         call FSD_ChgFilePtr to beginning of log
;         call DOS_Write to erase log & write EABaseTable
;         call FSD_FLUSHBUF to flush log and EABaseTable
;         call CMShrink to update the cluster map

Procedure  DoClusterSwap,near
        assume  ds:nothing, es:nothing

LocalVar    dcs_LogData,WORD            ; offset of LogStruc
LocalVar    dcs_LenChange,WORD          ; number of clusters of chain difference
LocalVar    dcs_VPB,DWORD               ; VPB for volume in question
LocalVar    dcs_LastValid,WORD          ; last valid logical cluster
LocalVar    dcs_BytesPerClus,WORD       ; number of bytes per cluster

        EnterProc   <ax,cx,es,di,si>

; we are ready to begin.  prepare to write the log.

        mov     si,[CurrTCB]            ; ss:si -> TCB
        lds     si,TASKAREA:[si].TCBThisSFT ; ds:si -> SFT for EA file
        mov     dx,[si].sf_fsd.sfdFAT_firFILEclus ; (dx) = first cluster of EA file
        xor     bx,bx                   ; (bx) = first sector of the file
        call    GetEAFileSector
        jc      dcs20                   ; error, failed to get cluster

; copy out the log data.

        mov     si,[CurrTCB]            ; ss:si -> TCB
        lds     si,TASKAREA:[si].TCBCurBuf; ds:si -> sector buffer
        or      [si].buf_flags,BUF_DIRTY; mark the buffer dirty
        add     si,BUFINSIZ             ; ds:si -> beginning of buffer data
        add     si,eaf_logtype          ; ds:si -> beginning of log
        mov     bx,[dcs_LogData]        ; ss:bx -> log data

        mov     di,[dcs_LenChange]      ; (di) = amount of the change
        call    SetLog                  ; set up the log

        CALLFAR RlseBuffr               ; write the buffer to disk

; we have written the log, flush it to disk.

        mov     di,[CurrTCB]
        les     di,TASKAREA:[di].TCBThisSFT ; es:di -> SFT for "EA DATA. SF"
        mov     ax,es:[di].sf_fsi.sfi_hVPB
        mov     dx,FLU_KEEP             ; keep the sector in the buffer cache
        SaveReg <ax>
        CALLFAR FSD_FlushBuf
        RestoreReg <dx>
        jnc     dcs30

; intermediate jumps to save 9 LJxx macros

dcs10:  jmp     dcserr

dcs20:  jmp     dcsx

; change the FAT information

dcs30:  SaveReg <dx,bx>
        mov     bx,dx
        call    UseCache
        RestoreReg <bx,dx>
        jc      dcs35
CpuMode 386
        SaveReg <ds,es>
        movzx   edx,dx
        pushad
        FlatContext <ds,es>
        CCALL32 _BufFlushFATDir,<edx>
        popad
        RestoreReg <es,ds>
        assume  ds:nothing, es:nothing
CpuMode reset
dcs35:  mov     si,[dcs_LogData]        ; ss:bx -> log data
        les     di,[dcs_VPB]            ; es:di -> VPB
        TaskContext <DS>,,noassume      ; ds = ss

        mov     bx,[si].ls_cluster1     ; (bx) = the cluster to change
        mov     dx,[si].ls_clusval1     ; (dx) = the value it should have
        call    DoPack                  ; write the FAT entry
        jc      dcs10                   ; big trouble.  exit with error.

        mov     bx,[si].ls_cluster2     ; (bx) = the cluster to change
        mov     dx,[si].ls_clusval2     ; (dx) the value it should have
        call    DoPack                  ; write the FAT entry
        jc      dcs10                   ; big trouble.  exit with error.

        mov     bx,[si].ls_cluster3     ; (bx) = the cluster to change
        mov     dx,[si].ls_clusval3     ; (dx) the value it should have
        call    DoPack                  ; write the FAT entry
        jc      dcs10                   ; big trouble.  exit with error.

; FAT is updated.  Update the handle tables.

        xor     ah,ah                   ; calculate bytes per cluster
        mov     al,es:[di].vpb_fsd.vpdFAT_cluster_mask
        inc     ax                      ; (ax) = sectors per cluster
        mul     es:[di].vpb_fsi.vpi_cbSector; (ax) = bytes per cluster
        mov     [dcs_BytesPerClus],ax   ; save the number of bytes per cluster

        mov     ax,ss:[si].ls_handle    ; (ax) = first changed handle
        mov     cx,[dcs_LenChange]      ; (cx) = amount of the change
        call    FixBaseOffTables        ; fix the EA offset and base tables
        jc      dcs10

; all handle changes complete.  fix up SFT and flush the changes

        mov     di,[CurrTCB]
        les     di,TASKAREA:[di].TCBThisSFT ; es:di -> SFT for "EA DATA. SF"
        and     es:[di].sf_flags,NOT DEVID_FILE_CLEAN   ; Mark file as dirty
        mov     ax,[dcs_BytesPerClus]   ; (ax) = bytes per cluster
        mov     cx,[dcs_LenChange]      ; number of clusters of change
        test    cx,8000h                ; Is the number of clusters negative?
        jz      dcs40                   ; no, nothing special to do

        neg     cx
        mul     cx                      ; dx:ax = number of bytes added
        sub     es:[di].sf_fsi.sfi_size.loword,ax ;
        sbb     es:[di].sf_fsi.sfi_size.hiword,dx ; subtract size of the EAData
        jmp     short dcs50

dcs40:  mul     cx                      ; dx:ax = number of bytes added
        add     es:[di].sf_fsi.sfi_size.loword,ax ;
        adc     es:[di].sf_fsi.sfi_size.hiword,dx ; add the size of the EAData
dcs50:  SaveReg <ax,dx,bp>

        TaskContext <DS>,,NoAssume
        call    DirFromSFT              ; Trashes BP
        RestoreReg <bp,dx,ax>
        LJC     dcs20                   ; Error. return error to caller

        assume ds:nothing, es:nothing   ; Blown away by DirFromSFT

; got the directory entry in a kernel buffer.  update and mark it dirty.
; NOTE: the addition below works even if CX is negative

        or      es:[bx].buf_flags,BUF_DIRTY ; Mark buffer as dirty
        test    [dcs_LenChange],8000h   ; Is the change negative?
        jz      dcs60                   ; no, normal positive change

        sub     es:[di].dir_size_l,ax   ;
        sbb     es:[di].dir_size_h,dx   ; add the size of the EAData
        jmp     short dcs70

dcs60:  add     es:[di].dir_size_l,ax   ;
        adc     es:[di].dir_size_h,dx   ; add the size of the EAData

dcs70:  CALLFAR RlseBuffr               ; Release the kernel buffer

        mov     di,[CurrTCB]
        les     di,TASKAREA:[di].TCBThisSFT ; es:di -> SFT for "EA DATA. SF"
        mov     ax,es:[di].sf_fsi.sfi_hVPB
        SaveReg <ax>
        mov     ax,FS_COMMIT_ONE        ; commit this file
        CALLFAR FSD_Commit
        RestoreReg <dx>
        jc      dcserr

        SaveReg <dx,bx>
        mov     bx,dx
        call    UseCache
        RestoreReg <bx,dx>
        jc      dcs75
CpuMode 386
        SaveReg <ds,es>
        movzx   edx,dx
        pushad
        FlatContext <ds,es>
        CCALL32 _BufFlushFATDir,<edx>
        popad
        RestoreReg <es,ds>
        assume  ds:nothing, es:nothing
CpuMode reset

; SFT and directory updated and all changes flushed.  clear out the log.

dcs75:  mov     si,[CurrTCB]            ; ss:si -> TCB
        lds     si,TASKAREA:[si].TCBThisSFT ; ds:si -> SFT for EA file
        mov     dx,[si].sf_fsd.sfdFAT_firFILEclus ; (dx) = first cluster of EA file
        xor     bx,bx                   ; (bx) = first sector of the file
        les     di,[dcs_VPB]            ; es:di -> VPB
        call    GetEAFileSector
        jc      dcsx                    ; error, failed to get cluster

        mov     di,[CurrTCB]            ; ss:di -> TCB
        les     di,TASKAREA:[di].TCBCurBuf  ; es:di -> sector buffer
        or      es:[di].buf_flags,BUF_DIRTY ; mark the buffer dirty
        add     di,BUFINSIZ             ; es:di -> beginning of buffer data
        mov     cx,(size LogStruc) / 2  ; (cx) = number of words to clear
        xor     ax,ax                   ; (ax) = 0 = fill value
        add     di,eaf_logtype          ; es:di -> log data
        rep     stosw                   ; fill the log with zeros

        CALLFAR RlseBuffr               ; write the buffer to disk

; we have written the log, flush it to disk.

        mov     di,[CurrTCB]
        les     di,TASKAREA:[di].TCBThisSFT ; es:di -> SFT for "EA DATA. SF"
        mov     ax,es:[di].sf_fsi.sfi_hVPB
        mov     dx,FLU_KEEP             ; keep the sector in the buffer cache
        SaveReg <es,di,ax>
        CALLFAR FSD_FlushBuf
        RestoreReg <dx,di,es>
        jc      dcserr

        SaveReg <dx,bx>
        mov     bx,dx
        call    UseCache
        RestoreReg <bx,dx>
        jc      dcs80
CpuMode 386
        SaveReg <ds,es>
        movzx   edx,dx
        pushad
        FlatContext <ds,es>
        CCALL32 _BufFlushFATDir,<edx>
        popad
        RestoreReg <es,ds>
        assume  ds:nothing, es:nothing
CpuMode reset
dcs80:  clc

; B720123 get rid of entire map then rebuild
; log is written out.  update cluster map.  reset cluster variables in SFT

;       mov     ax,dcs_LastValid        ; (ax) = last valid logical cluster
;       CALLFAR CMShrink                ; shrink the cluster map
CpuMode 386
        mov     edi,es:[di].sf_MFT
        FlatContext <ds>,ax             ; ds:edi -> mft
        CALLFAR CMKill                  ; remove possibly invalid cluster map
  Assume DS:NOTHING
CpuMode reset
        mov     di,[CurrTCB]
        les     di,TASKAREA:[di].TCBThisSFT ; es:di -> SFT for "EA DATA. SF"
        mov     ax,es:[di].sf_fsd.sfdFAT_firFILEclus; (ax) = first cluster
        mov     es:[di].sf_fsd.sfdFAT_lstclus,ax; first cluster accessed last
        mov     es:[di].sf_fsd.sfdFAT_cluspos,0 ; position of first cluster

dcsx:
ifdef FSSTRICT                          ; this routine had better not error
        jnc     dcsxx                   ; no error
        InternalError <DoClusterSwap: routine returned error>
dcsxx:
endif
        LeaveProc
        ret

dcserr: mov     ax,ERROR_EA_FILE_CORRUPT
        stc
        jmp     short dcsx

EndProc  DoClusterSwap


;***    SetLog - Set the log as appropriate for the call type
;
;       SetLog is a worker for DoClusterSwap.  It copies the values
;       from the log buffer into the sector buffer.
;
;       last valid - first cluster after which the change will occur
;       first continue - first cluster of the end of the EA file
;       first add - first cluster of the chain being added
;       last add - last cluster of the chain being added
;       first delete - first cluster of the chain being deleted
;       last delete - last cluster of the chain being deleted
;
;       Input buffer on entry:          The EA file log on exit:
;
;         Simple Add:
;
;           cluster1 = 0                    cluster1 = last valid
;           clusval1 = 0                    clusval1 = first add
;           cluster2 = last add             cluster2 = last add
;           clusval2 = first continue       clusval2 = first continue
;           cluster3 = last valid           cluster3 = 0
;           clusval3 = first add            clusval3 = 0
;
;         Simple Delete:
;
;           cluster1 = 0                    cluster1 = last delete
;           clusval1 = 0                    clusval1 = EOF
;           cluster2 = last delete          cluster2 = last valid
;           clusval2 = EOF                  clusval2 = first continue
;           cluster3 = last valid           cluster3 = 0
;           clusval3 = first continue       clusval3 = 0
;
;         Cluster Swap:
;
;           cluster1 = last delete          cluster1 = last delete
;           clusval1 = EOF                  clusval1 = EOF
;           cluster2 = last add             cluster2 = last valid
;           clusval2 = first continue       clusval2 = first add
;           cluster3 = last valid           cluster3 = last add
;           clusval3 = first add            clusval3 = first continue
;
;
;       ENTRY   SS:BX -> buffer to be place into EA file log
;               DS:SI -> beginning of log information in sector buffer
;               DI - number of clusters of change in EA file size
;
;       EXIT    None
;
;       USES    AX,Flags
;
;       ALGORITHM
;
;       SetLog
;
;         dest.logtype = src.logtype
;         dest.handle  = src.handle
;         dest.handval = src.handval
;         if (src.cluster1 != 0)            swap
;           dest.cluster1 = src.cluster1
;           dest.clusval1 = src.clusval1
;           dest.cluster2 = src.cluster3
;           dest.clusval2 = src.clusval3
;           dest.cluster3 = src.cluster2
;           dest.clusval3 = src.clusval2
;         else
;           dest.cluster3 = 0
;           dest.clusval3 = 0
;           if (src.handval < 0)            delete
;             dest.cluster1 = src.cluster2
;             dest.clusval1 = src.clusval2
;             dest.cluster2 = src.cluster3
;             dest.clusval2 = src.clusval3
;           else                            add
;             dest.cluster1 = src.cluster3
;             dest.clusval1 = src.clusval3
;             dest.cluster2 = src.cluster2
;             dest.clusval2 = src.clusval2

Procedure SetLog,near
        assume  ds:nothing, es:nothing, ss:TASKAREA

        mov     ax,ss:[bx].ls_logtype
        mov     [si].ls_logtype,ax      ; set the log type
        mov     ax,ss:[bx].ls_handle
        mov     [si].ls_handle,ax       ; set the handle in the log
        mov     ax,ss:[bx].ls_handval
        mov     [si].ls_handval,ax      ; set the handle value in the log
        mov     ax,ss:[bx].ls_cluster1  ; (ax) = cluster1
        or      ax,ax                   ; Is this a cluster swap?
        jz      stl10                   ; no, go do a simple add or delete

        mov     [si].ls_cluster1,ax     ; cluster1 = cluster1
        mov     ax,ss:[bx].ls_clusval1  ; clusval1 = clusval1
        mov     [si].ls_clusval1,ax
        mov     ax,ss:[bx].ls_cluster2  ; cluster3 = cluster2
        mov     [si].ls_cluster3,ax
        mov     ax,ss:[bx].ls_clusval2  ; clusval3 = clusval2
        mov     [si].ls_clusval3,ax
        jmp     short stl30             ; go finish

stl10:  mov     [si].ls_cluster3,ax     ; cluster and value are both zero
        mov     [si].ls_clusval3,ax
        cmp     di,ax                   ; Is this a delete?
        jl      stl20                   ; yes, go do delete stuff

        mov     ax,ss:[bx].ls_cluster3  ; cluster1 = cluster3
        mov     [si].ls_cluster1,ax
        mov     ax,ss:[bx].ls_clusval3  ; clusval1 = clusval3
        mov     [si].ls_clusval1,ax
        mov     ax,ss:[bx].ls_cluster2  ; cluster2 = cluster2
        mov     [si].ls_cluster2,ax
        mov     ax,ss:[bx].ls_clusval2  ; clusval2 = clusval2
        mov     [si].ls_clusval2,ax
        jmp     short stlx              ; go exit

stl20:  mov     ax,ss:[bx].ls_cluster2  ; cluster1 = cluster2
        mov     [si].ls_cluster1,ax
        mov     ax,ss:[bx].ls_clusval2  ; clusval1 = clusval2
        mov     [si].ls_clusval1,ax

stl30:  mov     ax,ss:[bx].ls_cluster3  ; cluster2 = cluster3
        mov     [si].ls_cluster2,ax
        mov     ax,ss:[bx].ls_clusval3  ; clusval2 = clusval3
        mov     [si].ls_clusval2,ax

stlx:   ret                             ; vacations over, back to work

EndProc SetLog


;***    DoPack - Call the pack routine
;
;       DoPack is a worker for DoClusterSwap.  It calls the Pack
;       routine to change the FAT entries for the cluster swap.
;
;       DoPack saves and restores registers around the call to
;       Pack.  It is used to simplify the code in DoClusterSwap.
;
;       ENTRY   ES:DI -> VPB
;               BX  -  FAT entry to be changed
;                      or zero if no write is to take place
;               DX  -  the value the FAT entry should be changed to
;
;       EXIT    'C' if some error
;               'NC' if success
;
;       USES    BX,DX

Procedure DoPack,near
        assume  ds:nothing, es:nothing

        or      bx,bx                   ; are we to write the FAT entry?
        jz      dopx                    ; no, skip the operation, carry clear

        SaveReg <si,di,bp>              ; save the regs that will get trashed
        mov     bp,di                   ; es:bp -> VPB
        call    Pack                    ; change the FAT entry
        RestoreReg <bp,di,si>           ; restore the regs

dopx:   ret                             ; vacations over, back to work

EndProc DoPack


;***    DoSKPCLP - Call the SKPCLS routine
;
;       DoSKPCLP is a worker for SwapClusterChain.  It calls SKPCLP
;       to find out the physical cluster associated with a logical cluster.
;
;       DoSKPCLP saves and restores registers around the call to
;       SKPCLP.  It is used to simplify the code in SwapClusterChain.
;       It returns carry for the case where the cluster was not found,
;       that is cx != 0 on exit.
;
;       ENTRY   (bx)    =  physical first cluster in file
;               (cx)    =  logical cluster in file, 0 if first cluster
;               (dx)    =  0 if chain is in "EA DATA. SF", !0 otherwise
;               (es:di) -> VPB
;               TCBThisSFT -> SFT -> MFT
;
;       EXIT    'NC'if success
;                   (bx) = Last cluster skipped to
;                   (cx) = No. of clusters remaining (0 unless EOF)
;               'C' if failure
;                     if (cx != 0)
;                       (ax) = 0
;                     else
;                       (ax) = error code or 0 if (CX != 0)
;               DS = TASKAREA
;
;
;       USES    ax,bx,cx,si,flags

Procedure DoSKPCLP,near
        assume  ds:nothing, es:nothing

        clc                             ; set good return code
        jcxz    dosx                    ; skip no clusters

        SaveReg <dx,di,bp>              ; save the regs that will get trashed
        mov     bp,di                   ; es:bp -> VPB
        or      dx,dx                   ; Is this for "EA DATA. SF"?
        jnz     dos10                   ;   No, do not use cluster map

        CALLFAR SKPCLP                  ; get the fat value
        jc      dosx                    ; error, return it
        jmp     short dos20

dos10:  xor     dx,dx                   ; start with beginning of chain
        CALLFAR CMSkip                  ; get the fat value
        jc      dos30                   ; error, return it

dos20:  jcxz    dos30                   ; no error, we got the cluster

        xor     ax,ax                   ; SKPCLP didn't error, but didn't find
        stc                             ; the cluster we wanted

dos30:  RestoreReg <bp,di,dx>           ; restore the regs
dosx:   ret                             ; vacations over, back to work

EndProc DoSKPCLP


;***    FixBaseOffTables - update the Base and Off tables in the EA file
;
;       FixBaseOffTables is a worker for DoClusterSwap
;
;       This routine recalculates and updates the EABase and EAOff tables.
;
;       For more information, see SwapClusterChain
;
;       N.B.: This routine assumes that the handle AX is the handle
;       right after the one that is changing.  For CX >= 0 this is
;       not important.  For CX < 0, Handle MOD BlockSize is used
;       to determine whether the offset block needs to be adjusted
;       or just the base table.
;
;       ENTRY   ES:DI -> VPB
;               [TCBThisSFT] -> SFT for "EA DATA. SF"
;               AX = first handle to change
;               CX = length(new chain) - length(old chain)
;
;       EXIT    'C' if some error
;                   AX = error code
;               'NC' if success
;
;       USES    AX,BX,CX,DX,SI,DI,DS,ES,Flags
;
;
;       ALGORITHM
;
;       FixBaseOffTables
;         if the amount of change == 0
;           return
;         if (change handle != first handle in block)
;           call AdjustTableBlock to add the change values in the offset table
;         if (offset table was adjusted)
;           start with the base value for the next handle block
;         else
;           start with the base block for the change handle
;         call AdjustTableBlock to adjust the base table
;

Procedure  FixBaseOffTables,near
        assume  ds:nothing, es:nothing

LocalVar    fbo_VPB,DWORD               ; VPB for volume of interest
LocalVar    fbo_ChgHandle,WORD          ; first handle to be changed
LocalVar    fbo_LenChange,WORD          ; number of clusters of chain difference
LocalVar    fbo_BaseAdjust,WORD         ; start at current or next base entry

        EnterProc   <es,di,ax,cx,eaf_basetable>

        jcxz    fbo30                   ; No change in file length, do nothing

        test    ax,NOT EA_BLKHMASK      ; Is the handle first in its block?
        jz      fbo20                   ;   yes, do not adjust base table

.errnz  size EAFileHeader - (2 * EA_BLOCKSIZE)

        add     [fbo_BaseAdjust],2      ; start with the next base value
        shl     ax,1                    ; (ax) = byte offset into handle table
        add     ax,size EAFileHeader    ; (ax) = byte offset into file
        mov     bx,cx                   ; (bx) = amount to adjust handle by
        mov     cx,ax                   ; (cx) = byte offset into file
        add     cx,EA_BLOCKSIZE         ; (cx) = byte offset + size(handleblock)
        and     cx,EA_BLKOFFMASK        ; (cx) = beginning of next handle block
        sub     cx,ax                   ; (cx) = number of byts left in block
        jz      fbo10                   ; no handles to adjust, skip adjust

        shr     cx,1                    ; (cx) = number of words left in block
        mov     si,[CurrTCB]            ; ss:si -> TCB
        lds     si,TASKAREA:[si].TCBThisSFT ; ds:si -> SFT for EA file
        call    AdjustTableBlock        ; adjust the block of handles
        jc      fbox

fbo10:  mov     ax,[fbo_ChgHandle]      ; (ax) = change handle
fbo20:  shr     ax,EA_HTOBASE           ; (ax) = entry number in base table
        shl     ax,1                    ; (ax) = byte offset in base table
        add     ax,[fbo_BaseAdjust]     ; (ax) = offset of base entry
        mov     cx,size EAFileHeader    ; (cx) = offset of end of base table
        sub     cx,ax                   ; (cx) = number of bytes to adjust
        js      fbo30                   ; in last handle block, no base adjust

        shr     cx,1                    ; (cx) = number of words to adjust
        mov     bx,[fbo_LenChange]      ; (bx) = amount to adjust handle by
        mov     si,[CurrTCB]            ; ss:si -> TCB
        lds     si,TASKAREA:[si].TCBThisSFT ; ds:si -> SFT for EA file
        les     di,[fbo_VPB]            ; es:di -> VPB
        call    AdjustTableBlock        ; adjust the block of handles

fbox:   LeaveProc
        ret

fbo30:  clc
        jmp     short fbox

EndProc FixBaseOffTables


;***    AdjustTableBlock - update a block of handles or base table values
;
;
;       FIXED FOR VDISK
;
;
;       AdjustTableBlock is a worker for FixBaseOffTables
;
;       NOTE: This routine is closely related to EAHandleToOffset.
;       Much of the code is the same and the routines should probably
;       be merged sometime in the future.  Bugs in one may well be
;       bugs in the other.
;
;       N.B.: This routine assumes that the handle AX is the handle
;       right after the one that is changing.  For CX >= 0 this is
;       not important.  For CX < 0, Handle MOD BlockSize is used
;       to determine whether the offset block needs to be adjusted
;       or just the base table.
;
;       ENTRY   ES:DI -> VPB
;               DS:SI -> SFT for "EA DATA. SF"
;               [TCBThisSFT] -> SFT for "EA DATA. SF"
;               AX = offset of first word to change
;               BX = amount to change handle by
;               CX = number of words to change
;
;       EXIT    'C' if some error
;                   AX = error code
;               'NC' if success
;                 if (BX = 0) on entry
;                   AX = value of first allocated handle or -1
;                   CX = words left unscanned
;
;       USES    AX,BX,CX,DX,SI,DI,DS,ES,Flags
;
;       ALGORITHM
;
;       call  OffsetToClusSec to convert the offset into a cluster and sector
;       while (words left to adjust)
;           call GetEAFileSector to read in the current sector
;           for (start at first word in buffer that needs adjusting;
;                words left to adjust && not at the end of the sector;
;                words left--, next word)
;               if (word is valid)
;                   add adjust value to the word
;               store the word back in the sector buffer
;           call RlseBuffr to place the sector back on disk
;           sector number++
;           if (sector number > sectors per cluster)
;               cluster number++
;               sector number = 0
;

Procedure  AdjustTableBlock,near
        assume  ds:nothing, es:nothing

LocalVar    atb_VPB,DWORD               ; VPB for volume of interest
LocalVar    atb_OffInSec,WORD           ; number of bytes of handles left
LocalVar    atb_Count,WORD              ; number of handles to change
LocalVar    atb_LenChange,WORD          ; number of clusters of chain difference
LocalVar    atb_Cluster,WORD            ; cluster of current buffer
LocalVar    atb_Sector,WORD             ; sector in cluster of current buffer
LocalVar    atb_cbSector,WORD           ; number of bytes per sector
LocalVar    atb_FILEClus,WORD           ; first physical cluster of EA file

        EnterProc   <es,di,ax,cx,bx>

; figure out which sector and where in the sector the offset is.

        mov     bx,es:[di].vpb_fsi.vpi_cbSector ; (bx) = bytes per sector
        mov     [atb_cbSector],bx       ; save the number of bytes per sector

        xor     dx,dx                   ; (dx:ax) = byte offset of handle
        call    OffsetToClusSec         ; (ax) = cluster, (dx) = sector
                                        ; (bx) = offset of handle in sector
        mov     [atb_Cluster],ax        ; save the cluster containing the word
        mov     [atb_Sector],dx         ; save the sector containing the word
        mov     [atb_OffInSec],bx       ; save the offset of word in the sector
        mov     cx,ax                   ; (cx) = logical cluster with handle
        mov     ax,[si].sf_fsd.sfdFAT_firFILEclus ; (ax) = first cluster of EA file
        mov     [atb_FILEClus],ax       ; save the first cluster of EA file
        mov     bx,dx                   ; (bx) = sector in cluster with handle

; we are now ready to read the sector.  read it.

atb10:  xor     dx,dx                   ; (dx) = 0, no physical cluster passed
        call    GetEAFileSector
        LJC     atberr                  ; error, failed to get cluster

; sector with word is now in memory, adjust the values in the sector

        mov     di,[CurrTCB]            ; ss:di -> TCB
        les     di,TASKAREA:[di].TCBCurBuf  ; es:di -> sector buffer
        or      es:[di].buf_flags,BUF_DIRTY ; mark the buffer dirty
        add     di,BUFINSIZ             ; es:di -> beginning of buffer data
        mov     si,di                   ; es:si -> beginning of buffer data
        mov     bx,[atb_LenChange]      ; (bx) = amount to change word by
        add     di,[atb_OffInSec]       ; (di) = offset of first word to change
        add     si,[atb_cbSector]       ; (si) = offset of the end of the sector
        xor     dx,dx                   ; (dx) = 0, buffer still clean

; all parameter set, update the values in the table

atb30:  cmp     di,si                   ; Have we reached the end of the sector?
        jae     atb50                   ;   yes, get rid of the buffer

        dec     [atb_Count]             ; Are there any more words to do?
        js      atb90                   ;   no, all words have been adjusted

        mov     ax,es:[di]              ; (ax) = handle to be adjusted
        cmp     ax,EA_INVALID           ; Is the handle invalid?
        je      atb40                   ;   yes, handle is invalid, don't adjust

        inc     dx                      ; (dx) != 0, buffer is dirty
        add     ax,bx                   ; (ax) = adjusted handle value

atb40:  stosw                           ; restore the value
        jmp     short atb30             ; go do the next handle

; we ran out of sector, see if there are more words to update

atb50:  cmp     [atb_Count],0           ; Are we done?
        je      atb90                   ;   yes, exit

        or      dx,dx                   ; Did anything in the sector change?
        jnz     atb60                   ;   yes, no need to mark it clean

        mov     di,[CurrTCB]            ; ss:di -> TCB
        mov     di,TASKAREA:[di].TCBCurBuf.Offst; es:di -> sector buffer
        and     es:[di].buf_flags,NOT BUF_DIRTY ; mark the buffer clean

atb60:  CALLFAR RlseBuffr               ; updates complete, put sector on disk

; sector is on disk, determine which sector to get next

        les     di,[atb_VPB]            ; es:di -> VPB
        xor     ax,ax                   ; (ax) = 0
        mov     [atb_OffInSec],ax       ; start at the beginning of next sector
        mov     al,es:[di].vpb_fsd.vpdFAT_cluster_mask  ; (ax) = mask sec/clus
        inc     [atb_Sector]            ; get the next sector
        and     [atb_Sector],ax         ; mask the sector to number per cluster
        jnz     atb70                   ; sector is in the same cluster

        inc     [atb_Cluster]           ; update goes into the next cluster

atb70:  mov     cx,[atb_Cluster]        ; (cx) = logical cluster to get
        mov     bx,[atb_Sector]         ; (bx) = logical sector to get
        mov     ax,[atb_FILEClus]       ; (ax) = first phys cluster in EA file
        jmp     atb10                   ; go process the sector

; the last sector has been updated, place it on disk

atb90:  or      dx,dx                   ; Did anything in the sector change?
        jnz     atbx                    ;   yes, no need to mark it clean

        mov     di,[CurrTCB]            ; ss:di -> TCB
        mov     di,TASKAREA:[di].TCBCurBuf.Offst; es:di -> sector buffer
        and     es:[di].buf_flags,NOT BUF_DIRTY ; mark the buffer clean

atbx:   CALLFAR RlseBuffr               ; updates complete, put sector on disk

atberr: LeaveProc
        ret

EndProc AdjustTableBlock


;***    OffsetToClusSec - converts an offset into a logical cluster/sector
;
;
;       FIXED FOR VDISK
;
;
;       This routine converts a DWORD offset into a file and converts it
;       into a logical cluster/sector pair and offset inside of the sector.
;
;       ENTRY   ES:DI -> file VPB
;               DX:AX = logical byte offset
;
;       EXIT    AX = logical cluster offset into the file
;               DX = logical sector offset inside of the last cluster
;               BX = logical byte offset inside of the last sector
;
;       USES    AX,BX,CX,DX,FLAGS

Procedure OffsetToClusSec,near
        assume  ds:nothing, es:nothing

        xor     cx,cx                   ; (cx) = 0, assume < 64K sectors
        cmp     dx,es:[di].vpb_fsi.vpi_cbSector ; will a divide overflow?
        jb      ocs10                   ; no, just go do it.

; ((dx:ax) DIV cbSector) >= 64K, the divide would overflow.
; Get around that by breaking up the division into two parts:
;
; (((dx) DIV cbSector) * 64K) + ((((dx) MOD cbSector):ax) DIV cbSector)

        SaveReg <ax>
        mov     ax,dx                   ; (ax) = high word of byte offset
        xor     dx,dx                   ; (dx:ax) = (ax)
        div     es:[di].vpb_fsi.vpi_cbSector; (ax) = sec * 64K, (dx) = remainder
        mov     cx,ax                   ; (cx) = high word of number of sectors
        RestoreReg  <ax>                ; (dx:ax) = number of bytes

ocs10:  div     es:[di].vpb_fsi.vpi_cbSector; (ax) = sectors, (dx) = offset
        mov     bx,dx                   ; (bx) = offset of byte in last sector
        mov     dx,cx                   ; (dx:ax) = # of sectors
        xor     cx,cx                   ; (cx) = 0
        mov     cl,es:[di].vpb_fsd.vpdFAT_cluster_mask  ; CX = sec/clus - 1
        inc     cx                      ; CX = sectors/cluster
        div     cx                      ; (ax) = cluster, (dx) = sector

ocsx:   ret

EndProc OffsetToClusSec

FATEASWAPCODE ENDS

        FARCODE HIGH4
HIGH4CODE       SEGMENT
;**     HogEAFile - prevent user from opening the EA file on a FAT volume
;
;       This procedure is called after a new FAT volume is mounted. It enters
;       the FAT EA file name into the MFT to prevent the user from playing
;       with the EA file. If an error occur in the procedure, we just ignore
;       it and try to open it again in proceduer SetupFatEA.
;
;       If this procedure is called with the vpb already containing a EA sft,
;       it means the user open the drive door and close it again, potentially
;       having removed the disk and moved the EA file around. Therefore we
;       need to close and re-open the EA file to update the sft and the cluster
;       map.
;
;       ENTRY   ax = hVPB
;
;       EXIT    if 'CY' - error
;                 ax = error code
;               if 'NC' - success
;
;       USES    ax,bx,cx,dx,si,di,ds,es,flags
;
;       PSEUDO CODE
;
;       if not FAT for sure then
;         return
;       allocate SFT
;       fill in SFT
;       call EA_EnterShare

Procedure HogEAFile,faronly
    assume ds:nothing,es:nothing,ss:TASKAREA

LocalVar hef_hVPB,WORD


        EnterProc <ax>
        mov     bx,ax                                   ; bx = hVPB
        VPBContext ds,bx                                ; es:bx -> vpb
        test    ds:[bx].vpb_fMisc,VPB_FM_UNKNOWN        ; unknown volume?
        jnz     hefx                                    ; yes, ignore it

        cmp     ds:[bx].vpb_FSC.Offst,FSC_LOCAL         ; FAT?
        jne     hefx                                    ; ignore lean volume

        cmp     ds:[bx].vpb_fsd.vpdFAT_pEASFT.Segmt,0   ; already hogged?
        jz      hef10                                   ; no, go hog it

; EA file is already hogged. We end up here because the user opened and closed
; the drive door with the same disk (potentially modified) in it. We indicate
; this by setting the dooropen bit so SetupFatEA will throw away the current
; (potentially invalid) SFT and clustermap and re-open the EA file again.

        or      ds:[bx].vpb_fsd.vpdFAT_eaflags,eavpb_dooropen
        jmp     short hefx

hef10:  mov     ds:[bx].vpb_fsd.vpdFAT_eaflags,0        ; reset flag

; create dummy sft

        CALLFAR SFNAlloc                                ; es:di -> sft
        jc      hefx                                    ; no sft; go panic

; save EA sft in the VPB for later use

        mov     bx,hef_hVPB
        VPBContext ds,bx
        mov     ds:[bx].vpb_fsd.vpdFAT_pEASFT.Offst,di
        mov     ds:[bx].vpb_fsd.vpdFAT_pEASFT.Segmt,es

; fill in SFT

        mov     es:[di].sf_ref_count,1          ; is this correct?
        mov     es:[di].sf_FSC.Segmt,0
        mov     es:[di].sf_FSC.Offst,FSC_LOCAL
        mov     es:[di].sf_fsi.sfi_hVPB,bx
        mov     es:[di].sf_fsi.sfi_mode2,0
        mov     es:[di].sf_fsd.sfdFAT_EAHandle,0    ; Indicate that there are
                                                    ; no EAs for this file

; note: it is very important to mark the ea file clean. Otherwise, if we call
; shutdown before we do any ea write, it will try to flush this invalid sft,
; which probably will result in a clobbered boot sector. This is found out the
; hard way.

        mov     es:[di].sf_flags,SF_NOJFN or DEVID_FILE_CLEAN
                                                ; skip jfn check in MFTclose

; copy name into sfdFAT_name

        DosContext <ds>,,noassume
        lea     si,EADirName                    ; ds:si -> ea file name
        mov     cx,11                           ; dir entry file name length
        lea     di,[di].sf_fsd.sfdFAT_name      ; es:di -> sfdFAT_name
        rep     movsb                           ; copy name into SFT
        call    EA_EnterShare

hefx:   LeaveProc
        ret

EndProc HogEAFile


;**     UnHogEAFile - remove EA file from sharer
;
;       This procedure should be called when a FAT vpb is going away to remove
;       the ea file from the sharer and to de-allocate the ea sft.
;
;       ENTRY   bx = hVPB of a FAT volume
;
;       EXIT    if 'NC' - success
;               if 'CY' - error; EA sft not freed
;                 ax = error code

Procedure UnHogEAFile,faronly
    assume ds:nothing,es:nothing,ss:TASKAREA


        VPBContext ds,bx                                ; ds:bx -> vpb
ifdef FSSTRICT
        cmp     ds:[bx].vpb_FSC.Offst,FSC_LOCAL         ; FAT?
        jne     uheie1                                  ; no, go panic

        test    ds:[bx].vpb_fsd.vpdFAT_eaflags,eavpb_changing
        jnz     uheie2                                  ; pending EA operation?

        cmp     ds:[bx].vpb_fsd.vpdFAT_eareaders,0      ; any reads in progress?
        jnz     uheie4                                  ; yes, go panic
endif

        les     di,ds:[bx].vpb_fsd.vpdFAT_pEASFT        ; es:di -> ea sft

        mov     ax,es
        or      ax,ax                                   ; ea file hogged?
        jz      uhe20                                   ; no, return

        mov     es:[di].sf_ref_count,0
        push    bx
        TaskContext <ds>,,noassume
        mov     bx,-1           ; no handle; we won't have dup handles to
                                ; this file - so no need to pass handles
        CALLFAR MFTclose        ; remove from sharer, deallocate cluster map
        pop     bx
        jc      uhexi

        push    bx
        CALLFAR SFLinkFree
ifdef FSSTRICT
        jc      uheie3
endif
        pop     bx
        push    ds
        VPBContext ds,bx                                ; ds:bx -> vpb
        xor     ax,ax
        mov     ds:[bx].vpb_fsd.vpdFAT_pEASFT.Offst,ax
        mov     ds:[bx].vpb_fsd.vpdFAT_pEASFT.Segmt,ax
        mov     ds:[bx].vpb_fsd.vpdFAT_eaflags,ax
        mov     ds:[bx].vpb_fsd.vpdFAT_eareaders,ax
        pop     ds
uhe20:  clc
uhexi:  ret

ifdef FSSTRICT
uheie1: InternalError <UnHogEAFile: lean>               ; not fat
uheie2: InternalError <UnHogEAFile: write>
uheie3: InternalError <UnHogEAFile: sft?>
uheie4: InternalError <UnHogEAFile: read>
endif

EndProc UnHogEAFile


;**     EA_EnterShare - enter EA file name in sharer
;
;       Put EA file name in sharer to prevent access by other threads.
;       Caller must provide SFT and make sure the volume is FAT-for-sure.
;
;       There's no EA_RemoveShare. Just call MFTclose.
;
;       ENTRY   bx = hVPB (the vpdFAT_pSFT field must be filled in)
;
;       EXIT    if 'CY' - error
;                 ax = error code
;               if 'NC' - success
;
;       USES    ax,bx,cx,dx,si,di,ds,es,flags

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

LocalVar ens_hVPB,WORD
LocalVar ens_WFP,cbEAFile               ; "A:EA DATA. SF"

        EnterProc <bx>
        VPBContext ds,bx                ; es:bx -> vpb

ifdef FSSTRICT
        test    ds:[bx].vpb_fMisc,VPB_FM_UNKNOWN        ; unknown volume?
        LJNZ    ensie1                                  ; yes, go panic

        cmp     ds:[bx].vpb_FSC.Offst,FSC_LOCAL         ; FAT?
        LJNE    ensie2                                  ; no, go panic
endif

; get drive number

        mov     bx,ens_hVPB                     ; bx = hVPB
        VPBContext ds,bx                        ; ds:bx -> VPB
        lds     si,ds:[bx].vpb_fsi.vpi_pDPB     ; ds:si -> DPB
        mov     al,ds:[si].dpb_drive            ; al = 0-based drive number
        add     al,'A'                          ; al = drive letter

; copy EA filename into WFP

        DosContext <ds>
        lea     si,EAFile+1             ; ds:si -> ea file name skip drive
        TaskContext <es>
        lea     di,ens_WFP              ; es:di -> ens_WFP
        mov     cx,cbEAFile-1           ; cx = file name - drive letter length
        stosb                           ; store drive letter; advance di
        rep     movsb                   ; copy rest of file name
    assume es:nothing,ds:nothing

; save global variables before we destroy them

        TaskContext <ds>                ; required by MFT_Enter
                                        ;72417 moved from PTDA to TCB
        mov     si,[CurrTCB]
        push    TASKAREA:[si].TCBWFP_Start
                                        ;72417 end
        push    TASKAREA:[si].TCBThishVPB
        push    TASKAREA:[si].TCBThisSFT.Offst
        push    TASKAREA:[si].TCBThisSFT.Segmt

; setup tcb variables for MFT_Enter

        mov     TASKAREA:[si].TCBThishVPB,bx            ; needed by MFT_Enter
        VPBContext es,bx
        les     di,es:[bx].vpb_fsd.vpdFAT_pEASFT        ; es:di -> sft
        mov     TASKAREA:[si].TCBThisSFT.Offst,di
        mov     TASKAREA:[si].TCBThisSFT.Segmt,es
        lea     ax,ens_WFP
                                        ;72417 moved from PTDA to TCB
        mov     TASKAREA:[si].TCBWFP_Start,ax                   ; [WFP_start] -> ens_WFP
                                        ;72417 end

; parameters to MFT_Enter: sfdFAT_name, user_id, proc_id

        mov     cx,1                    ; probably can be 0 or 1 or 2
        CALL32  MFT_Enter,nocheck

; restore global variables

        mov     si,[CurrTCB]
        pop     TASKAREA:[si].TCBThisSFT.Segmt
        pop     TASKAREA:[si].TCBThisSFT.Offst
        pop     TASKAREA:[si].TCBThishVPB

ifdef FSSTRICT                          ; see if WFP_Start has changed
        pushf
        lea     bx,ens_WFP
                                        ;72417 moved from PTDA to TCB
        cmp     TASKAREA:[si].TCBWFP_Start,bx
        jne     ensie3
        popf
endif

        pop     TASKAREA:[si].TCBWFP_Start
                                        ;72417 end

        LeaveProc
        ret

ifdef FSSTRICT
ensie1: InternalError <EA_EnterShare: unknown volume>
ensie2: InternalError <EA_EnterShare: not FAT>
ensie3: InternalError <EA_EnterShare: WFP_Start changed>
endif

EndProc EA_EnterShare

HIGH4CODE       ENDS

        END
