SCCSID  equ     "@(#)disk.asm   6.26 92/03/11"
;
;        IBM/Microsoft Confidential
;
;        Copyright (c) IBM Corporation 1987, 1989
;        Copyright (c) Microsoft Corporation 1987, 1989
;
;        All Rights Reserved
;
TITLE   DISK - Disk utility routines
;
; Low level read and write routines for local SFT I/O on files and devs
;
;       Global          Local
;
;       EndTransfer
;       FSD_Read
;       DOS_Read
;                       ReadDev
;                       DevReadRaw
;                       EndReadDev
;                       CRDFERR
;       FSD_Write_NT
;       FSD_Write
;                       SpoolWrite
;       DOS_Write
;       WriteDev
;       DevWriteRaw
;       EndWriteDev
;       DirRead
;       FirstCluster
;       SET_BUF_AS_DIR
;       SetupForIO
;       SetupForIO_EA
;       Read_Lock_Violation
;       Write_Lock_Violation
;       DiskRead
;               DoDiskRead
;                       FATReadInit
;                       FATRWLockMem
;                       BreakDown
;                       DoCPDiskRead
;                       SetAccessDenied
;               SetSFT
;               UpdateSFTPosition
;       DiskUpdateFileSize
;       DiskWrite
;               DoDiskWrite
;                       FATWriteInit
;                               FileGrowth
;                       DoCPDiskWrite
;                       WrtErr
;                       Setup_PhysIO
;       ABSDRDWRT
;                       AbsSetup
;                       XAbsSetup
;                               GetVPB
;                       Get_SecSize
;   NOTE ON SWAPPER INTERACTION:
;
;       We assume that swapper will write in full sectors, but read partial
;       sectors.  We further assume that ALL file I/O done by the swapper
;       is swap I/O.  Therefore, we call DRead and DWrite to do normal or
;       bypass transfers based strictly on the current task being the
;       swapper task.
;
;   MODIFICATION HISTORY:
;     - 10/18/91 PTR B728103    Bill Madden     6.17
;       Changed XAbsSetup to only allocate the amount of memory needed to read
;       in the data, instead of defaulting to 64K.
;    - 3/11/92 PTR b734380  Steve Best take perfview pipe counters
;    - 8/10/92 PaulDev - fixes for GA printing problems with raw data
;    - 11/04/92 DEF 24687 Bill Madden - return err for ABSDRDWRT if > 32 mb. 1.7
;    - 10/29/93 DEF 74842 Catherine Bundy - changed FSD_READ to retry read if
;      read request came from a vdm and received a lock violation
;    - 11/15/93 DEF 75871 Catherine Bundy - changed FSD_READ to recognize the
;      retry delay loop, and yield between read retries due to lock violations.
;    - 12/08/93 DEF 76699 RAC - Make Perfview code optional
;    - 12/30/94 Change Team 108379 - Save/Restore ES for *.FS_READ call to
;                                    prevent traps if IFS trashing ES
;    - 03/23/95 Change Team 115855 - Save/Restore ES for *.FS_WRITE call to
;                                    prevent traps if IFS trashing ES
;    - 04/03/95 @SEC  SDD - added security hooks
;    - 11/27/95 - MLM defect 72417
;                 moved from PTDA to TCB:
;                 RetryCount, ShareRetriesLeft, RetryLoop,
;                 DirStart, DirSec, NxtClusNum

.xlist
include dosseg.inc
include basemaca.inc
include osmaca.inc
include fnofs.inc
include oldmaca.inc
include osconst.inc
.xcref
include argframe.inc
include buffer.inc
include dossymc.inc
include devsym.inc
include dirent.inc
include dpb.inc
include error.inc
include filemode.inc
include filesys.inc
include mi.inc
include mult.inc
include protmode.inc
include seldesc.inc
include virtsym.inc
include screen.inc
include sft.inc
include fsh.inc
include tkexport.inc
include mft.inc
include vpb.inc
include proc.inc
include fsc.inc
include pgexport.inc
include vmexport.inc
include curdir.inc
include filemacs.inc
include splmsgp.inc

ifdef PERFVIEW
include perfvw.inc
endif

include strat2.inc
include vmowner.inc                                                       ;whs
include harderr.inc
ifdef SECURITY
include secure2.inc
endif
.cref
.list

        ModName , FNO_DISK_ASM
        CpuMode 386

        ReferGDT        GDT_VPB,WORD

        ReferTask       CurrTCB,WORD            ; Current thread control block
                                                ;72417 removed PTDA entries now
                                                ;      in TCB
        ReferTask       ExtErr_Locus,BYTE
        ReferTask       ExtErr,WORD
        ReferTask       ProcType,BYTE

        ReferFlat       _pTCBCur,DWORD
        ReferFlat       _pPTDACur,dword

ifdef PERFVIEW
        ReferFlat       _fPerfview,dword                               ;sfb
endif
        ReferGlobal     EADirName,BYTE
        ReferGlobal     ABIOS_present,BYTE

        EXTRNFAR        BufFree
        EXTRNFAR        BufDecRef
        EXTRNFAR        BufIncRef
        EXTRNFAR        BufNext
        EXTRNFAR        BufRd
        EXTRNFAR        BigBufRd
        EXTRNFAR        BufWrt
        EXTRNFAR        CharHard
        EXTRNFAR        DevIOCall2
        EXTRNFAR        CMShrink
        EXTRNFAR        DecVPBRef
        EXTRNFAR        DRead
        EXTRNFAR        DWrite2
        EXTRNFAR        DRead2
        EXTRNFAR        DWrite
        EXTRNFAR        FlushBuf
        EXTRNFAR        FNDCLUS
        EXTRNFAR        FreeReqPacket
        EXTRNFAR        FreeTempBuffer
        EXTRNFAR        fVolumeWriteable
        EXTRNFAR        GetBuffr
        EXTRNFAR        GetTempBuffer
        EXTRNFAR        IncVPBRef
        EXTRNFAR        ReadPipe
        EXTRNFAR        SetReadNoAlloc
        EXTRNFAR        SetWriteNoAlloc
        EXTRNFAR        SegLock
        EXTRNFAR        SegUnlock
        EXTRNFAR        ShSU
        EXTRNFAR        SKPCLP
        EXTRNFAR        SuBuff
        EXTRNFAR        UsrArgFault
        EXTRNFAR        WritePipe
        EXTRNFAR        DskRead
        EXTRNFAR        DskWrite
        EXTRNFAR        GDTFree
        EXTRNFAR        SegMapVirt
        EXTRNFAR        GetThisDrv
        EXTRNFAR        InitCDS
        EXTRNFAR        WhatVolumeCheck
        EXTRNFAR        SegAlloc                                          ;whs
        EXTRNFAR        SegFree                                           ;whs
        EXTRNFAR        SegLock
        EXTRNFAR        SegUnlock
        EXTRNFAR        GetThisDrv
        EXTRNFAR        VirtToPhys
        EXTRNFAR        Volume_Alloc_Lock
        EXTRNFAR        Volume_Alloc_Unlock
        EXTRNFAR        WhatVolume
        EXTRNFAR        Yield
ifdef SECURITY
        EXTRNFAR        SecReadPre        ;@SEC
        EXTRNFAR        SecReadPost       ;@SEC
        EXTRNFAR        SecWritePre       ;@SEC
        EXTRNFAR        SecWritePost      ;@SEC
        ReferFlat       _fSec             ;@SEC
endif

        EXTRN           Allocate:NEAR
        EXTRN           Chk_Block:NEAR
        EXTRN           DirectIOAllowed:NEAR
        EXTRN           FigRec:NEAR
        EXTRN           FileCommit:NEAR
        EXTRN           IsEOF:NEAR
        EXTRN           Lock_Violation:NEAR
        EXTRN           LockMFT:NEAR
        EXTRN           LockMFTShar:NEAR
        EXTRN           NextSec:NEAR
        EXTRN           NmPipeRead:NEAR
        EXTRN           NmPipeWrite:NEAR
        EXTRN           Optimize:NEAR
        EXTRN           ProbeBuff:NEAR
        EXTRN           RelBlks:NEAR
        EXTRN           Release:NEAR
        EXTRN           TransactSpooler:NEAR
        EXTRN           UnlockMFT:NEAR
        EXTRN           UnPack:NEAR
        EXTRN           Init_VPB_Cache:NEAR
        EXTRN           UseCache:NEAR

        EXTRN32         _VMUnlock
        EXTRN32         _ioBeginTransfer
        EXTRN32         _SELConvertToLaddr
        EXTRN32         _PGLinToPageList
        EXTRN32         _BufRead
        EXTRN32         _BufWrite
        EXTRN32         CacheFlush
        EXTRN32         _BufFlushFAT

        EXTRNF32        demMapSegLenCX                          ;6.17


        FARCODE HIGH2
HIGH2CODE    SEGMENT

    ASSUME  SS:TASKAREA

PGLSTSIZE       equ     17 *  SIZE pagelist_s

BREAK <EndTransfer - cleanup after user memory validation>
;***    EndTransfer - cleanup after user memory validation
;
;       ENTRY   (TCBfIOLock)    non-zero if lock handle is valid, 0 otherwise
;               (TCBLockHndl)   contains lock handle or nothing
;
;       EXIT    none
;
;       USES    none
;
Procedure   EndTransfer,hybrid
    assume  ds:nothing,es:nothing,ss:TASKAREA

        pushf
        SaveReg <ax,bx,dx,si>
        mov     bx,CurrTCB
        mov     al,TASKAREA:[bx].TCBfIOLock ; (al) = lock validity flag
        or      al,al
        jz      etx                     ; Memory was not locked

        SaveReg <cx,ds,es>
        FlatContext <ds,es>,cx
        mov     ecx,[_pTCBCur]
        lea     ecx,[ecx].TCBLockHndl
        CCALL32 _VMUnlock,<ecx>
        or      eax,eax
        jnz     short etie

    assume ds:nothing,es:nothing
;BUGBUG donh 5-16-89: If we don't clear the FLAT assumes, MASM attempts
;to use ds instead of ss, reasoning that TASKAREA can be reached via FLAT
        dec     TASKAREA:[bx].TCBVolIONest      ; I/O no longer in progress
        mov     TASKAREA:[bx].TCBfIOLock,0
        RestoreReg <es,ds,cx>

etx:    RestoreReg  <si,dx,bx,ax>
        popf
        ret

etie:   InternalError <EndTransfer: VMUnlock failed>
EndProc     EndTransfer

CpuMode 286

Break <FSD_Read - Main read routine>

;***    FSD_Read - Main read routine>
;
;       ENTRY   [TCBThisSFT] set to the SFT for the file being used
;               [TCBDMAAdd] contains transfer address
;               [TCBJfnFlag] contains handle flags
;               (CX)    Number of bytes to read
;               (DS)    TASKAREA
;
;       EXIT    (CX)    Number of bytes read
;               Carry clear
;                  SFT Position and cluster pointers updated
;               Carry set
;                  AX is error code
;
;       USES    ax,bx,cx,dx,si,di,ds,es,flags
;
;       ALGORITHM
;
;       check read access
;       switch (sft.FSC) {
;
;         case FSD:
;           FS_Read
;           ret
;
;         case FAT:
;           DOS_Read
;           ret
;       }

Procedure   FSD_Read,<hybrid, far16>
    ASSUME      ES:NOTHING,ds:TASKAREA

        SegAssert SS,<DS>,"FSD_Read"
        mov     di,CurrTCB

ifdef SECURITY
; The security subsystem may need to open the file even if the file was
; opened for write only access  @SEC
        test    TASKAREA:[di].TCBSecFlags, TCBSEC_NOCHECK                 ;@SEC
        les     di,TASKAREA:[di].TCBThisSFT ; es:di -> sft
        jnz     fsr00                                                     ;@SEC
endif

;                                                                          ;### B728205
; Check if opening spooled file                                            ;### B728205
;                                                                          ;### B728205
        test    es:[di].sf_flags2, SFF2_SPOOLED     ; Spooled file?        ;### B728205
        jnz     fsr00                               ; YES, Skip mode check ;### B728205
;
; Verify that the SFT has been opened in a mode that allows reading.
;
        MOV     AL,BYTE PTR ES:[DI.sf_fsi.sfi_mode]
        AND     AL,open_access
        CMP     AL,open_for_write
    .386p
        JE      fsr50                   ; can't write, go error exit
    CpuMode reset

;       (cx)    count of bytes to read
;       (es:di) points to sft
fsr00:                                                                  ;### B728205
        clc
        jcxz    fsrx                     ; no bytes to read, go exit

ifdef SECURITY
;@SEC
; Call the security Subsystem to see if this read is OK with them.        ;@SEC
; ES:DI       -> SFT                                                      ;@SEC
; CX          -> Byte count                                               ;@SEC
; [TCBDMAAdd] -> buffer                                                   ;@SEC
        SaveReg <DS>                                                      ;@SEC
        CpuMode 386                                                       ;@SEC
        FlatContext <DS>,AX                                               ;@SEC
        xor     ax,ax                                                     ;@SEC
        cmp     _fSec,al                                                  ;@SEC
        jz      fsr05                                                     ;@SEC

        CALLFAR SecReadPre                                                ;@SEC
        jnc     fsr05                                                     ;@SEC
        ; Carry Set, AX has error, Clear Bytes read, and leave            ;@SEC
        xor     cx,cx                                                     ;@SEC
        stc                                                               ;@SEC
        pop     DS                                                        ;@SEC
        ret                                                               ;@SEC

fsr05:                                                                    ;@SEC
        RestoreReg <DS>                                                   ;@SEC
        CpuMode reset                                                     ;@SEC
endif


; dispatch to fsd or fat

        cmp     es:[di].sf_FSC.Offst,FSC_LOCAL ; what file system?
        jb      fsr20                   ; fsd, go call fsd worker

fsr1:   mov     al,RLR_READ_ACCESS      ; indicate read operation

; ES:DI -> SFT
; CX = byte count
; AL = type of access to region 0 = exclusive/write 1 = read/shared

        call    Chk_Block               ; check for locks on range
        JNC     fsr2                    ; There are no lock violations

        call    Read_Lock_Violation
        JNC     fsr1                    ; User wants more retries
        jmp     short fsrx

fsr2:   CALLFAR DOS_Read                ; cx = bytes read
fsrx:   jmp     fsr60                   ;ceb 74842


; if the FSD does its own lock processing, don't check for locks.
fsr20:  SaveReg <DS>
        lds     si,es:[di].sf_FSC       ; DS:SI-> FSC
        lds     si,[si].FS_ATTRIBUTE    ; DS:SI-> Attribute word for FSD
        test    word ptr [si],FS_ATTR_LOCKINFO   ; FSD does lock processing?
        RestoreReg <DS>                 ; DS = TASKAREA
        jnz     fsr30                   ; yes, skip local lock check

    assume ds:nothing

fsr21:  mov     al,RLR_READ_ACCESS      ; indicate read operation

; ES:DI -> SFT
; CX = byte count
; AL = type of access to region 0 = exclusive/write 1 = read/shared

        call    Chk_Block               ; check for locks on range
        JNC     fsr30                   ; There are no lock violations

        call    Read_Lock_Violation
        JNC     fsr21                   ; User wants more retries
        jmp     fsr60                   ;ceb 74842

; setup paremeters and call FS_Read

fsr30:
        push    es                      ; 108397 - push in case ifs trashes es
        push    cx                      ; save for retries        ceb 75914
        push    cx                      ; (tos) = byte count
        mov     bx,sp                   ; ss:bx -> byte count
        lea     ax,es:[di].sf_fsi       ; es:ax -> sf_fsi
        push    es
        push    ax                      ; push sf_fsi
        lea     ax,es:[di].sf_fsd       ; es:ax -> sf_fsd
        push    es
        push    ax                      ; push sf_fsd
        mov     si,CurrTCB              ; ss:si -> TCB
        push    TASKAREA:[si].TCBDMAAdd.Segmt
        push    TASKAREA:[si].TCBDMAAdd.Offst ; push result buffer address
        push    ss
        push    bx                      ; push pointer to byte count
        get_advise                      ; ax = advisory bits
        push    ax                      ; push IOFlag

        lds     si,es:[di].sf_FSC       ; ds:si -> fsc

;***PV  PerfView - thread file I/O wait time
;                  file count of reads
;                  file read time
;
;       Thread hook 7 (instance 0) as described in DCR 614.
;       File hooks 1 and 2 as described in DCR 614.
;
;       pTCBCur points to the TCB.
;       pTCBCur->TCBThisSFT points to the SFT.

ifdef PERFVIEW
        pushf                                                           ;sfb
        CpuMode 386                                                     ;sfb
        SaveReg <es>                                                    ;sfb
        FlatContext <es>                                                ;sfb
        cmp     es:_fPerfview,0         ; check if perfview is up       ;sfb
        je      short fsr35                                             ;sfb
        PvwStartTm thd,FileIOWait,0     ; Start timer
fsr35:                                                                  ;sfb
        CpuMode Reset                                                   ;sfb
        ASSUME  ES:NOTHING                                              ;sfb
        RestoreReg <es>                                                 ;sfb
        popf                                                            ;sfb
endif ;PERFVIEW
        call    ds:[si].FS_READ

        pop     cx                      ; cx = bytes read           ceb 75914
        xor     bx,bx                   ; check for error message   ceb 75914
        cmp     bx,ax                   ; ceb 75914
        pop     bx                      ; ceb 75914
        pop     es                      ; 108397
        jnc     fsr38                   ; ceb 75914
        call    Network_Retry           ; ceb 75914
        JNC     fsr30                   ; User wants more retries  ceb 74842
        jmp     fsr39                   ; ceb 75914

fsr38:                                  ; ceb 75914
                                        ;72417 moved from PTDA to TCB
        SetBP_CurrTCB
        push    [bp].TCBRetryCount      ; ceb 75914  reset shareretriesleft
        pop     [bp].TCBShareRetriesLeft  ; ceb 75914
        UnSetBP_CurrTCB
                                        ;72417 end

fsr39:                                  ; ceb 75914
;***PV  PerfView - thread file I/O wait time
;                  file read time
;                  file count of bytes read
;
;       Thread hook 7 (instance 0) as described in DCR 614.
;       File hooks 2 and 3 as described in DCR 614.
;
;       pTCBCur points to the TCB.
;       pTCBCur->TCBThisSFT points to the SFT.
;       If carry clear, then (CX) = number of bytes read

ifdef PERFVIEW
        pushf                                                           ;sfb
        CpuMode 386                                                     ;sfb
        SaveReg <es>                                                    ;sfb
        FlatContext <es>                                                ;sfb
        cmp     es:_fPerfview,0         ; check if perfview is up       ;sfb
        je      short fsr40                                             ;sfb
        PvwEndTm thd,FileIOWait,0       ; Stop timer
fsr40:                                                                  ;sfb
        CpuMode Reset                                                   ;sfb
        ASSUME  ES:NOTHING                                              ;sfb
        RestoreReg <es>                                                 ;sfb
        popf                                                            ;sfb

endif ;PERFVIEW
ifdef SECURITY
        pushf
        CpuMode 386
        SaveReg <es>
        FlatContext <es>
; Call the security Subsystem to give it a chance to decrypt the buffer   ;@SEC
; ES            -> FLAT                                                   ;@SEC
; CX            -> Byte count                                             ;@SEC
; [TCBDMAAdd]   -> buffer                                                 ;@SEC
; [TCBThisSFT]  -> Sft                                                    ;@SEC
; AX            -> Return Code (Only if Carry Set)                        ;@SEC
                                                                          ;@SEC
        jnc     fsr40a                                                    ;@SEC
        xor     ax,ax        ; If no error clear RC                       ;@SEC
                                                                          ;@SEC
fsr40a: cmp     es:_fSec,0    ; Is security enabled?                      ;@SEC
        jz      fsr40b                                                    ;@SEC
        CALLFAR SecReadPost  ; No effect on AX,CX                         ;@SEC

fsr40b: CpuMode Reset                                                     ;@SEC
        ASSUME  ES:NOTHING                                              ;sfb
        RestoreReg <es>                                                 ;sfb
        popf                                                            ;sfb

endif

;        jmp short fsr60                        ;ceb 74842
         ret

        ;@SEC This error happens before we've audited the read starting
        ;@SEC so don't audit it afterward.
fsr50:  CALLFAR SetAccessDenied
fsr60:                                         ;ceb 74842
ifdef SECURITY
        ; Error Exit, but AFTER we have Security Audited the Read Starting;@SEC
        ; AX     -> Return Code iff cary set                              ;@SEC
        ; CY     -> Its set                                               ;@SEC
        ; [TCBDMAAddr]                                                    ;@SEC
        ; [TCBThisSFT]                                                    ;@SEC
        ; CX     -> Zero for No Bytes Read                                ;@SEC
                                                                          ;@SEC
        pushf                     ; Save Carry Flag                       ;@SEC
        jc      fsr65                                                     ;@SEC
        xor     ax,ax             ; Clear rc if no error                  ;@SEC
fsr65:  SaveReg     <es>                                                  ;@SEC
        FlatContext <es>          ; is bx free for this?                  ;@SEC
        cmp     _fSec,0                                                   ;@SEC
        jz      fsr70                                                     ;@SEC
        CALLFAR SecReadPost                                               ;@SEC
        ; BUGBUG the function headers says I don't have to preserve ES    ;@SEC
        ;        should I trust it?                                       ;@SEC
fsr70:  RestoreReg <es>                                                   ;@SEC
        popf                      ; restore carry flag                    ;@SEC
endif ; SECURITY
        ret

EndProc     FSD_Read


Break <FSD_Write, FSD_Write_NT - Main write routine and device output routines>

;***    FSD_Write - Main disk and device write routine, with truncation
;
;       FSD_Write is the "original" code.  A length 0 write (CX=0) will
;       do a truncate or extend.  New file size will equal current file
;       position.
;
;
;***    FSD_Write_NT - Main disk and device routine, WITHOUT truncation
;
;       FSD_Write_NT is new for OS/2.  A length 0 write (CX=0) simply returns.
;
;       Except for the truncate/extend versus no-op return difference,
;       FSD_Write and FSD_Write_NT are identical.
;
;       ENTRY   [TCBThisSFT] set to the SFT for the file being used
;               [TCBDMAAdd]  contains transfer address
;               [TCBJfnFlag] jfn flag (used for doing write-through)
;               CX = No. of bytes to write
;               DS = TASKAREA
;
;       EXIT    Carry Clear
;                       SFT Position and cluster pointers updated
;                       CX = No. of bytes written
;               Carry Set
;                       AX is error code
;                       CX = 0
;
;       USES    ax,bx,cx,dx,si,di,ds,es,flags
;
;       ALGORITHM
;
;       check access
;       if (write count == 0 && no truncate)
;         return 0
;       if (direct acces) {
;         update sft size and position fields
;         error check size
;         if (write count == 0) {
;           return 0
;         }
;       } else {
;         check access if fcb case
;       }
;       switch (sft)
;         case fsd:
;           FS_Write
;           break
;         case fat:
;           DOS_Write
;           break
;       }
;       make sure cx = 0 if error

Procedure   FSD_Write_NT,<hybrid,far16> ; If we enter here, CX=0 -> no-op
    ASSUME      ES:NOTHING,DS:TASKAREA
        LocalVar  sec_BytesRequested, word
        LocalVar  sec_PreCalloutRejected, byte  ; Flag if WritePre rejects        ;@SEC

        xor     si,si                   ; no truncation for this entry point
        xor     ax,ax                   ; not a Spool Write
        jmp     short fsw10

Entry   FSD_WriteNoSpool,near,local     ; entry for spool write
        xor     ax,ax
        inc     ax                      ; ax =1 for spool write
        xor     si,si                   ; no truncation allowed
        jmp     short fsw10


;**     FSD_Write

Entry   FSD_Write,hybrid                ; If we enter here, CX=0 -> truncate
        mov     si,1                    ; truncation allowed for this entry
        xor     ax,ax                   ; Not a Spool Write


fsw10:  EnterProc <cx>                                                    ;@SEC
        or      ax,ax
        jnz     fsw15                   ; if spool write, go to it

ifdef SECURITY
        FlatContext <DS>                                                  ;@SEC
        ; AX = 0
        cmp     _fSec,al                ; Is security enabled?            ;@SEC
        jz      short fsw12             ; No, skip callout                ;@SEC
        mov     sec_PreCalloutRejected,al  ; Initialize (not rejected)    ;@SEC
        CALLFAR SecWritePre             ; notify security subsystem       ;@SEC
        jnc     fsw12                   ; security said ok, proceed       ;@SEC
        jmp     short fsw25                                               ;@SEC

fsw12:  TaskContext <DS>                                                  ;@SEC
else   ; NOT SECURITY
        SegAssert SS,<DS>,"FSD_Write"
endif  ; NOT SECURITY
        mov     di,CurrTCB
        les     di,TASKAREA:[di].TCBThisSFT

        test    es:[di].sf_flags2, SFF2_SPOOLED     ; Spooled file?       ;whs
        jz      fsw15                               ; no, continue        ;whs
        call    SpoolWrite              ; Comm. with spooler if necessary ;whs
        jmp     fsw110                  ; return                          ;whs
                                                                          ;whs
fsw15:                                                                    ;whs
        ; @SEC for security reasons the only routine allowed to call this ;@SEC
        ; is SpoolWrite (which has already gone throught the security     ;@SEC
        ; check at fsw10).                                                  ;@SEC
        ; 122222 entry moved to top of routine and jumps to here
        ; this is so stack frame is setup correctly
;Entry   FSD_WriteNoSpool,near,local    ; entry after spooler checks      ;@SEC
                                        ; cx = byte count                 ;whs
                                        ; es:di -> SFT                    ;whs
                                                                          ;whs
        MOV     AL,BYTE PTR ES:[DI.sf_fsi.sfi_mode]                       ;whs
        AND     AL,open_access
        CMP     AL,open_for_read
        JE      fsw25                   ; invalid access, go error exit

; if write count == 0 and entered from FSD_Write_NT, then do nothing

        or      si,cx           ; write count == 0 and entered from Write_NT?
        jnz     fsw30           ; no, go do next check

fsw20:  xor     ax,ax           ; yes, we wrote 0 bytes correctly (NO-OP)
ifdef   SECURITY
        jmp     fswx            ; route to security b4 returning          ;@SEC
else
        ret
endif

fsw25:  CALLFAR SetAccessDenied
ifdef   SECURITY
        jmp     fswx            ; route to security b4 returning          ;@SEC
else
        ret
endif

;
;   At this point, we check for writes to direct disk access SFTS.
;   If this is a direct access SFT, we set cx=min(cx,space-to-end-of-disk),
;   because it makes no sense to change the size of a disk device.
;
fsw30:  test    es:[di].sf_fsi.sfi_mode,open_direct ; direct access SFT?
        jz      fsw40                   ; normal SFT, continue normal work
;
; Check to see if this handle has been locked for direct access write
; operations. If not, we fail the operation.
;
        test    es:[di].sf_flags2,SFF2_LOCKED_DRIVE
        jnz     fsw35                   ; Drive is locked. Continue.
        STC
        mov     ax,ERROR_ACCESS_DENIED
        jmp     fsw110                  ; Return access_denied.

;
;   NOTE    Since we got here, we know this SFT doesn't support an FCB,
;           because open_direct and FCBs are mutually exclusive
;
;   NOTE    Since we're doing direct I/O to the disk, "free" doesn't
;           mean anything.  however, sf_fsi.sfi_size-sf_fsi.sfi_position ("free space")
;           is the upper limit for writes not running off the end of
;           the disk.
;
fsw35:  mov     ax,es:[di].sf_fsi.sfi_size.LowWord
        mov     dx,es:[di].sf_fsi.sfi_size.HighWord        ;DX:AX = # bytes on disk
        sub     ax,es:[di].sf_fsi.sfi_position.LowWord
        sbb     dx,es:[di].sf_fsi.sfi_position.HighWord    ;DX:AX = # bytes "free"

;       If dx > 0, we have at least 10000 bytes "free", since
;       maximum transfer length in CX is 0ffffh, we always have enough
;       space if dx > 0.

        or      dx,dx
        jnz     fsw50                   ; tran. len. ok, go do write

;       dx = 0, so ax = # free bytes

        cmp     cx,ax                   ; will write overrun end of disk?
        jbe     fsw50                   ; no overrun, go do write

;       cx is too large, limit length to avoid overruning the disk

        mov     cx,ax                   ; cx = min(cx, free space)
        or      cx,cx                   ; zero length output? (ie out of space?)
        jnz     fsw50                   ; not out of space, go do output

;       it's a 0 length operation, report success

        jmp     short fsw20             ; report no error, no bytes written
                                        ; (tried to write at end of disk)
;
; NOTE: The following check for writing to a Read Only File is performed
;           ONLY on FCBs!!!!
;       We ALLOW writes to Read Only files via handles to allow a CREATE
;           of a read only file which can then be written to.
;       This is OK because we are NOT ALLOWED to OPEN a RO file via handles
;           for writting, or RE-CREATE an EXISTING RO file via handles. Thus,
;           CREATing a NEW RO file, or RE-CREATing an existing file which
;           is NOT RO to be RO, via handles are the only times we can write
;           to a read-only file.
;
fsw40:  test    es:[di].sf_flags,SF_FCB
        jz      fsw50                   ; Not an FCB
        test    es:[di].sf_fsi.sfi_DOSattr,attr_read_only
        jnz     fsw25                   ; Can't write to Read_Only files via FCB

; dispatch to fsd or fat

fsw50:  cmp     es:[di].sf_FSC.Offst,FSC_LOCAL ; what file system?
        jb      fsw70                   ; fsd, go call fsd worker
;
; Check to make sure that no other process has locked any part of the file
; where we are going to do the write.
;
        SaveReg <CX>                    ; preserve byte count
        or      cx,cx                   ; is this a truncate?
        jnz     fsw51
        mov     cx,0FFFFH               ; Check for lock on WHOLE file.
fsw51:  mov     al,RLR_WRITE_ACCESS     ; indicate write operation

; ES:DI -> SFT
; CX = byte count
; AL = type of access to region 0 = exclusive/write 1 = read/shared

        call    Chk_Block               ; check for locks on range
        RestoreReg <CX>                 ; restore byte count
        JNC     fsw52
        call    Write_Lock_Violation
        JNC     fsw51                   ; User wants more retries
        JMP     fswx

fsw52:
        push    bp                      ; @SEC.. save stack frm
        CALLFAR DOS_Write               ; cx = bytes written
        pop     bp

; If a spooled file was sent to a printer the pmspooler will have
; set the nonspooled flag which will propogated in the sft.
; If an error occurred we must not update cx (bytes written).
        LJNC    fswx                                   ;### B729771
        TaskContext <ds>
        mov     di,CurrTCB                  ;###
        les     di,TASKAREA:[di].TCBThisSFT            ;### B729771
        test    es:[di].sf_flags2, SFF2_NONSPOOLED     ; Write was to printer ;### B729771
        stc                                            ; test clears carry    ;### B729771
        LJNZ    fswx                                   ; Leave cx alone       ;### B729771
        jmp     fsw110                                                        ;### B729771

; if the FSD does its own lock processing, don't check for locks.

fsw70:  SaveReg <DS>
        lds     si,es:[di].sf_FSC       ; DS:SI-> FSC
        lds     si,[si].FS_ATTRIBUTE    ; DS:SI-> Attribute word for FSD
        test    word ptr [si],FS_ATTR_LOCKINFO   ; FSD does lock processing?
        RestoreReg <DS>                 ; DS = TASKAREA
        jnz     fsw72                   ; yes, skip local lock check

    assume ds:nothing
;
; Check to make sure that no other process has locked any part of the file
; where we are going to do the write.
;
fsw71:  mov     al,RLR_WRITE_ACCESS     ; indicate write operation

; ES:DI -> SFT
; CX = byte count
; AL = type of access to region 0 = exclusive/write 1 = read/shared

        call    Chk_Block               ; check for locks on range
        JNC     fsw72
        call    Write_Lock_Violation
        JNC     fsw71                   ; User wants more retries
        JMP     fswx
fsw71a:                                                                 ;sfb
        JMP     fsw90                                                  ;sfb
; setup paremeters and call FS_write

fsw72:  push    cx                      ; save this for retries    ceb 75914
        push    es                      ; 115855
        push    cx                      ; (tos) = byte count
        mov     bx,sp                   ; ss:bx -> byte count
        lea     ax,es:[di].sf_fsi       ; es:ax -> sf_fsi
        push    es
        push    ax                      ; push sf_fsi
        lea     ax,es:[di].sf_fsd       ; es:ax -> sf_fsd
        push    es
        push    ax                      ; push sf_fsd
;sfb    jcxz    fsw90                   ; if byte count = 0, call FS_NewSize
        jcxz    fsw71a                  ; if byte count = 0, call FS_NewSize

        mov     si,CurrTCB              ; ss:si -> TCB
        push    TASKAREA:[si].TCBDMAAdd.Segmt
        push    TASKAREA:[si].TCBDMAAdd.Offst ; push result buffer address
        push    ss
        push    bx                      ; push pointer to byte count
        get_advise                      ; ax = advisory bits
        push    ax                      ; push IOflag

        lds     si,es:[di].sf_FSC       ; ds:si -> fsc

;***PV  PerfView - thread file I/O wait time
;                  file count of writes
;                  file write time
;
;       Thread hook 7 (instance 3) as described in DCR 614.
;       File hooks 4 and 5 as described in DCR 614.
;
;       pTCBCur points to the TCB.
;       pTCBCur->TCBThisSFT points to the SFT.

ifdef PERFVIEW
        pushf                                                           ;sfb
        CpuMode 386                                                     ;sfb
        SaveReg <es>                                                    ;sfb
        FlatContext <es>                                                ;sfb
        cmp     es:_fPerfview,0         ; check if perfview is up       ;sfb
        je      short fsw80                                             ;sfb
        PvwStartTm thd,FileIOWait,3     ; Start timer                   ;sfb
fsw80:                                                                  ;sfb
        CpuMode Reset                                                   ;sfb
        ASSUME  ES:NOTHING                                              ;sfb
        RestoreReg <es>                                                 ;sfb
        popf                                                            ;sfb
endif ;PERFVIEW

        call    ds:[si].FS_WRITE

        pop     cx                      ; cx = bytes read
        pop     es                      ; 115855
        xor     bx,bx                   ; check for error message
        cmp     bx,ax
        pop     bx                      ; bx = original bytes to read
        jnc     fsw83
        call    Network_Retry           ; ceb 75914
        JNC     fsw72                   ; User wants more retries  ceb 74842
        jmp     fsw84
fsw83:
                                        ;72417 moved from PTDA to TCB
        SetBP_CurrTCB
        push    [bp].TCBRetryCount             ; ceb 74842  reset shareretriesleft
        pop     [bp].TCBShareRetriesLeft       ; ceb 74842
        UnSetBP_CurrTCB
                                        ;72417 end
fsw84:
;***PV  PerfView - thread file I/O wait time
;                  file write time
;                  file count of bytes written
;
;       Thread hook 7 (instance 3) as described in DCR 614.
;       File hooks 5 and 6 as described in DCR 614.
;
;       pTCBCur points to the TCB.
;       pTCBCur->TCBThisSFT points to the SFT.
;       If (AX) = NO_ERROR, then (CX) = number of bytes written


ifdef PERFVIEW
        pushf                                                           ;sfb
        CpuMode 386                                                     ;sfb
        SaveReg <es>                                                    ;sfb
        FlatContext <es>                                                ;sfb
        cmp     es:_fPerfview,0         ; check if perfview is up       ;sfb
        je      short fsw85                                             ;sfb
        PvwEndTm thd,FileIOWait,3       ; Stop timer
fsw85:                                                                  ;sfb
        CpuMode Reset                                                   ;sfb
        ASSUME  ES:NOTHING                                              ;sfb
        RestoreReg <es>                                                 ;sfb
        popf                                                            ;sfb
endif ;PERFVIEW

        jmp     short fswx              ;  ceb 75914

; setup parameters and call FS_NewSize

fsw90:  push    es:[di].sf_fsi.sfi_position.hiword ; new file size hi word
        push    es:[di].sf_fsi.sfi_position.loword ; new file size low word
        get_advise                      ; ax = advisory bits
        push    ax                      ; push IOflag
        lds     si,es:[di].sf_FSC       ; ds:si -> fsc
        call    ds:[si].FS_NEWSIZE

        pop     cx                      ; (CX) = 0 for FS_NewSize
        pop     es                      ; 115855
        pop     bx                      ; ceb 75914
fsw100: xor     bx,bx
        cmp     bx,ax                   ; set carry if ax != 0

fsw110: jnc     fswx                    ; no error, return cx = bytes written
        mov     cx,0                    ; error, return cx = 0

ifdef   SECURITY
fswx:   pushf                           ; save stupid carry flag          ;@SEC
        mov     bx,sec_BytesRequested   ; get initial byte count          ;@SEC
        jc      fswx1                                                     ;@SEC
        xor     ax,ax                   ; set AX=0 if no error            ;@SEC
fswx1:  FlatContext <ds>,dx                                               ;@SEC
        cmp     _fSec,0                                                   ;@SEC
        jz      fswxx                                                     ;@SEC
        ; [TCBDMAAddr] -> User buffer                                     ;@SEC
        ; [TCBThisSFT] -> SFT to be used                                  ;@SEC
        ; AX           -> Return Code user will see                       ;@SEC
        ; BX           -> Byte count of initial request                   ;@SEC
        ; CX           -> 0 or BytesWritten depending on AX               ;@SEC
        CALLFAR SecWritePost            ; presereves only AX,CX           ;@SEC

fswxx:  popf                            ; restore carry flag              ;@SEC
        LeaveProc                                                         ;@SEC
        ret
else ; not SECURITY
        LeaveProc                                                         ;@SEC
fswx:   ret
endif

EndProc     FSD_Write_NT

;***    SpoolWrite - Take proper actions if file is spooled
;
;       ENTRY   ES:DI -> SFT of spooled file
;               CX    =  bytes to write
;
;       EXIT    Carry Clear
;                       No error
;               Carry Set
;                       Error
;
;       USES:   AX, Flags
;
;       Algorithm:
;            Do a zero byte write if job was canceled
;            Tell Spooler on first write
;            Write data directly to the disk file (not the printer)
;            Tell Spooler if error occurred
;            retry or return error
;
Procedure SpoolWrite, <near>
        ASSUME  DS:TASKAREA,ES:NOTHING,SS:TASKAREA

LocalVar  ww_ByteCount, word                ; Requested byte count

        CpuMode 286

; just ignore writes if the job has been cancelled

        test    es:[di].sf_flags2, SFF2_CANCELJOB
        jz      spw10
        xor     cx,cx                   ; don't write anything
spw10:

        EnterProc <cx>                  ; save byte count in local varible

; tell spooler on first writes

        test    es:[di].sf_flags2, SFF2_DATAWRITTEN
        jnz     spw20
        jcxz    spw20                   ; ignore 0 byte writes
        or      es:[di].sf_flags2, SFF2_DATAWRITTEN ; only do next call once
        call    SpoolFirstWrite
        jc      spw50
spw20:

; write the data to the file
spw30:
        SegAssert SS,<DS>,"SpoolWrite"
        mov     cx, ww_ByteCount        ; get entry byte count
        mov     di,CurrTCB
        les     di,TASKAREA:[di].TCBThisSFT
        push    bp
        call    FSD_WriteNoSpool
        TaskContext <ds>
        pop     bp
        jc      spw50

        cmp     cx, ww_ByteCount    ; all bytes written? (file full?)
        je      spw99               ; return NC

; Hummm?, probably disk full, tell spooler and find out what to do

        push    ax
        cmp     ax, 0                   ; Watch out for possible
        jne     spw45                   ;   zero return code
        mov     ax, ERROR_DISK_FULL     ;   with disk full
spw45:
        call    SpoolWriteErr
        pop     ax                      ; restore original return code
        jnc     spw30                   ; try it again
        clc                             ; restore NC
        jmp     spw60                   ; exit

; had some type of hard error, tell spooler and find out what to do
spw50:
        call    SpoolWriteErr
        jnc     spw30                   ; retry it
        mov     cx,0                    ; no bytes transfered

; cancel rest of job
spw60:
        mov     di, CurrTCB
        les     di, TASKAREA:[di].TCBThisSFT
        pushf
        or      es:[di].sf_flags2, SFF2_CANCELJOB ; ignore all furhter writes
        popf

spw99:
;   cy = error, nc= worked OK
;   AX = return code
;   CX = bytes transfered

        LeaveProc
        ret

        CpuMode Reset
EndProc SpoolWrite

;***    SpoolFirstWrite - First write to spool file
;
;       ENTRY   TASKCONTEXT DS
;
;       EXIT    Carry Clear
;                       No error
;               Carry Set
;                       Error code in AX
;
;       USES:   AX, Flags
;
;       Algorithm:
;          Tell Spooler of first write
;          Check for activation string
;          Allocate a buffer and receive string from spooler
;          Write it to the file
;          Free temporary buffer
;
Procedure SpoolFirstWrite, <near>
        ASSUME  DS:TASKAREA,ES:NOTHING,SS:TASKAREA

LocalVar  ww_TCBDMAAdd, dword               ; save/restore TCBDMAAdd
LocalVar  ww_MsgInSeg,  word                ; Dynamic inbound buffer segment
LocalVar  ww_MsgIn,  <size MSG_IN>          ; Message from spooler

        CpuMode 286
        EnterProc

;   Tell spooler of the first write

        mov     ax,HDR_FIRSTWRITE       ; command to spooler
        mov     cx, size MSG_IN         ; size and location of parms
        mov     bx, ds                  ;   to return info
        lea     dx, [ww_MsgIn]          ;   bx:dx -> ww_MsgIn
        call    SpoolXact
        jc      spf90

;   Check spooler return info to see if code page data to write

        mov     bx,[ww_MsgIn].MSGIN_Msg.MFWI_uslen
        cmp     bx, 0                               ; got some data?
        je      spf90                               ; Nope, return NC

;   Allocate a buffer to receive the code page activation string

        mov     ax,ERROR_INSUFFICIENT_BUFFER
        add     bx,size MSG_IN        ; add room for message header
        jc      spf90                 ; (must be less than 64k)
        xor     ax,ax                 ; ax:bx = size
        mov     ch,D_DATA0
        mov     cl,SA_GDT
        mov     si,HF_MOVABLE
        mov     di,FSCDEVTMPOWNER
        CALLFAR SegAlloc              ; bx:00 -> allocated segment
        jc      spf90                 ; SegAlloc failed?
        mov     ww_MsgInSeg,bx

;   Get activation string

        mov     cx,[ww_MsgIn].MSGIN_Msg.MFWI_uslen  ; inbound message size
        add     cx, size MSG_IN                     ;
        mov     dx, 0                               ; bx:dx -> message area
        mov     ax,HDR_GETCPACTIVATE                ; spooler command
        call    SpoolXact
        jc      spf80

;   Check spooler return info to see if code was returned

        mov     es, ww_MsgInSeg
        mov     cx, es:MSGIN_Msg.MACTI_uslen    ; Get CP data size
        jcxz    spf80                           ; none???? then don't write it

;   Set up TCBDMAAdd for recursive call (and remember current value)

        CpuMode 386
        mov     ax,  es
        sal     eax, 16
        mov     ax,  offset MSGIN_Msg.MACTI_bData
        mov     di,  CurrTCB
        xchg    eax, TASKAREA:[di].TCBDMAAdd
        mov     [ww_TCBDMAAdd], eax
        CpuMode Reset

;   Write the code page activation string to the file
;   cx = bytes to write

        push    bp
        les     di,TASKAREA:[di].TCBThisSFT
        call    FSD_WriteNoSpool
        TaskContext <ds>
        pop     bp

;   restore TCBDMAAdd

        CpuMode 386
        mov     ebx, [ww_TCBDMAAdd]
        mov     di,  CurrTCB
        mov     TASKAREA:[di].TCBDMAAdd, ebx
        CpuMode Reset

;   free temp buffer
spf80:
        pushf
        push    ax
        mov     ax,[ww_MsgInSeg]
        CALLFAR SegFree
        TaskContext <ds>
        pop     ax
        popf

spf90:
;   cy = error & AX = return code, nc= worked OK
        LeaveProc
        ret

        CpuMode Reset
EndProc SpoolFirstWrite


;***    SpoolWriteErr - send message to spooler, and wait for reply
;
;       ENTRY   DS: TASKAREA
;               AX = return code
;
;       EXIT    Carry Clear
;                       Retry operation, AX = don't care
;               Carry Set
;                       Abort operation, AX = error code
;
;       USES:   all regs except  DS and BP
;
;
Procedure SpoolWriteErr, <near>
        ASSUME  DS:TASKAREA,ES:NOTHING,SS:TASKAREA
LocalVar  ww_MsgIn,  <size MSG_IN>      ; Message from spooler

        EnterProc
        SaveReg <ax,cx>

;   Tell spooler of the error

        mov     si, ax                  ; pass parameter to spooler
        mov     ax, HDR_WRITEFAIL       ; command to spooler
        mov     cx, size MSG_IN         ; size and location of parms
        mov     bx, ds                  ;   to return info
        lea     dx, [ww_MsgIn]          ;   bx:dx -> ww_MsgIn
        call    SpoolXact
        jc      spe90

;   check for retry

        cmp     [ww_MsgIn].MSGIN_Msg.MFAILI_usResponse, WRITEFAIL_RETRY
        je      spe85                   ; retry, return NC
        stc                             ; return CY
spe85:

;   cy = bomb request with return code = AX, NC = retry operation
spe90:
        RestoreReg <cx,ax>
        LeaveProc
        ret

        CpuMode Reset
EndProc SpoolWriteErr

;***    SpoolXact - send message to spooler, and wait for reply
;
;       ENTRY
;               ax    =   HMO_Type
;               bx:dx =   seg:offset of output buffer
;               cx    =   size of output buffer
;               si    =   parm to spooler (or don't care value)
;
;       EXIT    Carry Clear
;                       No error
;               Carry Set
;                       Error
;
;       USES:   all regs
;
;
Procedure SpoolXact, <near>
        ASSUME  DS:TASKAREA,ES:NOTHING,SS:TASKAREA

LocalVar  ww_TCBThisSFT, dword              ; save/restore TCBThisSFT
LocalVar  ww_MsgOut, <size MSG_OUT>         ; Message to   spooler
LocalVar  ww_NmPipeStruc, <size DosTransactNmPipeStruc>

        CpuMode 286
        EnterProc

        TaskContext <ds>
        mov     di, CurrTCB
        les     di,TASKAREA:[di].TCBThisSFT
        mov     [ww_TCBThisSFT].Segmt, es
        mov     [ww_TCBThisSFT].Offst, di

;   Fill in the fields of the structure for TransactSpooler

        mov     [ww_MsgOut].MSGOUT_Header.MHO_Type, al
        mov     ax, es:[di].sf_fsi.sfi_selfsfn
        mov     [ww_MsgOut].MSGOUT_Header.MHO_uHandle, ax
        mov     word ptr [ww_MsgOut].MSGOUT_Msg, si

        mov     [ww_NmPipeStruc].tnmp_InBufferLen, size MSG_OUT
        mov     [ww_NmPipeStruc].tnmp_InBuffer.Segmt,  ds
        lea     ax, [ww_MsgOut]
        mov     [ww_NmPipeStruc].tnmp_InBuffer.Offst,  ax

        mov     [ww_NmPipeStruc].tnmp_OutBufferLen, cx
        mov     [ww_NmPipeStruc].tnmp_OutBuffer.Segmt, bx
        mov     [ww_NmPipeStruc].tnmp_OutBuffer.Offst, dx

        mov     dx, es:[di].sf_NmPipeSfn
        lea     si, [ww_NmPipeStruc]                ; SS:SI -> struct

;   send messeage to spooler and wait for reply
;    SS:SI -> TransactNmPipe structure
;    DX    == SFN (named pipe)

        call    TransactSpooler
        TaskContext <ds>

;   Restore TCBThisSFT because TransactSpooler destroys it.
;   cy = error, nc= worked OK

        CpuMode 386
        mov     eax, [ww_TCBThisSFT]
        mov     di, CurrTCB
        mov     TASKAREA:[di].TCBThisSFT,eax
        CpuMode Reset

        jc      spx90                               ; Error, get out

;   Check spooler return code

        les     si,[ww_NmPipeStruc].tnmp_OutBuffer
        mov     ax,es:[si].MSGIN_Header.MHI_Error   ; Get error code
        cmp     ax, 0                               ; spooler OK?
        je      spx90                               ; yes, return NC
        stc                                         ; no,  return with carry

spx90:
;   return
;   cy = error, nc= worked OK

        LeaveProc
        ret

        CpuMode Reset
EndProc SpoolXact



;***    Read_Lock_Violation - Handle read lock violation
;
;       ENTRY   ES:DI points to SFT.
;               DS = TASKAREA
;
;       EXIT    'NC' if we are to retry the operation
;               'C' if we are to return an error
;                   CX = 0
;                   AX = ERROR_SHARING_VIOLATION
;
;       USES    AX, BX, DX, SI, Flags
;
Procedure Read_Lock_Violation,NEAR
        ASSUME  ES:NOTHING,ds:TASKAREA

        SegAssert SS,<DS>,"Read_Lock_Violation"

ERR_ON_CHECK:
        CMP     [ProcType],PT_REALMODE  ; 3xBox?
        JNE     NO_HARD_ERR             ; not 3xBox, simply return error code

        TEST    ES:[DI].sf_flags,SF_FCB
        JNZ     HARD_ERR
        PUSH    CX
        MOV     CL,BYTE PTR ES:[DI.sf_fsi.sfi_mode]
        AND     CL,open_sharing_mode
        CMP     CL,sharing_compat
        POP     CX
        JNE     NO_HARD_ERR
HARD_ERR:
        call    Lock_Violation
        jc      NO_HARD_ERR
        CALLFAR Yield                   ; User wants to retry, yield CPU
        jmp short rlvx
        jnc     rlvx
NO_HARD_ERR:
        XOR     CX,CX                   ;No bytes transferred
        mov     ax,ERROR_LOCK_VIOLATION
        STC
rlvx:   ret
EndProc Read_Lock_Violation




;***    Write_Lock_Violation - Handle write lock violation
;
;       ENTRY   ES:DI points to SFT.
;               DS = TASKAREA
;
;       EXIT    'NC' if we are to retry the operation
;               'C' if we are to return an error
;                   CX = 0
;                   AX = ERROR_SHARING_VIOLATION
;
;       USES    AX, BX, DX, SI, Flags
;
Procedure Write_Lock_Violation,NEAR
        ASSUME  ES:NOTHING,ds:TASKAREA

        SegAssert SS,<DS>,"Write_Lock_Violation"

        JMP     ERR_ON_CHECK

EndProc Write_Lock_Violation


;***    Network_Retry - Controls file read and write retries over the network
;                       New subroutine for defect 75914
;
;       ENTRY   TASKCONTEXT DS
;
;       EXIT    Carry CLear
;                     Retry the operation
;               Carry Set
;                     Don't Retry
;
;       USES    AX - error code
;               BX - retry flag
;
;       Algorithm:
;               Check for lock violation, if no return cy
;               Is the requesting prodcedure a vdm? If no, return cy
;               check for remaining retries
;               Yield for delay between retries
;               return nc
;
;
Procedure   Network_Retry, <near>
        ASSUME  ES:NOTHING,ss:TASKAREA,ds:NOTHING

;  Check for error
        cmp     ax,33                   ;ceb 74842 is this a lock violation?
        je      nrr10                   ;ceb 74842
        jmp     short nrr50             ;ceb 74842 no, return error

;  Check for VDM                        ;ceb 74842
nrr10:                                  ;ceb 74842

        CMP     [ProcType],PT_REALMODE  ; 3xBox?     ceb 74842
        JNE     short nrr50             ; not 3xBox, simply return error code ceb 74842


;  Is the retry count exhausted?
nrr20:
                                        ;72417 moved from PTDA to TCB
        SetBP_CurrTCB
        cmp     [bp].TCBShareRetriesLeft,0  ; check if there are
        UnSetBP_CurrTCB
        jnz     short nrr30             ; still some retries left ceb 74842
        stc                             ; ceb 74842  no retries left,
        SetBP_CurrTCB
        push    [bp].TCBRetryCount      ; ceb 74842  reset shareretriesleft
        pop     [bp].TCBShareRetriesLeft  ; ceb 74842
        UnSetBP_CurrTCB
        mov     ax,ERROR_LOCK_VIOLATION
        jmp     short nrr50             ; ceb 74842  return error

;  Delay between retries
nrr30:
        SetBP_CurrTCB
        mov     cx,[bp].TCBRetryLoop    ; ceb 75871
        UnSetBP_CurrTCB
nrr40:
        CALLFAR Yield                   ; ceb 75871  delay between retries
        dec     cx                      ; ceb 75871
        jcxz    nrr45                   ; ceb 75871  go do retry
        jmp     short nrr40             ; ceb 75871  loop for more delay

nrr45:
        clc                             ; clear carry for retry  ceb 74842
        SetBP_CurrTCB
        dec     [bp].TCBShareRetriesLeft  ; check if there are
        UnSetBP_CurrTCB
                                        ;72417 end
        mov     cx,bx                   ; restore original byte count
        jmp     nrx

nrr50:  stc

nrx:
        ret

EndProc     Network_Retry

HIGH2CODE   ENDS


        FARCODE HIGH4
HIGH4CODE    SEGMENT

;**     DOS_Read - do sft read for pipe, local device, or FAT file
;
;       ENTRY   es:di       -> sft
;               cx           = byte count
;               ds           = TASKAREA
;               [TCBDMAAdd]  = destination address
;               [TCBThishVPB] = vpb
;               [TCBThisSFT] -> sft
;
;               note:
;               es:di and cx are required for all cases;
;               TCBDMAAdd are required for pipe only;
;               no specific paremeters are required for device or file
;
;       EXIT    if NC
;                 cx = number of bytes read
;                 sft updated
;               if CY
;                 ax = error code
;
;       USES    ax,bx,cx,dx,si,di,ds,es,flags
;
;       ALGORITHM
;
;       mark user segment as unlock
;       switch (sft) {
;
;         case pipe:
;           ReadPipe
;           break
;
;         case device:  /* local device */
;           ReadDev     /* may lock user segment */
;           break
;
;         default:
;           LockMFT
;           SetUpForIO
;           DiskRead    /* may lock user segment */
;           UnlockMFT
;       }
;       EndTransfer     /* unlock user segment */

Procedure DOS_Read,hybrid
    assume DS:TASKAREA, ES:NOTHING

        SegAssert SS,<DS>,"DOS_Read"
        mov     si,TASKAREA:[CurrTCB]   ; ss:si -> TCB
        mov     TASKAREA:[si].TCBfIOLock,0 ; mark user segment not locked
        test    es:[di].sf_flags,SF_PIPE +DEVID_DEVICE
        jnz     dr10

        test    TASKAREA:[si].TCBfSwapping,SM_TCB_SWAPPING
        jnz     dr10                    ; swap file doesn't need LockMFT

        SaveReg <AX>                    ; LockMFTShar trashes AX
        call    LockMFTShar             ; Put share lock on mft entry for SFT
        RestoreReg <AX>                 ; AX:BX = byte position in file

dr10:   SaveReg <bp>
        call    SetupForIO              ; ax:bx = position in file
        jc      drerr
        RestoreReg <bp>

; see note in SetupForIO for warning

        mov     dx,es:[di].sf_flags     ; dx = flags
    .386p
        or      cx,cx
        jz      dore30                  ; segment wrap error or nothing to read
    CpuMode reset

        test    dx,SF_PIPE              ; is this a pipe?
        jnz     dore10                  ; yes, go read pipe

        test    dx,DEVID_DEVICE         ; is this a device?
        jnz     dore20                  ; yes, go read device

; not pipe, not device, must be FAT file
; AX:BX = byte position in file

;***PV  PerfView - thread file I/O wait time
;                  file count of reads
;                  file read time
;
;       Thread hook 7 (instance 1) as described in DCR 614.
;       File hooks 1 and 2 as described in DCR 614.
;
;       pTCBCur points to the TCB.
;       pTCBCur->TCBThisSFT points to the SFT.

ifdef PERFVIEW
        pushf                                                           ;sfb
        CpuMode 386                                                     ;sfb
        SaveReg <es>                                                    ;sfb
        FlatContext <es>                                                ;sfb
        cmp     es:_fPerfview,0         ; check if perfview is up       ;sfb
        je      short dr15                                              ;sfb
        PvwStartTm thd,FileIOWait,1     ; Start timer
dr15:                                                                   ;sfb
        CpuMode Reset                                                   ;sfb
        ASSUME  ES:NOTHING                                              ;sfb
        RestoreReg <es>                                                 ;sfb
        popf                                                            ;sfb
endif ;PERFVIEW

        call    DiskRead                ; read FAT file
        ASSUME DS:nothing

;***PV  PerfView - thread file I/O wait time
;                  file read time
;                  file count of bytes read
;
;       Thread hook 7 (instance 1) as described in DCR 614.
;       File hooks 2 and 3 as described in DCR 614.
;
;       pTCBCur points to the TCB.
;       pTCBCur->TCBThisSFT points to the SFT.
;       If carry clear, then (CX) = number of bytes read

ifdef PERFVIEW
        pushf                                                           ;sfb
        CpuMode 386                                                     ;sfb
        SaveReg <es>                                                    ;sfb
        FlatContext <es>                                                ;sfb
        cmp     es:_fPerfview,0         ; check if perfview is up       ;sfb
        je      short dr20                                              ;sfb
        PvwEndTm thd,FileIOWait,1       ; Stop timer
dr20:                                                                   ;sfb
        CpuMode Reset                                                   ;sfb
        ASSUME  ES:NOTHING                                              ;sfb
        RestoreReg <es>                                                 ;sfb
        popf                                                            ;sfb
endif ;PERFVIEW

        pushf                           ; save cy

dore1:  mov     si,SS:[CurrTCB]   ; ss:si -> TCB
        test    SS:[si].TCBfSwapping,SM_TCB_SWAPPING
        jnz     dore3                   ; swap file doesn't need LockMFT

        call    UnlockMFT

dore3:  CALLFAR EndTransfer             ; unlock user buffer
        popf                            ; restore cy
dore5:  ret

drerr:  pop     bp                      ; RestoreReg <bp>
        pushf                           ; save cy to indicate error
        jmp     dore1
dore10:
;b734380pushf                                                           ;sfb
;b734380CpuMode 386                                                     ;sfb
;b734380SaveReg <es>                                                    ;sfb
;b734380FlatContext <es>                                                ;sfb
;b734380cmp     es:_fPerfview,0         ; check if perfview is up       ;sfb
;b734380je      short dr30                                              ;sfb
;b734380PvwStartTm thd,PipeIWait,0
;b734380dr30:                                                                   ;sfb
;b734380CpuMode Reset                                                   ;sfb
;b734380ASSUME  ES:NOTHING                                              ;sfb
;b734380RestoreReg <es>                                                 ;sfb
;b734380popf                                                            ;sfb

        test    dx,SF_NMPIPE            ; name pipe?
        jnz     dore15                  ; yes, go read name pipe

        CALLFAR ReadPipe                ; read anomymous pipe
        jmp     short dore18

dore15: call    NmPipeRead
dore18: pushf                           ; save cy

;b734380pushf                                                           ;sfb
;b734380CpuMode 386                                                     ;sfb
;b734380SaveReg <es>                                                    ;sfb
;b734380FlatContext <es>                                                ;sfb
;b734380cmp     es:_fPerfview,0         ; check if perfview is up       ;sfb
;b734380je      short dr40                                              ;sfb
;b734380PvwEndTm thd,PipeIWait,0
;b734380dr40:                                                                   ;sfb
;b734380CpuMode Reset                                                   ;sfb
;b734380ASSUME  ES:NOTHING                                              ;sfb
;b734380RestoreReg <es>                                                 ;sfb
;b734380popf                                                            ;sfb

        jmp     short dore3

;b734380jmp     dore3
dore20: call    ReadDev
        pushf                           ; save cy
        jmp     short dore3
;b734380jmp     dore3

; segment wrap or nothing to read
dore30: pushf                           ; save cy
        test    dx,SF_PIPE OR DEVID_DEVICE      ; clears carry!
        jnz     dore3                           ; pipe or device, skip UnlockMFT
;sfb    jmp     short dore1                     ; NOT pipe or device, UnlockMFT
        jmp     dore1                     ; NOT pipe or device, UnlockMFT
EndProc DOS_Read


BREAK <ReadDev - Read from device for PROT process>
;***    ReadDev - Read from device for PROT process
;
;       ENTRY   (CX)    number of bytes to read
;               (ES:DI) ptr to SFT
;               TCBDMAAdd set up with transfer address
;               TCBNextAdd set with offset of transfer address
;               (ds)    TASKAREA
;
;       EXIT    (DS)    TASKAREA
;               (ES:DI) ptr to SFT
;
;               (CX)    Number of bytes read
;               'NC' if no error
;                   SFT Position updated
;                   (TCBNextAdd)    offset beyond transfer
;               'C' if error
;                  AX is error code
;
;       USES    ax, bx, cx, dx, si, flags
;
;       NOTE    We are reading from a device.  Examine the status of the
;               device to see if we can short-circuit the I/O.
;
;               If the device is the null device, we can safely indicate
;               no transfer.
;
;               We only support raw device I/O, and then only to devices
;               other than the console.  For the console, we return a special
;               error code which tells the Ring 3 DOSCALL dynlink library to
;               make VIO/KBD calls.
;
;               It is very hard to make the prot mode I/O behave as DOS 3.2
;               since DOSREAD goes through the dynlink library
;
Procedure   ReadDev,,local
    assume      ds:TASKAREA,es:nothing
        MOV     [ExtErr_Locus],ERRLOC_SERDEV
        MOV     BL,BYTE PTR ES:[DI.sf_flags]


        TEST    BL,DEVID_DEVICE_NULL    ; NUL device?
        jnz     rd1                     ;   Yes (always at EOF)

        TEST    BL,DEVID_DEVICE_CON_IN  ; Is it console device?
        jz      rd2                     ;   No, go do RAW input

        mov     ax,ERROR_VIOKBD_REQUEST
        xor     cx,cx
        stc
        jmp     short rdx

rd1:    call    EndReadDev              ; Finish read
        jmp     short rdx

rd2:    mov     ax,0                    ; do read
        call    DevRWRaw                ; Read from device
rdx:    ret
EndProc     ReadDev

CpuMode 386
BREAK <DevRWRaw - Read/Write in raw mode from device>
;***    DevRWRaw - Read/Write in raw mode from device
;
;       We loop through until no more data to transfer or until the user does
;       not respond RETRY to the hard error.  Note that this means that we
;       can return an error and STILL transfer bytes.
;
;       ENTRY   (CX)    number of bytes to read/write
;               (ds)    TASKAREA
;               ax      0 if read else write
;               TCBDMAAdd set up with transfer address
;               TCBNextAdd set with offset of transfer address
;
;       EXIT    (CX)    Number of bytes read/written
;               (ES:DI) ptr to SFT
;               'NC' if no error
;                   SFT Position updated
;                   (TCBNextAdd)    offset beyond transfer
;               'C' if error
;                   AX is error code
;
;       USES    ax, bx, cx, dx, si, di, ds, es, flags
;
Procedure   DevRWRaw,,local
        assume  es:nothing,ds:TASKAREA,ss:TASKAREA

LocalVar        drwr_count,WORD
LocalVar        drwr_Operation,WORD     ;0 = READ;1=Write
LocalVar        drwr_NoOfPgBreaks,WORD
LocalVar        drwr_TmpBufPtr,DWORD
LocalVar        drwr_TmpBuffer,PGLSTSIZE        ;tmpbuffer for pagelist entries

;*  Validate and lock user buffer

        EnterProc       <cx,ax>
        SaveReg <es>
        or      cx,cx                           ; clears carry
        LJZ     drwr60                  ; nothing to write (carry clear)
drwr10: mov     dx,ACCTYPE_RW           ; need write access to segment
        test    drwr_Operation,-1       ; assumed for read
        jz      drwr15                  ; yes, go lock mem
        mov     dx,ACCTYPE_READ         ; need read access to segment for Write
drwr15: les     di,es:[di].sf_devptr    ; es:di -> device hdr
        mov     ax,es:[di].SDevAtt      ; dev attribute in ax
        Dev_FuncLev     DEVLEV_3,ax     ; Paging knowledgable device driver
        jc      drwr16                  ; no, don't use memory above 16M
        test    es:[di].SDevCaps,DEV_16MB ;
        jz      drwr16                  ; DD does not support access to > 16MB
        mov     ax,0                    ;
        jmp     short drwr17

drwr16: mov     ax,VML_16M + VML_CONTIG ; memory below 16M and contigous
drwr17: TaskContext     es
        lea     di,drwr_TmpBuffer       ; buffer for pagelist
        call    FATDevLockMem
        jc      drwrx                   ; memory invalid
        mov     drwr_NoOfPgBreaks,cx    ;
        mov     drwr_TmpBufPtr.Segmt,ES ; store the page buffer ptr
        mov     drwr_TmpBufPtr.Offst,di ;
        xor     si,si                   ; first time we should allocate req pkt
; top of the loop
; at this point =
;  si = 0 first time and non-zero for retries or for next contig.memory calls
;  di = next struc in paginfo buffer in case we called pageManager earlier
;  es:bx = request packet
;
        assume ds:nothing
drwr20: push    di
        push    es
        mov     es,drwr_TmpBufPtr.Segmt
        mov     cx,es:[di].pl_cb.LowWord ; new count;
        pop     es
                                        ;This can never be more than 64k
drwr30: mov     di,bx
        mov     bx,CurrTCB
        mov     ds,TASKAREA:[bx].TCBDMAAdd.Segmt
        mov     bx,TASKAREA:[bx].TCBNextAdd     ; (ds:bx) transfer address
        XOR     AX,AX                   ; (ax) Media Byte, unit = 0
        mov     dx,ax                   ; (dx) Start at 0
        test    drwr_Operation,-1       ; if read call setreadnoalloc
        jnz     drwr35
; this call become setread when it is called the first time in the loop
        CALLFAR SetReadNoAlloc  ; es:bx=reqpkt =
                                ;SetRead(ds:bx=xaddr cx=cnt dx=start
                                ;       ah=media al=unit,si=0 first time)
        jmp     short drwr36
drwr35: CALLFAR SetWriteNoAlloc

; es:BX = request packet
;
drwr36: mov     si,CurrTCB
        lds     si,TASKAREA:[si].TCBThisSFT      ; (ds:si) sft for transfer

        mov     ax,[si].sf_fsi.sfi_selfsfn ; (ax) = index of SFT in table
        mov     es:[bx].IOSFN,ax        ; allow char dev to virtualize based on
                                            ; open instance

        lds     si,[si].sf_devptr       ; (ds:si) device driver header
        CALLFAR DevIOCall2

        mov     ax,es:[bx].IOcount      ; (ax) number transferred
        mov     di,CurrTCB
        add     TASKAREA:[di].TCBNextAdd,ax ; (TCBNextAdd) addr of next input area
        sub     cx,ax                   ; (cx) number remaining

        mov     ax,es:[bx].ReqStat
        test    ax,STERR                ; did an error occur?
        JZ      short drwr50            ; no, go return completion

        and     ax,STECODE
        cmp     ax,ERROR_I24_CHAR_CALL_INTERRUPTED      ; interrupted wait?
        jne     short drwr40

        call    CRDFERR                 ; Perform exit processing
        mov     ax,ERROR_INTERRUPT      ; Set appropriate error code
        pop     di                      ; discard ptr to pageinfo buffer
        jmp     short drwrx             ; And return

drwr40: xor     ax,ax
        CALLFAR CharHard

        cmp     al,1
        jb      short drwr50            ; (al) == 0, go ignore error
        jnz     short drwr45            ; (al) > 1, fail/abort the error
        mov     si,ax                   ; si = non-zero
        jmp     drwr30

drwr45: call    CRDFERR                 ; Handle error
        pop     di
        jmp     short drwrx

drwr50: pop     di
        jcxz    drwr90
drwr51: CALLFAR FreeReqPacket

;       Carry is clear

drwr60: test    drwr_Operation,-1
        jnz     drwr70
        call    EndReadDev              ; Update SFT
        jmp     short drwrx
drwr70: call    EndWriteDev             ; Update SFT
drwrx:  RestoreReg      <es>
        LeaveProc
        ret

; all bytes transferred in this  chunk; go for next read for next contig. mem.
drwr90: dec     drwr_NoOfPgBreaks
        jz      drwr51                                  ; end of all dev reads
        mov     si, SIZE        pagelist_s      ; non-zero si
        add     di, si
        jmp     drwr20          ; go back for the next read

EndProc     DevRWRaw

CpuMode 286                             ; keep those jumps short...

;***    EndReadDev - Finish read on device
;
;       ENTRY   (TCBThisSFT)    device SFT
;
;       EXIT    (es:di)         pointer to SFT
;               (cx)            number of bytes transferred
;
;       NOTE    It is very hard to make the prot mode I/O behave as DOS 3.2
;               since DOSREAD goes through the dynlink library
;
;       USES    ax, cx, si, di, es, flags
;
Procedure   EndReadDev,,local
        assume  ds:nothing,es:nothing
        TaskContext DS
erd1:   call    SetSFT                  ; Update SFT
        ret
EndProc     EndReadDev


;***    CRDFERR - error exit routine
;
;       We  have  encountered a device-driver error and we must
;       clean  up  and return an error.
;
;       ENTRY   (ES:BX)         request packet address
;               (TCBNextAdd)    offset beyond transfer
;               (TCBDMAAdd)     pointer to beginning of transfer
;               (TCBThisSFT)    pointer to SFT of transfer
;
;       EXIT    (ES:DI)         SFT
;               (ax)            ERROR_ACCESS_DENIED
;               (cx)            number of bytes transferred
;               (DS)            TASKAREA
;               Carry set
;
;       USES    AX, CX, DI, DS, Flags
;
Procedure CRDFERR,,local
        assume  ds:nothing,es:nothing
        CALLFAR FreeReqPacket           ; release the request packet

        TaskContext DS                  ; DS = SS
        mov     di,CurrTCB              ; ES:DI = SFT

        mov     cx,TASKAREA:[di].TCBNextAdd      ; (cx) offset beyond transfer
        sub     cx,TASKAREA:[di].TCBDMAAdd.Offst ; (cx) # transfered

        les     di,TASKAREA:[di].TCBThisSFT

        mov     ax,ERROR_ACCESS_DENIED  ; AX = error code
        stc                             ; Carry set
        ret
EndProc CRDFERR


;**     DOS_Write - write to pipe, device, or FAT file
;
;       ENTRY   es:di       -> sft
;               cx           = byte count
;                            = 0 - truncate or extent file size to current
;                                  file position. This may happen if
;                                  FSD_Write is called, but won't if
;                                  FSD_Write_NT is called (zero-size write
;                                  will not be passed to DOS_Write).
;               [TCBDMAAdd]  = data to write
;               [TCBJfnFlg]  = jfn flag
;               [TCBThisSFT] = sft
;
;               note:
;               es:di and cx are required for all cases;
;               TCBDMAAdd are required for pipe only;
;               TCBJfnFlg is requuired for disk file only;
;               no specific paremeters are required for device
;
;       EXIT    if NC
;                 cx = bytes written
;                 sft updated
;               if CY
;                 ax = error code
;
;       USES    ax,bx,cx,dx,si,di,ds,es,flags
;
;       ALGORITHM
;
;       switch (sft) {
;
;         case pipe:
;           writePipe
;           break
;
;         case device:  /* local device */
;           writeDev
;           return
;
;         default:      /* disk file */
;           LockMFT
;           SetupForIO
;           Diskwrite   /* file write for FAT */
;           if (no error) {
;             set ST_SWRITE and ST_PWRITE flags in sfi_tstamp
;             if (write-thru)
;               FlushBuf
;           }
;           UnlockMFT
;       }
;       EndTransfer     /* unlock user buffer */

Procedure DOS_Write,hybrid
    assume ds:nothing,ds:nothing

        TaskContext <ds>
        mov     si,TASKAREA:[CurrTCB]   ; ss:si -> TCB
        mov     TASKAREA:[si].TCBfIOLock,0 ; mark user segment not locked

        test    es:[di].sf_flags,SF_PIPE + DEVID_DEVICE
        jnz     dw10

        test    TASKAREA:[si].TCBfSwapping,SM_TCB_SWAPPING
        jnz     dw10                    ; swap file doesn't need LockMFT

        SaveReg <AX>                    ; LockMFT trashes AX
        call    LockMFT
        RestoreReg <AX>                 ; AX:BX = byte position in file

dw10:   SaveReg <bp>
        call    SetupForIO              ; ax:bx = file position
        RestoreReg <bp>

; see warning in SetupForIO

        mov     dx,es:[di].sf_flags     ; dx = flags
    .386p
        jc      dowr30                  ; segment wrap error, go exit
    CpuMode reset

        test    dx,SF_PIPE              ; pipe?
        LJNZ    dowr10                  ; yes, go write pipe

        test    dx,DEVID_DEVICE         ; is this a device?
    .386p
        jnz     dowr20                  ; yes, go write device
    CpuMode reset

; not pipe, not device, must be FAT file
; AX:BX = byte position in file

        MOV     [ExtErr_Locus],ERRLOC_DISK ; set locus in case of disk error

;***PV  PerfView - thread file I/O wait time
;                  file count of writes
;                  file write time
;
;       Thread hook 7 (instance 2) as described in DCR 614.
;       File hooks 4 and 5 as described in DCR 614.
;
;       pTCBCur points to the TCB.
;       pTCBCur->TCBThisSFT points to the SFT.

ifdef PERFVIEW
        pushf                                                           ;sfb
        CpuMode 386                                                     ;sfb
        SaveReg <es>                                                    ;sfb
        FlatContext <es>                                                ;sfb
        cmp     es:_fPerfview,0         ; check if perfview is up       ;sfb
        je      short dw20                                              ;sfb

        PvwStartTm thd,FileIOWait,2     ; Start timer
dw20:                                                                   ;sfb
        CpuMode Reset                                                   ;sfb
        ASSUME  ES:NOTHING                                              ;sfb
        RestoreReg <es>                                                 ;sfb
        popf                                                            ;sfb
endif ;PERFVIEW

        call    DiskWrite               ; write FAT file

;***PV  PerfView - thread file I/O wait time
;                  file write time
;                  file count of bytes written
;
;       Thread hook 7 (instance 2) as described in DCR 614.
;       File hooks 5 and 6 as described in DCR 614.
;
;       pTCBCur points to the TCB.
;       pTCBCur->TCBThisSFT points to the SFT.
;       If carry clear, then (CX) = number of bytes read

ifdef PERFVIEW
        pushf                                                           ;sfb
        CpuMode 386                                                     ;sfb
        SaveReg <es>                                                    ;sfb
        FlatContext <es>                                                ;sfb
        cmp     es:_fPerfview,0         ; check if perfview is up       ;sfb
        je      short dw30                                              ;sfb

        PvwEndTm thd,FileIOWait,2       ; Stop timer
dw30:                                                                   ;sfb
        CpuMode Reset                                                   ;sfb
        ASSUME  ES:NOTHING                                              ;sfb
        RestoreReg <es>                                                 ;sfb
        popf                                                            ;sfb
endif ;PERFVIEW

        jc      dowr5                   ; error, go error exit

; write was successful and it was to a disk file, set flag in sfi_tstamp to
; request time stamping on last write time

        or      es:[di].sf_fsi.sfi_tstamp,ST_PWRITE or ST_SWRITE

; flush the buffers if write through bit is on.
; cx = number of bytes written
; bugbug 20-jun-88 peterwu - unfinished business with SFT time stamp
; if write thru bit is on, should we time stamp and propagate? currently
; we don't

        mov     bx,CurrTCB
        test    TASKAREA:[bx].TCBJfnFlag,JFN_WRTTHR ; Is write-through turned on?
        jz      dowr5                   ; no, go normal exit (carry cleared)

; Write through is turned on, so flush buffers before unlocking mft

        pushf
CpuMode 386
        SaveReg <ds,es,edi,esi,ecx>
        mov     esi,es:[di].sf_fsd.sfdFAT_fldMask
        mov     di,es:[di].sf_fsi.sfi_hVPB
        movzx   edi,di
        FlatContext <ds,es>
        CCALL32  CacheFlush,<esi,edi>
        RestoreReg <ecx,esi,edi,es,ds>
CpuMode reset
        mov     ax,es:[di].sf_fsi.sfi_hVPB
        SaveReg <cx,es,di>              ; preserve number of bytes written
        CALLFAR FlushBuf
        RestoreReg <di,es,cx>           ; cx = number of bytes written
        popf

        ASSUME  DS:TASKAREA,ES:NOTHING
        xor     dx,dx                                             ;### Japan ptr j200324
        sub     dx,ax                   ; 0 - 0 = NC, 0 - !0 = CY ;### Japan ptr j200324
dowr5:  mov     si,TASKAREA:[CurrTCB]   ; ss:si -> TCB
        pushf
        test    TASKAREA:[si].TCBfSwapping,SM_TCB_SWAPPING
        jnz     dowr6                   ; swap file doesn't need LockMFT
        call    UnlockMFT

dowr6:  popf

dowr7:  CALLFAR EndTransfer             ; unlock user segment
        ret
dowr10:
;b734380pushf                                                           ;sfb
;b734380CpuMode 386                                                     ;sfb
;b734380SaveReg <es>                                                    ;sfb
;b734380FlatContext <es>                                                ;sfb
;b734380cmp     es:_fPerfview,0         ; check if perfview is up       ;sfb
;b734380je      short dw40                                              ;sfb

;b734380PvwStartTm thd,PipeOWait,0
;b734380dw40:                                                                   ;sfb
;b734380CpuMode Reset                                                   ;sfb
;b734380ASSUME  ES:NOTHING                                              ;sfb
;b734380RestoreReg <es>                                                 ;sfb
;b734380popf                                                            ;sfb

        test    dx,SF_NMPIPE            ; name pipe?
        jnz     dowr15                  ; yes, go write name pipe

        CALLFAR WritePipe               ; write anonymous pipe
        jmp     short dowr18
dowr15:
        call    NmPipeWrite
dowr18:
;b734380pushf                                                           ;sfb
;b734380CpuMode 386                                                     ;sfb
;b734380SaveReg <es>                                                    ;sfb
;b734380FlatContext <es>                                                ;sfb
;b734380cmp     es:_fPerfview,0         ; check if perfview is up       ;sfb
;b734380je      short dw50                                              ;sfb

;b734380PvwEndTm thd,PipeOWait,0
;b734380dw50:                                                                   ;sfb
;b734380CpuMode Reset                                                   ;sfb
;b734380ASSUME  ES:NOTHING                                              ;sfb
;b734380RestoreReg <es>                                                 ;sfb
;b734380popf                                                            ;sfb

        jmp     short dowr7
;b734380jmp     dowr7

dowr20: call    WriteDev
        jmp     short dowr7
;b734380jmp     dowr7

dowr30: test    dx,SF_PIPE OR DEVID_DEVICE      ; clears carry!
        jnz     dowr7                           ; pipe or device, skip UnlockMFT
        jmp     short dowr5                     ; NOT pipe or device, UnlockMFT
;b734380jmp     dowr5                           ; NOT pipe or device, UnlockMFT
EndProc DOS_Write


;***    WriteDev - Write to device for PROT process
;
;       ENTRY   (cx)    count of bytes to write
;               (ES:DI) ptr to SFT
;               TCBDMAAdd set up with transfer address
;               TCBNextAdd set with offset of transfer address
;               (ds)    TASKAREA
;
;       EXIT    (DS)    TASKAREA
;               (ES:DI) ptr to SFT
;               (CX)    No. of bytes written
;               'NC' if no error
;                       SFT Position and cluster pointers updated
;                       (TCBNextAdd)    offset beyond transfer
;               'C' if error
;                       AX is error code
;
;       USES    ax, bx, cx, dx, si, flags
;
;       NOTE    We only do raw output to non-console devices.  For console
;               device, we return special error to tell Ring 3 DOSCALLS
;               dynlink library to call VIO/KBD.
;
Procedure   WriteDev
        assume  ds:TASKAREA,es:nothing
        MOV     [ExtErr_Locus],ERRLOC_SERDEV

;;;; BUGBUG - I'm not sure if this is needed here. The bit is never needed in
;;;;          protect mode, but I will leave it in here since it may be used
;;;;          by a real mode process.
        OR      ES:[DI.sf_flags],DEVID_DEVICE_EOF   ; Reset EOF for input

        MOV     BL,BYTE PTR ES:[DI.sf_flags]

        test    bl,DEVID_DEVICE_NULL    ; NUL device?
        jnz     wdx                     ; yes, all done

        JCXZ    wd1                     ; no bytes to write, go clean up

        TEST    BL,DEVID_DEVICE_CON_OUT ; Is it console device?
        jz      wd2                     ;   No, go do RAW output

        mov     ax,ERROR_VIOKBD_REQUEST ; get proper error code
        stc                             ; signal error
        jmp     short wdx

wd1:    call    EndWriteDev
        jmp     short wdx

wd2:    mov     ax,1                    ; do device write raw
        call    DevRWRaw
wdx:    ret
EndProc     WriteDev


;***    EndWriteDev - end write to device
;
;       ENTRY   (TCBThisSFT)    device SFT
;
;       EXIT    (DS)    TASKAREA
;               (ES:DI) ptr to SFT
;               (CX)    count of bytes transferred
;               carry clear
;
;       USES    cx, di, ds, es, flags
;
Procedure   EndWriteDev
        assume  es:nothing,ds:nothing
        TaskContext DS
        mov     di,CurrTCB
        mov     cx,TASKAREA:[di].TCBNextAdd          ; (cx) offset beyond transfer
        sub     cx,TASKAREA:[di].TCBDMAAdd.Offst     ; (cx) # transfered
        les     di,TASKAREA:[di].TCBThisSFT
        UpdateSFTPosition
        ret
EndProc     EndWriteDev


Break   <DirRead -- READ A DIRECTORY SECTOR>

;***    DirRead -- Read a directory sector
;
;       Read the directory block into [CurBuf].
;
;       ENTRY   AX=Directory block number (relative to first block of directory)
;               ES:BP = VPB
;               [DirSec] = First sector of first cluster of directory
;               [ClusNum] = Next cluster
;               [CLUSFAC] = Sectors/Cluster
;
;       EXIT    [NxtClusNum] = Next cluster (after the one skipped to)
;               [SecClusPos] Set
;               [CurBuf] Points to Buffer with dir sector
;               ES:BP still points to VPB
;               Carry set if error (user said FAIL to I 24)
; DS preserved, all other registers destroyed.
;
Procedure   DirRead,NEAR
        assume  DS:TASKAREA, ES:NOTHING

        SegAssert SS,<DS>,"DirRead"
        chkvselhd es,bp
;
; Note that ClusFac is is the sectors per cluster.  This is NOT necessarily
; the same as what is in the DPB!  In the case of the root directory, we have
; ClusFac = # sectors in the root directory.  The root directory is detected
; by DirStart = 0.
;
        XOR     DX,DX
                                        ;72417 moved from PTDA to TCB
        SetBP_CurrTCB
        CMP     [bp].TCBDirStart,0
        UnSetBP_CurrTCB
                                        ;72417 end
        jnz     SubDir
        XCHG    AX,DX
        JMP     short DoRead
;
; Convert the sector number in AX into cluster and sector-within-cluster pair
;
SubDir:
        MOV     DL,AL
        AND     DL,ES:[bp].vpb_fsd.vpdFAT_cluster_mask
;
; DX is the sector-in-cluster
;
        MOV     CL,ES:[bp].vpb_fsd.vpdFAT_cluster_shift
        SHR     AX,CL
;
; DX is position in cluster
; AX is number of clusters to skip
;
DoRead:
        CALLFAR IncVPBRef       ; for searches, insure at least 1 ref

        push    bp
        mov     bp,CurrTCB
        mov     TASKAREA:[bp].TCBSecClusPos,DL
        MOV     CX,AX
        MOV     AH,DL
;
; CX is number of clusters to skip.
; AH is remainder
;
                                        ;72417 moved from PTDA to TCB
        MOV     DX,word ptr TASKAREA:[bp].TCBDirSec
        MOV     SI,word ptr TASKAREA:[bp].TCBDirSec+2
                                        ;72417 end
        ADD     DL,AH
        ADC     DH,0
        ADC     SI,0            ; SI:DX = sector number of directory
        mov     BX,TASKAREA:[bp].TCBClusNum
                                        ;72417 moved from PTDA to TCB
        mov     [bp].TCBNxtClusNum,BX
                                        ;72417 end
        pop     bp
        JCXZ    pre_FC
SKPCLLP:
        push    si              ; Save high word of sector number
        call    UnPack
        pop     si
        jc      fc2
        XCHG    BX,DI
        call    IsEOF           ; test for eof based on fat size
        JAE     HAVESKIPPED
        LOOP    SKPCLLP
HAVESKIPPED:
                                        ;72417 moved from PTDA to TCB
        SetBP_CurrTCB
        mov     [bp].TCBNxtClusNum,BX
        UnSetBP_CurrTCB
                                        ;72417 end
        MOV     DX,DI
        MOV     BL,AH
        call    FigRec

pre_FC:
        xchg    cx,si           ; CX:DX = sector number
        jmp     short fc1

Entry   FirstCluster
;
; CX:DX = sector number
;
        CALLFAR IncVPBRef       ; for searches, insure at least 1 ref

fc1:    push    bp
        mov     bp,CurrTCB
        mov     TASKAREA:[bp].TCBAllowed,ALLOWED_RETRY + ALLOWED_FAIL
        pop     bp
        xor     al,al           ; Indicate pre-read
        CALLFAR GetBuffr

fc2:    pushf
        CALLFAR DecVPBRef       ; remove the temporary reference we applied
        popf

        jc      dirx

;***    SET_BUF_AS_DIR - Set the type of CurBuf to be a directory sector.
;
;       ENTRY   [CurBuf] points to currently assigned buffer
;
;       EXIT    NONE
;
;       USES    FLAGS

Entry   SET_BUF_AS_DIR
        assume  DS:TASKAREA
        ASSUME  ES:NOTHING

        PUSH    DS
        PUSH    SI
        mov     si,CurrTCB
        lds     si,TASKAREA:[si].TCBCurBuf
        OR      [SI.buf_flags],BUF_ISDIR        ; Clears carry
        POP     SI
        POP     DS
dirx:   ret
EndProc DirRead


Break   <SetupForIO -- SETUP A DISK READ OR WRITE FROM USER>

;***    SetupForIO - prepare for a user read or write of file or direct disk
;***    SetupForIO_EA - prepare for a read or write of extended attributes
;
;       Various local variables used in the read/write code are initialized.
;
;       WARNING For callers of SetupForIO
;                   The caller must call LockMFT before calling SetupForIO
;                   to avoid problems with concurrent access to the
;                   sf_fsi.sfi_position variable.
;
;       ENTRY   ES:DI point to SFT (value also in ThisSFT)
;               [TCBDMAAdd] contains transfer address
;               CX = Byte count
;
;       EXIT    'NC' if success
;                   CX = byte count
;                   AX:BX = byte position in file (meaningless for EAs)
;                   TCBThishVPB   = handle to VPB segment if disk file
;                   ES:DI Points to SFT
;                   TCBNextAdd = Displacement of disk transfer within segment
;               'C' if ERROR
;                   CX = 0
;
;       The following fields are relevant to local files only (not devices):
;
;           [TCBSecPos] = Position of first sector (local files only)
;           [TCBBytSecPos] = Byte position in first sector (local files only)
;           [TCBClusNum] = First cluster (local files only)
;           [TCBSecClusPos] = Sector within first cluster (local files only)
;
;
;       USES    AX, BX, DX, SI, BP, Flags
;
Procedure   SetupForIO,<hybrid,far16>
    ASSUME DS:TASKAREA,ES:NOTHING

        xor     si,si                   ; This I/O is for a file or direct disk
        jmp     short xfio5

Entry   SetupForIO_EA
        mov     si,1                    ; This I/O is for extended attributes
xfio5:  SegAssert SS,<DS>,"SetupForIO"

CpuMode 386
        mov     eax,es:[di].sf_fsi.sfi_position

        push    eax                     ;Save current file position
        mov     bp,CurrTCB
        mov     bx,es:[di].sf_fsi.sfi_hVPB
        mov     TASKAREA:[bp].TCBThishVPB,bx
        mov     bx,TASKAREA:[bp].TCBDMAAdd.Offst
        mov     TASKAREA:[bp].TCBNextAdd,bx     ; Set NextAdd to start of Xaddr
        test    es:[di].sf_flags,SF_ISNET + DEVID_DEVICE + SF_PIPE
        jnz     short xfio30            ;Following not done on devs, NET, pipes
        or      si,si
        jz      short xfio10            ; This is for a file or direct disk
;
; We are setting up for I/O to extended attributes. We always assume that the
; I/O will be done starting in the first sector of the chain, but not
; necessarily at the beginning of that first sector.  The caller of this
; routine specifies the offset in the first sector of the chain.  Since
; we may assume that the access will start in the first sector of the
; first cluster in the chain, we set the "current position" for the EA chain
; to zero except for the byte offset.
;
; Again, TCBBytSecPos is set by the caller.

        xor     ax,ax
        mov     [bp].TCBSecPos.LowWord,ax
        mov     [bp].TCBSecPos.HighWord,ax
        mov     [bp].TCBSecClusPos,al
        mov     [bp].TCBClusNum,ax
        mov     [bp].TCBLastPos,ax
        jmp     short xfio30            ; Finish up rest of setup
;
;** File or direct disk I/O
;
; Given the current byte position within the file, we calculate what that
; means in terms of:
;       - Byte offset within the first sector
;               TCBBytSecPos = Remainder (Byte position / vpi_cbSector)
;       - Sector position within file
;               TCBSecPos = Quotient (Byte position / vpi_cbSector)
;       - Cluster position within file
;               TCBClusNum = ((TCBSecPos AND cluster_mask) >> cluster_shift)
;
xfio10: SaveReg <ES,CX>
        mov     bp,TASKAREA:[bp].TCBThishVPB
        VPBContext ES,BP                ; Point at the vpb

        MOVZX   EBX,ES:[bp].vpb_fsi.vpi_cbSector
;
        xor     edx,edx
        div     ebx             ; eax = quotient and edx = remainder

        push    di
        mov     di,CurrTCB
        mov     TASKAREA:[di].TCBBytSecPos,DX

        mov     TASKAREA:[di].TCBSecPos,eax
;
; Convert the sector position into a cluster number.
;
        MOV     DX,AX
        AND     DL,ES:[bp].vpb_fsd.vpdFAT_cluster_mask
        mov     TASKAREA:[di].TCBSecClusPos,DL
        XCHG    CX,DX           ; DX:AX = sector number in file
        MOVZX   CX,ES:[bp].vpb_fsd.vpdFAT_cluster_shift
        jcxz    short xfio20
;
; Convert the sector number into a cluster number. Shift the 32-bit value
; CX times. We assume that the cluster number will fit into a 16-bit value.
;
xfio15: SHR     EAX,cl

xfio20: mov     TASKAREA:[di].TCBClusNum,AX
        pop     di
        RestoreReg <CX,ES>      ; ES:DI point to SFT; CX = byte count of xfer
;
; Check to see if the transfer will fit in the segment. We check to see that
; the offset of the transfer address will not wrap around a segment boundary.
; If it will, we set carry and return.
;
xfio30: MOV     AX,CX           ; AX = CX = byte count of transfer
        mov     bp,CurrTCB
        add     ax,TASKAREA:[bp].TCBDMAAdd.Offst ; See if it will fit in one segment
        JNC     short xfio60            ; Must be less than 64K
        mov     ax,TASKAREA:[bp].TCBDMAAdd.Offst
        NEG     AX              ; Amount of room left in segment (know less
                                ;    than 64K since max value of CX is FFFF).
        JNZ     short xfio50
        DEC     AX
xfio50: MOV     CX,AX           ; Can do this much
        JCXZ    short xfio70            ; Silly user gave Xaddr of FFFF in segment
        clc                     ; no error
xfio60: pop     bx      ; AX:BX = byte offset within file
        pop     ax
        ret
;
; We will get a segment wrap with this transfer.
; warning: setupforio used to return to caller of caller with carry cleared
; and cx=0. We change it to set carry and return to caller, but the caller
; should clear carry and return to its caller. Some callers may depend on
; this behavior.
;
xfio70: STC
        jmp     short xfio60    ; error exit (cx = 0)
EndProc SetupForIO
CpuMode 286

Break   <BreakDown -- CUT A USER READ OR WRITE INTO PIECES>

;***    BreakDown - Break up a read or write request
;
;       Disk read and writes may be partioned into three parts,
;       any of which may be empty.  The three parts are a segment
;       at the beginning which must be deblocked, a segment starting
;       at a sector boundary and continuing for an integral number of
;       sectors and a segment at the end which must be deblocked.
;       BreakDown computes the size of each segment.
;
;       ENTRY   CX = Length of disk transfer in bytes
;               ES:SI = pointer to VPB
;               [BytSecPos] = Byte position witin first sector
;               ds:di = ptr to page break info  if dx >1
;
;       EXIT    [BytCnt1] = Bytes to transfer in first sector
;               [SecCnt] = No. of whole sectors to transfer
;               [BytCnt2] = Bytes to transfer in last sector
;
;       USES    AX, BX, DX, Flags
;

Procedure   BreakDown,NEAR,LOCAL
        assume  ds:TASKAREA, ES:NOTHING


        SaveReg <di>
        mov     di,CurrTCB
        mov     ax,TASKAREA:[di].TCBBytSecPos
        mov     bx,cx
        OR      AX,AX               ; Partial first sector?
        JZ      rbd0                ; No.
        SUB     AX,ES:[si].vpb_fsi.vpi_cbSector
        NEG     AX                  ; Max number of bytes left in first sector
        SUB     BX,AX               ; Subtract from total length
        JAE     rbd0
        ADD     AX,BX               ; Don't use all of the rest of the sector
        XOR     BX,BX               ; And no bytes are left
rbd0:
        xchg    ax,bx
        XOR     DX,DX
        DIV     ES:[si].vpb_fsi.vpi_cbSector ; How many whole sectors?

; BX = number of bytes in partial first sector
; AX = number of whole sectors.
; DX = number of bytes in partial last sector
        mov     TASKAREA:[di].TCBBytCnt1,bx
        mov     TASKAREA:[di].TCBSecCnt,ax
        mov     TASKAREA:[di].TCBBytCnt2,dx

        assume ds:nothing,es:nothing
        RestoreReg <di>
        ret

EndProc BreakDown


Break   <DiskRead -- PERFORM USER DISK READ>

;***    DiskRead - Perform user disk read
;
;       DiskRead transfers disk data into user space.
;
;       ENTRY   AX:BX = byte position in file
;               CX = byte count
;               ThishVPB   = handle to VPB segment if disk file
;               ES:DI Points to SFT
;               [NextAdd] = Displacement of disk transfer within segment
;               ds = TASKAREA
;
;       EXIT    Carry Clear
;                       CX = No. of bytes read
;                       SFT offset and cluster pointers updated
;               Carry Set
;                       CX = 0
;                       AX has error code
;
;       USES    AX,BX,CX,DX,DI,SI,ES,DS
;
;       PSEUDOCODE:
;               Check for reads  (starting_beyond)/past EOF
;                       and update the count properly if needed
;               call DoDiskRead to do the read
;               call SetSFT to update the SFT
;
Procedure   DiskRead,NEAR,LOCAL
        ASSUME  ES:NOTHING, DS:TASKAREA
        SegAssert SS,<DS>, "DiskRead"

LocalVar    dskrd_BytPos,DWORD          ; Byte position of transfer within file

        EnterProc <AX,BX>               ; Initialize dskrd_BytPos
        SaveReg <BP>                    ; Save frame pointer for exit
        MOV     [ExtErr_Locus],ERRLOC_DISK
        mov     ax,es:[di].sf_fsi.sfi_size.LowWord
        mov     bx,es:[di].sf_fsi.sfi_size.HighWord
        sub     ax,[dskrd_BytPos].LowWord
        sbb     bx,[dskrd_BytPos].HighWord
        JB      drd2                    ;Read starts past EOF
        JNZ     drd1                    ;More than 64k to EOF
        OR      AX,AX
        JZ      drd2                    ;Read starts at EOF
        CMP     AX,CX
        JAE     drd1                    ;I/O fits
        MOV     CX,AX                   ;Limit read to up til EOF
drd1:   test    es:[di].sf_fsi.sfi_DOSattr,attr_volume_id  ; cannot read from a volume id!
        jz      drd3

drderr: call    SetAccessDenied
        jmp     short drdx

drd2:   xor     ax, ax                  ; clear ax so no error code is returned
        call    WrtErr
        jmp     short drdx
;
; It is OK to go ahead with the READ operation.
;
drd3:   mov     bx,IOTYPE_FILE          ; Say that this is for a file or direct access
        call    DoDiskRead              ; Read the data off the disk
        jc      drdx                    ; Some kind of error encountered
        call    SetSFT
drdx:   RestoreReg <BP>                 ; Restore pointer to stack frame
        LeaveProc
        ret

EndProc DiskRead

BREAK <DoDiskRead - breaks read into 3 parts and reads them>

;***    DoDiskRead - Does the actual read of data for each set of
;                       contiguous pages or splits the reads into
;                       scatter/gather reads
;
;       We differentiate between extended attributes and files (direct
;       I/O) since the cluster chains start in different places and the
;       SFT fields used are different.
;
;       ENTRY   TCBThishVPB = handle to VPB segment if disk file
;               TCBThisSFT set up with pointer to current SFT
;               TCBNextAdd = Displacement of disk transfer within segment
;               ES:DI Points to SFT
;               CX = no of bytes to read
;
;       EXIT    'NC' if success
;                       CX = No. of bytes read
;               'C' if error
;                       CX = 0
;                       AX has error code
;
;       USES    ALL
;
;       PSEUDOCODE:
;          Find out whether this is a DASD or not by looking at SFT
;          Call BreakDown for the full Count to split the read into
;               head, middle, tail parts (TCBBytCnt1,2,TCBSecCnt set)
;          If (some full sectors to read i.e. TCBSecCnt != 0)
;          {
;               call FATRWLockMem to lock the memory and get the
;                       contig.break info
;          }
;          call FATReadInit to do some initialisation of TCB Vars
;          for (each set of Contig. Memory) do
;          {
;               call BreakDown again for the current count  and set
;                       TCBBytCnt1 etc properly
;               If (TCBBytCnt1 i.e. some head part to read)
;                       call BufRd to read it through FileSystem buffer
;               Call DoCPDiskRead to read middle portion (full sector reads)
;               if (TCBBytCnt2 i.e. some tail to read)
;                       call BufRd to read it through system buffers
;               go to next set of contig. memory;
;          }
;
;
Procedure   DoDiskRead,NEAR
    ASSUME DS:NOTHING, ES:NOTHING, SS:TASKAREA

LocalVar    ddr_hVPB,WORD
LocalVar    ddr_NorDirFlg,WORD  ; 0 => Normal read, 1 => direct read
LocalVar    ddr_Trans,WORD      ; 0 => No transfers have taken place so far
LocalVar    ddr_NoOfPgBrks,WORD ;
LocalVar    ddr_fStrat2?,WORD   ;
LocalVar    ddr_count,WORD      ; total no of bytes to read
LocalVar    ddr_PageList,DWORD  ; page list buffer pointer
LocalVar    ddr_PgLstBuffer,PGLSTSIZE
LocalVar    ddr_Remnant,WORD    ;
LocalVar    ddr_EntryCount,WORD

; Initialize local variables as follows:
;       - pSFT set to current sft ptr
;       - hVPB set to NULLVPB
;       - NorDirFlg set to 0 (presume this is not a direct READ)
;       - Trans set to 0 since we have not yet had a transfer
;       - No of page breaks in the given buffer = 1 (contig. memory)
;       - count set to requested no of bytes to be read (cx on input)

        EnterProc <NULLVPB,0,0,1,0,cx,0,0>

        mov     si,CurrTCB
        mov     si,TASKAREA:[si].TCBThishVPB
        mov     [ddr_hVPB],si           ; Save hVPB in local variable

        test    es:[di].sf_fsi.sfi_mode,open_direct ; SFT open for direct access?
        jz      ddr10                   ; Actually is normal access
        inc     [ddr_NorDirFlg]         ; Direct access
;
; call BreakDown for the full transfer count once to decide whether we have to
;       lock the memory or not
;
ddr10:  VPBContext ES,SI                ; ES:SI -> VPB
        CALL    BreakDown
        mov     bx,[ddr_hVPB]
        call    UseCache
        LJC     ddr17
        mov     cx,ddr_count

        SaveReg <di,cx,es>
        call    FATReadInit
        RestoreReg <es,cx,di>

        mov     si,[ddr_hVPB]
        call    Init_VPB_Cache
        LJC     ddr80
CpuMode 386
        movzx   ecx,ddr_count
        movzx   ebx,ddr_NorDirFlg
        TaskContext DS
        mov     di,CurrTCB
        les     si,TASKAREA:[di].TCBThisSFT
        mov     edx,es:[si].sf_fsd.sfdFAT_pSFT
        movzx   eax,TASKAREA:[di].TCBThisSFT.Offst
        add     edx,eax
        movzx   edi,[ddr_hVPB]
        FlatContext <ds,es>
        CCALL32 _BufRead,<ecx,edx,ebx,edi>
        TaskContext ds
        or      eax,eax
        stc
        LJNZ    ddr80
CpuMode 286
        mov     cx,ddr_count
        TaskContext DS
        clc
        jmp     ddr100
ddr17:
;
; Check whether the device driver supports strategy2
; and if so, also see whether this is a removable media.
; Use strategy2 (scatter/gather) ONLY in case of
;       devices that support strategy2
;       and for non-remov media
; Use Strat1 (the old, pre-2.0 method) for floppies for
; the time being.   -- Nagara 5/17/90

        mov     AX,ES:[si].vpb_fsi.vpi_cbSector ; We need this later.
        les     si,es:[si].vpb_fsi.vpi_pDPB
        mov     dx,word ptr es:[si].dpb_strategy2+2
        or      dx,word ptr es:[si].dpb_strategy2
        jz      ddr20
        test    es:[si].dpb_flags,DPBNONREMOV
        jnz     strat2                  ; hardfile? always use strat 2

        push    ds                      ; b722276
        DosContext <ds>
        test    ABIOS_present,1             ; abios machine?
        pop     ds
        assume ds:nothing
        jz      ddr20                   ; family 1, no disk support strat2

strat2:
        mov     ddr_fStrat2?,1          ; Use Strat2
ddr20:  mov     dx,ACCTYPE_RW           ; need write access to segment
        mov     cx,ddr_count            ;
        TaskContext     es
        lea     di,ddr_PgLstBuffer      ;
        mov     bx,CurrTCB
        test    ddr_fStrat2?,-1         ; can we use strategy2?
        jz      ddr13                   ; no
        cmp     TASKAREA:[bx].TCBSecCnt,2   ; More then two whole sectors ?
        ja      short ddr15                 ; Yes.
        jb      short ddr12                 ; Zero or one Whole sectors.
        test    TASKAREA:[bx].TCBBytCnt2,-1 ; Have a "tail"
        jne     short ddr15                 ; Yes.
        mov     TASKAREA:[bx].TCBSecCnt,1   ; Change it to one sector
        mov     TASKAREA:[bx].TCBBytCnt2,ax ; and sector size "tail"
ddr12:
CpuMode 386
        movzx   ecx,cx
        mov     es:[di].pl_cb,ecx   ; This can never be more than 64k
        mov     es:[di].pl_paddr,-1

CpuMode Reset
        mov     cx,1                ; no page breaks ; contiguous memory
        jmp     short ddr16

ddr13:  cmp     TASKAREA:[bx].TCBSecCnt,1   ; More then one whole sector ?
        jbe     short ddr12                 ; No.
ddr15:  call    FATRWLockMem            ; lock memory and get contig.break info
        LJC     ddr90                   ; jump if memory not valid
ddr16:  mov     [ddr_PageList].Offst,di
        mov     [ddr_PageList].Segmt,es
        mov     [ddr_NoOfPgBrks],cx

        call    FATReadInit
        les     di,[ddr_PageList]
        LJC     ddr90                   ; error in read init; bad sector?
;
        test    ddr_fStrat2?,-1         ; can we use strategy2?
        jz      ddr30                   ; no, break up the memory & use strat1

; We can use strategy2; just set up proper registers; do transfer for the
; FULL COUNT instead of the memory breakup counts
        VPBContext      ES,,NOCHECK     ;
        jmp     short ddr35
;
; Here the loop starts; for each set of contiguous memory we go read them
; es:di -> next entry in pagelist_s  structure

ddr30: mov      cx,es:[di].pl_cb.LowWord   ; This can never be more than 64k
;
; CX = Transfer count for this range of contig.memory
; ES:DI = Page break info buffer        ; not set if transfer will be done
;       fully by BufRd (and not referred in that case)
;
ddr30a: mov     si,[ddr_hVPB]
        VPBContext ES,SI                ; ES:SI -> VPB
        CALL    BreakDown

; Transfer the head portion ( if there is one)

ddr35:  mov     si,CurrTCB
        cmp     TASKAREA:[si].TCBBytCnt1,0
        JZ      ddr40                   ; No transfer for "head". See if any for mid.
;
; Read the "head" part of the transfer. This is for a partial sector. We do
; the transfer through a kernel buffer.
;
        mov     [ddr_Trans],-1
        mov     cx,TASKAREA:[si].TCBBytCnt1
        SaveReg <bp,si>                 ; Save stack frame pointer
        mov     si,[ddr_NorDirFlg]
        mov     bp,[ddr_hVPB]
        CALLFAR BufRd
        RestoreReg <si,bp>
        LJC     ddrerr
;
        test    [ddr_fStrat2?],-1       ; if strat1, we don't need to adjust
        jz      short ddr40             ; the pagelist

; strat2; adjust the first entry in the pagelist to compensate for the
; above Buffer Read of head portion.

        lds     di,[ddr_PageList]
        mov     cx,TASKAREA:[si].TCBBytCnt1
        mov     dx,ddr_NoOfPgBrks
        call    AdjustPageList
        mov     [ddr_PageList].Offst,di
        mov     [ddr_NoOfPgBrks],dx
;
; middle portion of the transfer; this is a read of integral sectors.
; we break this up into contig.clusters and pass one request to the DD in
; case of old device drivers.
;
ddr40:  cmp     TASKAREA:[si].TCBSecCnt,1
        jb      ddr50
        je      ddr45

; Read direct if more then one sector.
        call    DoCPDiskRead
        LJC     ddrerr
        jmp     ddr50
ddr45:
        SaveReg <bp>
        mov     si,[ddr_NorDirFlg]
        test    [ddr_Trans],-1      ; do we need to advance sector pos info?
        mov     bp,[ddr_hVPB]
        chkvselhd es,bp
        jz      ddr46               ; no, go skip advance of pos info
        call    NextSec
ddr46:  RestoreReg <bp>
        jc      ddr50               ; no more sectors to read; skip bufread
        mov     si,CurrTCB
        cmp     TASKAREA:[si].TCBBytCnt2,0 ; Has a "tail" ?
        je      ddr48                      ; No - Only one sector to read.
        SaveReg <bp>
        mov     si,[ddr_NorDirFlg]
        mov     bp,[ddr_hVPB]
        chkvselhd es,bp
        CALLFAR BigBufRd                   ; Do middle and tail together.
        RestoreReg <bp>
        mov     si,CurrTCB                 ; Done with transfer. No error.
        jc      ddr48                      ; Failed - do it the old way.
        mov     ax,TASKAREA:[si].TCBBytCnt2
        mov     TASKAREA:[si].TCBBytCnt1,ax
        mov     TASKAREA:[si].TCBBytSecPos,0
        jmp     ddr70                      ; We're done

; Read through cache buffer if exactly one sector.
ddr48:  mov     TASKAREA:[si].TCBBytSecPos,0
        mov     [ddr_Trans],-1
        SaveReg <bp>
        mov     si,[ddr_NorDirFlg]
        mov     bp,[ddr_hVPB]
        chkvselhd es,bp
        mov     cx,ES:[bp].vpb_fsi.vpi_cbSector
        CALLFAR BufRd
        RestoreReg <bp>
        jc      ddrerr
        mov     si,CurrTCB
        mov     TASKAREA:[si].TCBBytCnt1,0

; See if there is a "tail" part of the transfer.
;
ddr50:  mov     si,CurrTCB
        mov     ax,TASKAREA:[si].TCBBytCnt2
        OR      AX,AX
        JZ      ddr70                   ; No "tail" to read
        mov     TASKAREA:[si].TCBBytCnt1,ax
;
; There is a "tail" part of the transfer. This is for a partial sector. We do
; the transfer through a kernel buffer.
;
        test    [ddr_Trans],-1          ; do we need to advance sector pos info?
        JZ      ddr60                   ; no, go skip advance of pos info
        SaveReg <bp>
        mov     si,[ddr_NorDirFlg]
        mov     bp,[ddr_hVPB]
        chkvselhd es,bp
        call    NextSec
        RestoreReg <bp>
        cmc
        JNC     ddr70           ; NC -> no more sectors to read; skip bufread
        mov     si,CurrTCB      ; Done with transfer. No error.
ddr60:  mov     TASKAREA:[si].TCBBytSecPos,0
        mov     [ddr_Trans],-1
        mov     cx,TASKAREA:[si].TCBBytCnt1
        SaveReg <bp>
        mov     si,[ddr_NorDirFlg]
        mov     bp,[ddr_hVPB]
        chkvselhd es,bp
        CALLFAR BufRd
        RestoreReg <bp>

; end of all reads (head,tail, middle and all else you can think of!)
; Get back the page info buffer and go for the next range

ddr70:  les     di,[ddr_PageList]
        jc      ddrerrNoRest            ; get out; error in read
        test    [ddr_fStrat2?],-1       ; if Strat2, IO is over; quit
        jnz     short ddr75

; Using strat1 ; go for the next read ( for the next piece of contig. memory)

        dec     ddr_NoOfPgBrks          ;
        jz      ddr75                   ; no more ranges to read
        mov     si,CurrTCB
        mov     ax,TASKAREA:[si].TCBBytCnt1
        add     TASKAREA:[si].TCBBytSecPos,ax
        add     di,     SIZE    pagelist_s
        mov     [ddr_PageList].Offst,di
        jmp     ddr30

; All ranges are read.
; Go see if we allocated a buffer for page break info and release it

ddr75:  mov     cx,ddr_count            ; no of bytes read
ddr80:  jnc     ddr100
ddr90:  mov     cx,0            ; if error CX shd be 0, do not clear carry
ddr100: LeaveProc
        ret

ddrerr: les     di,[ddr_PageList]
ddrerrNoRest:
        stc
        jmp     short ddr90

EndProc DoDiskRead


Procedure       AdjustPageList,near,local
; adjusts the pagelist after the initial buffer read/write
;
; ENTRY:        ds:di -> pointer to pagelist
;               cx      no of bytes of IO done already
;               dx      no of entries in the pagelist
; EXIT:         ds:di -> pointer to pagelist for the next IO
;               dx      no of entries remaining in the pagelist
;
        Assume ds:nothing,es:nothing,SS:TASKAREA

apl00:  cmp     ds:[di].pl_cb.LowWord,cx        ; is the first page bigger
        ja      apl20                           ; than the initial buffered IO ?
        je      apl10                           ; equal ? throw the first entry
; the first transfer crosses over to the second entry of the page list
        sub     cx,ds:[di].pl_cb.LowWord        ; no of bytes done already in
                                                ; second page
        dec     dx                              ; throw the first entry out
        add     di,     SIZE    pagelist_s      ;
        jmp     apl00

; the first transfer was equal to the first entry in the pagelist
apl10:  dec     dx
        add     di,     SIZE    pagelist_s
        ret
; the first transfer was less than the first entry size in the page list

apl20:  sub     ds:[di].pl_cb.LowWord,cx        ;
        add     ds:[di].pl_paddr.LowWord,cx     ;
        adc     ds:[di].pl_paddr.hiword,0
        ret

EndProc AdjustPageList

BREAK   <FATReadInit - inits some TCBVars for DOS_READ>

;***   FATReadInit
;       This procedure inits some of the TCBVars (God knows what)  for a
;       read operation
;
;       IN:     BP = stack frame of DoDiskRead
;       OUT:    NC ->   TCBLastPos,TCBClusNum and some *.* TCB vars set
;               CY ->   error in finding the required cluster
;
;       USES:   AX,CX,SI,DI,DS,ES
;
;       CALLS:  FNDCLUS for normal file reads (not called for DASD reads)
;

Procedure FATReadInit,NEAR,LOCAL

        assume  DS:nothing,ES:nothing,SS:TASKAREA

        test    [ddr_NorDirFlg],-1      ; direct access?
        jnz     fri10                   ; yes, go skip cluster comp.
;
; Normal file i/o
;
        mov     ax,ddr_hVPB
        call    InitTCBVars

fri10:  ret
EndProc FATReadInit

BREAK   <InitTCBVars -inits some TCBVars for DOS_READ>
;***   InitTCBVars
;       This procedure inits some of the TCBVars (God knows what)  for a
;       read operation
;
;       IN:     AX = hVPB
;       OUT:    NC ->   TCBLastPos,TCBClusNum and some *.* TCB vars set
;               CY ->   error in finding the required cluster
;
;       USES:   AX,CX,SI,DI,DS,ES
;
;       CALLS:  FNDCLUS for normal file reads (not called for DASD reads)
;

Procedure InitTCBVars,<hybrid,far16>

        assume  DS:nothing,ES:nothing,SS:TASKAREA

        mov     si,CurrTCB
        mov     cx,TASKAREA:[si].TCBClusNum
;
        SaveReg <es,bp>                 ; Save stack frame pointer
        mov     bp,ax
        VPBContext      es,bp
        CALLFAR FNDCLUS
        RestoreReg <bp,es>
        JC      itcberr
        OR      CX,CX
        JNZ     itcb12                  ; CX != 0 means end-of-file.
        mov     si,CurrTCB
        mov     TASKAREA:[si].TCBLastPos,dx
        mov     TASKAREA:[si].TCBClusNum,bx
itcbx:  ret
itcberr:call    SetAccessDenied
        jmp     short itcbx

itcb12: call    WrtErr                  ; Process any error code
        jmp     itcbx
EndProc InitTCBVars

BREAK   <FATRWLockMem   - locks the user memory, and gets the contig.break info>

BREAK   <FATDevLockMem  - locks the user memory ,gets contig.break info for DEV>

;*** FATRWLockMem
;       This Procedure locks the user memory and queries page manager for
;       page contiguity info for the given user buffer
;
;       INPUT:  cx = count
;               dx = access rights needed for the user buffer
;               TCBDMAAdd ->transfer address
;               AX = VML_16M if DD cannot access memory above 16MB
;                    VML_CONTIG if level 1 or level 2 Device driver
;                    0 otherwise       ---- ONLY FOR FATDevLockMem ------
;               es:di = buffer for pagebreak info
;
;       OUTPUT: NC ->   cx = no of breaks
;                       es:di = temp buff where page break info is stored
;               CY -> invalid user buffer ??
;
;       USES    es,di,cx,ax,bx,flags
;
;       PSEUDOCODE:
;               call ioBeginTransfer to lock the user memory
;               if contig was specified (for FATDevLockMem)
;                       return success (no need to get page break info)
;               Convert the User selector to 0:32
;               Convert temp buf ptr also to 0:32
;               Call PGLinToPage to get page break info
;
;       NOTES:  Does not lock the memory in case of
;                       i) SWAPPER
;                       ii)  partial sector IO
;               and returns success immediately
;

CpuMode 386

Procedure       FATRWLockMem,NEAR,LOCAL
        assume  ds:nothing,es:nothing,ss:TASKAREA

LocalVar        flm_tmpbuf,DWORD
LocalVar        flm_count,WORD
LocalVar        flm_tran_addr,DWORD
LocalVar        flm_bufcnt,DWORD

        test    TASKAREA:[bx].TCBfSwapping,SM_TCB_SWAPPING
        jz      short flm20     ; swap IO always to CONTIGUOUS memory (pages)

; swapper IO ; convert the start address to a physical address and
; store it in the page list
; BX = CurrTCB

        SaveReg <ds,si>
        lds     si,TASKAREA:[bx].TCBDMAAdd      ; ds:si -> transfer address
        CALLFAR VirtToPhys                      ; ax:bx -> phys. address
        RestoreReg      <si,ds>                 ; no other register affected
IFDEF FSSTRICT
        jc      flmiie                          ; we are hosed! got error for
                                                ; a memory addr given to us by
                                                ; swapper
ENDIF
        mov     es:[di].pl_paddr.Segmt,ax       ;
        mov     es:[di].pl_paddr.Offst,bx       ;

flm10:  movzx   ecx,cx
        mov     es:[di].pl_cb,ecx   ; This can never be more than 64k
flm15:  mov     cx,1                ; no page breaks ; contiguous memory
        clc                         ; no error.
        ret

; more than one sector read
; FAT Disk Device Driver and not Swapper call;
; Find out whether the DD supports memory access above 16MB
; bx = currtcb
flm20:  SaveReg <es,di>
;        mov     ax,VML_16M      ;
        mov     ax,0            ; HACK HACK HACK
        mov     di,TASKAREA:[bx].TCBThishVPB
        VPBContext      es,di   ; es:di -> VPB
        test    es:[di].vpb_fsi.vpi_flags,VPB_ABOVE16M_ALLOWED
        RestoreReg      <di,es>
        jz      flm30
        xor     ax,ax

; AX = VML_16M if the DD cannot access memory above 16MB
; else 0


Entry   FATDevLockMem

flm30:  EnterProc       <es,di,cx>
;
; lock the user memory first
;

        SaveReg <ds>
        push    eax
        xor     eax,eax
        mov     flm_bufcnt,eax
        pop     eax

        mov     di,TASKAREA:CurrTCB     ; ss:di -> CurrTCB
        mov     TASKAREA:[di].TCBfIOLock,0    ; No lock yet.

        mov     bx,ss
        cmp     bx,TASKAREA:[di].TCBDMAAdd.Segmt ; Stack Selector ?
        jne     flm31
        jmp     flm99        ; yes - get out

flm31:
        FlatContext <ds,es>,bx

        SaveAlignESP <ebx>
        lea     ebx,flm_bufcnt          ; (ebx) = address of entries count.
        SSToDS  ebx
        PushP   ebx                     ; arg6: -> # of entries in page list.

        movzx   ebx,flm_tmpbuf.Offst    ; (ebx) = address of page list buffer.
        SSToDS  ebx
flm35:  PushP   ebx                     ; arg5: -> page list buffer.

        xor     ebx,ebx
        mov     bx,ax                   ; VML_16M and/or VML_CONTIG  OR 0
        cmp     dx,ACCTYPE_RW
        jne     flm40
        or      ebx,VML_DIRTY           ; we are going to dirty the memory
;
;  The setting of long term lock in stead of short prevents handlers
;  that use this devhlp routine from expired locks. Defect 74070 (by GWD)
;
flm40:  or ebx,VML_LONG                 ; def. 74070 GEB fix
        PushP   ebx                     ; arg4:  flags (DIRTY? + NON_CONTIG +
                                        ;               ABOVE_16MB?)
        xor     ebx,ebx
        mov     bx,cx                   ; (ebx) = size of transfer
        PushP   ebx                     ; arg3: size of transfer

        assume  ds:nothing,es:nothing

        mov     bx,TASKAREA:[di].TCBDMAAdd.Offst ; (ebx) = arg2: offset
        PushP   ebx                     ; arg2: offset
        mov     bx,TASKAREA:[di].TCBDMAAdd.Segmt ; (ebx) = arg1: segment
        PushP   ebx                     ; arg2: segment

        assume  ds:FLAT,es:FLAT

        CCALL32 _ioBeginTransfer,,,PushP ; BeginTransfer
        pop     esp

        assume  ds:nothing,es:nothing
        or      ax,ax
        jnz     flmerr

        mov     ecx,flm_bufcnt
        cmp     cx,0                    ; pagelist from iobegintransfer ?
        je      flm45

        cmp     cx,1                    ; all done - get out
        jne     flm99

        movzx   ecx,flm_count
        les     di,flm_tmpbuf
        mov     es:[di].pl_cb,ecx  ; This can never be more than 64k
        mov     cx,1                    ;contiguous memory, no breaks

        jmp     flm99                   ; Yes.


flm45:

; convert the user selector to a 0:32 ptr

        mov     di,TASKAREA:CurrTCB
        movzx   eax,TASKAREA:[di].TCBDMAAdd.Segmt
        FlatContext <ds,es>,bx
        CCALL32  _SELConvertToLaddr,<eax>

IFDEF FSSTRICT
        cmp     eax,-1                          ; error ?
        je      short flmiie
ENDIF

        assume ds:nothing,es:nothing

        mov     di,TASKAREA:CurrTCB
        movzx   ebx,TASKAREA:[di].TCBDMAAdd.Offst
        add     eax,ebx                         ; laddr +=offset
        mov     flm_tran_addr,eax

; convert  the 16:16 temp buffer address to 0:32
; since this is on stack use sstods

        movzx   eax,flm_tmpbuf.Offst

        ASSUME  DS:FLAT
        SSToDS  eax

        movzx   ecx,flm_count           ; (ecx) = count of bytes
                                        ; parm4: page info buffer
                                        ; parm3: count of bytes
                                        ; parm2: context
                                        ; parm1: base linear address
        CCALL32  _PGLinToPageList,<flm_tran_addr,[_pPTDACur],ecx,eax> ; (eax) = number of elements in array
        mov     cx,ax
flm99:
        RestoreReg      <ds>
        assume ds:nothing,es:nothing
        clc
        les     di,flm_tmpbuf
flmx:   LeaveProc
        ret

flmerr: stc
        pop     ds
        movzx   ecx,flm_count
        les     di,flm_tmpbuf
        mov     es:[di].pl_cb,ecx  ; This can never be more than 64k
        mov     cx,1                    ;contiguous memory, no breaks
        jmp     short flmx

IFDEF FSSTRICT
flmiie: InternalError   <FATRWLockMem: invalid transfer address?>
ENDIF

EndProc FATRWLockMem


CpuMode 286
BREAK   <DoCPDiskRead - Reads integ. no of sectors from disk>

;***    DoCPDiskRead - reads integral no of sectors for DOS_READ
;
;       DoCPDiskRead is the worker used to read clusters off the disk.
;
;       WARNING: This uses the stack frame DoDiskRead.
;
;       ENTRY   TCBThishVPB = handle to VPB segment if disk file
;               TCBThisSFT set up with pointer to current SFT
;               TCBNextAdd = Displacement of disk transfer within segment
;               ES      = VPB Sel
;               SI Points to current TCB
;
;       EXIT    'NC' if success
;               'C' if error
;
;       USES    ALL
;
;       PSEUDOCODE:
;               Advance to NextSec if needed
;               While (Sectors left to read) {
;                       call SetupPhysIO to get the next contig. set of
;                         clusters
;                       Call DREAD to read those clusters of the disk
;                       Update the data read, from any dirty buffers for
;                               the same sector/cluster by going through
;                               the buffer cache with BufNext and doing
;                               a SuBuff if needed.
;                       go for the next contig.set of clusters;
;               }
;
Procedure   DoCPDiskRead,NEAR
    ASSUME DS:NOTHING, ES:NOTHING, SS:TASKAREA

;
; Read in the middle part of the transfer. This is a transfer of an integral
; number of sectors.
;
CpuMode 386
        xor     eax,eax
        mov     TASKAREA:[si].TCBBytSecPos,ax
        xchg    TASKAREA:[si].TCBBytCnt1,ax
        test    [ddr_fStrat2?],-1       ; if strat2 the following code is not
        jnz     dpdr05                  ; needed
; strategy1 ;
; Physical addresses in PageList  is for the start of the transfer address.
; But we might have done some partial sector IO (count = TCBBytCnt1) through
; kernel buffers ; So we have to add the appropriate offset to the physical
; address.
;
        mov     bx,[ddr_PageList].Offst ; [ss:bx]-> PageList entry.
        add     ss:[bx].pl_paddr,eax    ; [ds:bx]-> physical transfer address.
        sub     ss:[bx].pl_cb,eax       ; Bytes remaining in this transfer.
CpuMode reset

;
dpdr05: test    [ddr_Trans],-1          ; do we need to advance sector pos info?
        JZ      dpdr10                  ; no, go skip advance of pos info
        SaveReg <bp>                    ; Save stack frame pointer
        mov     si,[ddr_NorDirFlg]
        mov     bp,[ddr_hVPB]
        call    NextSec
        RestoreReg <bp>
        LJC     dpdr90                  ; No more sectors to read

dpdr10: mov     di,CurrTCB
        mov     [ddr_Trans],-1          ; A transfer is taking place
        mov     dl,TASKAREA:[di].TCBSecClusPos
        mov     cx,TASKAREA:[di].TCBSecCnt
        mov     bx,TASKAREA:[di].TCBClusNum
;
;   The following loop uses the FAT to optimize contiguous I/O.  We call
;   Setup_PhysIO to find the largest contiguous piece we can read.  We read
;   it.  Then we scan the buffer cache for read-through.  Any overlapping
;   buffers are copied out.
;
;   DL = sector within cluster from which to start READ
;
RDLP:   MOVFAROFFSET di,BufIncRef,HIGH2CODE
        mov     ax,[ddr_hVPB]
        mov     si,[ddr_NorDirFlg]
        call    Setup_PhysIO            ; SI:DX = sector number
        LJC     dpdrx
    ASSUME      DS:NOTHING

;
;   (AX)    number of sectors remaining (after this transfer)
;   (DS:BX) ptr to transfer address (virtual address)
;   (CX)    number of sectors in this transfer
;   (SI:DX) physical sector number (transfer start)
;   (DI)    next cluster
;
        SaveReg <bp>                    ; Save stack frame pointer
        SaveReg <DI,AX>
        SaveReg <BX,SI,DX,CX>
;
;  set AL = 0 for cache based read
;         = ReadBypassAdj for no-caching
;      AH = 0 virtual address in (ds:bx)
;           1  physical address ptr in (ds:bx)
;  call DRead to do disk input for Strat1
; and DRead2 for Strat2
;
        mov     ax,0100h                ; assume normal read
        mov     di,[CurrTCB]
        test    TASKAREA:[di].TCBfSwapping,SM_TCB_SWAPPING
        jz      dpdr20                  ; not swappinc, go do normal read
        add     al,ReadBypassAdj        ; swapping, so do bypass read

dpdr20: xchg    di,si                   ; DI:DX = sector number

; Mark the fact that it is OK for the low level routine to handle any hard
; error paction. This is because it is OK to free any buffer that we might
; be using as part of this read operation.
;
        mov     si,TASKAREA:[CurrTCB]
        and     TASKAREA:[si].TCBBufHE,NOT (BufHE_Percolate+BufHE_CurBufFreed)
;
; call  DRead for strat1 req pkts
;
        test    [ddr_fStrat2?],-1       ; strat2?
        LJNZ    dpdr100                 ; yes, go call DRead2

        lds     bx,[ddr_PageList]       ; BP still holds DoDiskRead Frame ptr

        mov     bp,[ddr_hVPB]           ;
        CALLFAR DRead

; common code for scatter/gather(strat2) and strat1 calls
; at this point we have finished reading. We should update what we read in
; from disk (might be old data) from the system buffers, if there are any
; buffers corresponding to this transfer sectors.
;
dpdr25: RestoreReg <BX,DX,SI,DI>

CpuMode 386


        pushf                           ; Save flags returned by DRead
        SaveReg <eax>                   ; Save return code by DRead
;
; BX = number of sectors in this transfer
; SI:DX = sector number
; DI = Offset part of transfer address
; BP = hVPB
;
;   We've done the transfer.  Now copy newer data from buffers over the
;   older data we transfered off the disk.
;
        BufferContext DS


        SaveReg <ebx,ecx>

        mov     cx,si                   ; (cx:dx) = first sector
        shl     ecx,16
        mov     cx,dx                   ; (ecx) = first sector
        mov     edx,ecx                 ; (edx) = first sector
        movzx   ebx,bx                  ; (ebx) = number of sectors
        add     edx,ebx                 ; (edx) = last sector + 1

        xor     si,si                   ; (si) = 0, start with first buffer

; (ecx) = first sector of transfer
; (edx) = last sector + 1
; ds:si = buffer to continue scan from.  (si) = 0 to start.
; (di) = offset part of transfer address

dpdr30: CALLFAR BufNext                 ; BufNext(si->startbuffer)=>si->buffer
        jc      dpdr40                  ; no more buffs, go finish read
;
; A sector has been read in when a dirty copy of it is in a buffer
; The buffered sector must now be read into the right place
;
        SaveReg <di>
        SaveReg <ecx,si,ds,edx>
        sub     ecx,[si].buf_sector     ; (ecx) = -number of sectors into xfer
        neg     cx                      ; (cx) = number of sectors ( < 64K)
        mov     ax,cx                   ; (ax) = sector offset
        chkvselhd es,bp
        mov     cx,es:[bp].vpb_fsi.vpi_cbSector ; (cx) = size of a sector
        mul     cx                      ; (ax) = byte offset of sector in buffer
        add     di,ax                   ; (di) -> user buffer @ overlap
        lea     si,[si].BUFINSIZ        ; (si) -> kernel buffer data
        mov     bx,CurrTCB
        mov     bx,TASKAREA:[bx].TCBDMAAdd.Segmt ; (bx) = user segment

        CALLFAR SuBuff                  ; copy sector into buffer, trashes ds,es

        VPBContext ES,BP                ; refetch segment pointer
        RestoreReg <edx,ds,di,ecx>      ; (di) = saved (si), buffer offset

        CALLFAR BufDecRef               ; Done with buffer, dec the ref count
        mov     si,di                   ; (si) = buffer offset
        RestoreReg  <di>

        JMP     dpdr30                  ; get the next sector in buffer cache
;
; There are no more sectors in the buffer cache. Go on and finish off the READ.
;
dpdr40: TaskContext DS
        RestoreReg  <ecx,ebx>
        movzx   edx,bx                  ; edx = # of sectors in this transfer.


        RestoreReg <eax>        ; Retrieve error code from DRead (in ax)
        popf                    ; Retrieve return flags from DRead

dpdr50: RestoreReg <CX,BX>      ; CX = # sectors remaining, BX = Next cluster
        RestoreReg <bp>         ; BP = stack frame pointer
        jc      dpdrx
;
;   See if we need to loop back for more fat-scanning.
;   NOTE:  CX will ALWAYS be zero if we are doing a direct read
;
        JCXZ    dpdr90
        SaveReg <bp>
        mov     bp,[ddr_hVPB]
        chkvselhd es,bp

        movzx   eax,es:[bp].vpb_fsi.vpi_cbSector ; eax = Bytes per secotr
        mul     edx


        call    IsEOF                   ; test for EOF on FAT size
        RestoreReg <bp>
        JAE     dpdr90                  ; Done with transfer. No error.

        test    [ddr_fStrat2?],-1       ; strat2?
        jnz     dpdr88                  ; yes, skip pagelist update

        movzx   edx,[ddr_PageList].Offst ; [ss:edx]-> PageList entry.
        add     ss:[edx].pl_paddr,eax   ; Next transfer physical  address.
        sub     ss:[edx].pl_cb,eax      ; Bytes remaining in next transfer.
dpdr88:
CpuMode reset

        MOV     DL,0                    ; Start at first sector within cluster
        mov     si,CurrTCB
        inc     TASKAREA:[si].TCBLastPos  ; We'll be using next cluster
        JMP     RDLP

dpdr90: CLC                             ; Clear carry to indicate no error
dpdrx:  ret


; For Strat2 request (Scatter(/gather) )
; We have to call DRead2 instead of DRead
; ds,si is the only register that is free now

dpdr100: SaveReg        <ax>
        lds     si,[ddr_PageList]       ; get the pointer
        SaveReg <bp>
        mov     bp,[ddr_hVPB]
        call    AdjPgLstBefIO           ; adjust the count for the last entry
        RestoreReg      <bp>            ; si = no of entries in the page list
        mov     [ddr_Remnant],ax        ; for this transfer
        mov     [ddr_EntryCount],si     ; ax = Remnant for the last entry
        RestoreReg      <ax>            ;
        SaveReg <bp>

        CpuMode 386
        mov     ebx,[ddr_PageList]      ; si = no of entries for this transfer
        mov     bp,[ddr_hVPB]
        CALLFAR DRead2
        RestoreReg      <bp>
        pushf                           ; Save flags returned by DRead
        lds     si,     [ddr_PageList]  ;
        mov     bx,[ddr_EntryCount]     ;
        dec     bx
.errnz  ( SIZE  pagelist_s - 8 )
        rol     bx,3                    ;
        add     si,bx                   ; ds:si -> last entry of this transfer
        cmp     [ddr_Remnant],0         ;did the transfer ended on a entry exact
        jz      short dpdr21            ; yes, just increment the pointer
        movzx   eax,ds:[si].pl_cb.LowWord ; update the address part
        add     ds:[si].pl_paddr,eax    ;
        mov     ax,[ddr_Remnant]
        mov     ds:[si].pl_cb.LowWord,ax ; update the count
        jmp     short dpdr22
dpdr21: add     si, SIZE        pagelist_s
        add     bx, SIZE        pagelist_s
dpdr22: mov     [ddr_PageList].Offst,si ; update the pagelist pointer
        ror     bx,3                    ; no of entries to move forward
        sub     [ddr_NoOfPgBrks],bx     ;

        popf
        CpuMode reset
        mov     bp,[ddr_hVPB]
        jmp     dpdr25                  ; go update buffers from this read

EndProc DoCPDiskRead


Procedure       AdjPgLstBefIO,near,local
; ENTRY:        ds:si = start of pagelist table for this transfer
;               es:bp = VPB
;               cx    = count of sectors

        SaveReg <cx,dx>
        mov     ax,cx
        mul     es:[bp].vpb_fsi.vpi_cbSector ; CAN NEVER BE MORE THAN 64K
; ax = no of bytes for current transfer ; dx = 0
        xchg    cx,ax
aplb10: inc     dx
        sub     cx,ds:[si].pl_cb.LowWord
        jc      aplb90
        add     si, SIZE        pagelist_s
        jcxz    aplb20
        jmp     short aplb10

; transfer ended on a page boundary     (and on a page entry end)

aplb20: mov     ax,cx                   ; ax = remaining bytes on this pg entry

aplb40: mov     si,dx                   ;
        RestoreReg      <dx,cx>
        ret

; this entry spreads over to the next transfer

aplb90: mov     ax,cx
        neg     ax                      ; remaining bytes left on this transfer
        sub     ds:[si].pl_cb.LowWord,ax ; set the count on the last entry for
                                        ; this transfer
        jmp     short aplb40

EndProc AdjPgLstBefIO

;***    SetAccessDenied - triage an error for 3x compatibility
;
;       SetAccessDenied takes an error code and potentially maps it for
;       3.x compatibility.  Only ERROR_PROTECTION_VIOLATION is retained.
;
;       ENTRY   (ax)    contains error code
;
;       EXIT    (ax)    contains possibly modified error code
;               (cx)    0 (for no bytes transferred)
;               (ds)    TASKAREA
;               carry set
;
;       USES    ax, cx, ds
;
Procedure   SetAccessDenied,hybrid,LOCAL
        assume  es:nothing,ds:nothing
        TaskContext DS

        XOR     CX,CX
        cmp     ax,ERROR_PROTECTION_VIOLATION ; Bad user segment?
        je      sae1                    ;   Yes, leave error code alone

        mov     ax,ERROR_ACCESS_DENIED  ; Map error code (compatible!)

sae1:   STC
        ret
EndProc SetAccessDenied


;***    SetSFT - Update the SFT based upon a transfer
;
;       SetSFT uses the global TCB variables set in the DiskRead/DiskWrite
;       routines to update an SFT.
;
;       ENTRY   [TCBNextAdd]    points beyond successful data moved
;               [TCBDMAAdd]     points to beg of transfer
;               [TCBClusNum]    number of last cluster accessed in file
;               [TCBLastPos]    relative position in file of [TCBClusNum]
;               (ds)            TASKAREA
;
;       EXIT    (es:di)     point to SFT:
;                           sf_fsi.sfi_position updated
;                           sf_fsd.sfdFAT_lstclus/sf_fsd.sfdFAT_cluspos
;                               updated iff FAT-FS transfer
;               (cx)        number of bytes transferred
;               carry clear
;
;       USES    ax, cx, di, es, flags
;
;
Procedure   SetSFT,NEAR,LOCAL
        assume  ds:TASKAREA, ES:NOTHING

        push    bp
        mov     bp,CurrTCB

        les     di,TASKAREA:[bp].TCBThisSFT              ; (es:di) SFT of transfer

        mov     cx,TASKAREA:[bp].TCBNextAdd              ; (cx) offset beyond transfer
        sub     cx,TASKAREA:[bp].TCBDMAAdd.Offst         ; (cx) # transfered

        TEST    ES:[DI].sf_fsi.sfi_mode,open_direct     ; device or direct IO
        jnz     ssft1
        TEST    ES:[DI].sf_flags,DEVID_DEVICE   ; device or direct IO
        JNZ     ssft1                           ; yes, go skip cluster update

        mov     AX,TASKAREA:[bp].TCBClusNum
        MOV     ES:[DI.sf_fsd.sfdFAT_lstclus],AX
        mov     AX,TASKAREA:[bp].TCBLastPos
        MOV     ES:[DI.sf_fsd.sfdFAT_cluspos],AX

ssft1:  UpdateSFTPosition
        pop     bp
        ret
EndProc SetSFT


Break   <DiskWrite -- PERFORM USER DISK WRITE>

;***    DiskWrite - Perform user disk write
;
;       Diskwrite transfers data from user space to disk.
;
;       ENTRY   AX:BX = byte position in file
;               CX = byte count
;               ThishVPB   = handle to VPB segment if disk file
;               ES:DI Points to SFT
;               [NextAdd] = Displacement of disk transfer within segment
;               [TCBThisSFT] = sft
;               DS = TASKAREA
;
;       EXIT    Carry Clear
;                       CX = No. of bytes written
;                       SFT offset and cluster pointers updated
;                       ES:DI -> SFT
;               Carry set
;                       CX = 0
;                       AX has error code
;
;       USES    AX, BX, CX, DX, SI, Flags
;
Procedure   DiskWrite,NEAR,LOCAL
        ASSUME  ES:NOTHING, DS:TASKAREA

LocalVar    dskwr_BytPos,DWORD          ; Byte position of transfer within file

        SegAssert SS,<DS>,"DiskWrite"
        EnterProc <AX,BX>               ; Initialize local variable
        SaveReg <bp>                    ; Save pointer to stack frame for return
dskwr10:test    es:[di].sf_fsi.sfi_DOSattr,attr_volume_id  ; cannot write to a volume id!
        LJNZ    dskwraccerr
        CALLFAR fVolumeWriteable        ; Can we write to the volume?
        LJC     dskwrx                  ;   NO, exit with error
;
; It is OK to proceed with the WRITE operation
;
dskwr40:AND     ES:[DI.sf_flags],NOT DEVID_FILE_CLEAN ; Mark file as dirty
        mov     ax,[dskwr_BytPos].LowWord
        mov     dx,[dskwr_BytPos].HighWord  ; AX:BX = byte offset within file
;
; For normal WRITE operations, if we are writing zero bytes at the current
; position, then it is assumed that that is the EOF.
;
        test    es:[di].sf_fsi.sfi_mode,open_direct
        jnz     dskwr45                 ; This is a Direct WRITE.
        or      cx,cx
        jz      WrtEOF                  ; We are writing zero bytes
dskwr45:mov     bx,IOTYPE_FILE
        call    DoDiskWrite
        jc      dskwrerr                ; Error. Map error code to correct error.
dskwrx: RestoreReg <BP>                 ; Restore pointer to stack frame
        LeaveProc
        ret

;** Zero-byte WRITE
;
;   At this point, we are instructed to do a zero-byte write at the current
;   file position.  We call DiskUpdateFileSize to do the necessary work.
;
;   (dx:ax) = byte position in file (new size)
;   (ES:DI) -> SFT
;
WrtEOF: call    DiskUpdateFileSize      ; We are done with the write
        jnc     dskwrx                  ; but check for any error and
                                        ; suppress only "DISK_FULL" error
;** Error from DoDiskWrite
;
; We had an error from DoDiskWrite. We check to see if it is a "disk full"
; error. If so, we are not supposed to return with 'C' set (see WrtErr).
;
dskwrerr:
        call    WrtErr
        jmp     short dskwrx            ; Return with error code in AX

;** Other errors
;
; We return an "access denied" error to the caller
;
dskwraccerr:
        call    SetAccessDenied
        jmp     short dskwrx

EndProc DiskWrite


;***    DiskUpdateFileSize - Write EOF to file
;
;       We are to do a zero-byte write at the current file position. We
;       set the file length to be equal to the current file position. We
;       do this by either releasing clusters or allocating clusters.
;       If we are at the beginning of the file, the the effect is to
;       truncate the file.
;
;       NOTE    This routine is used to re-size a file.
;
;       ENTRY   DX:AX = current position in file (= New size of file)
;               DS = TASKAREA
;
;       EXIT    'C' if (error)
;                   AX has error code
;               'NC' if everything was fine
;
;       USES    AX, CX, DX, DI, SI, ES, Flags
;
Procedure   DiskUpdateFileSize,NEAR

    ASSUME ES:NOTHING, DS:TASKAREA, SS:TASKAREA
        SegAssert SS,<DS>, "DiskUpdateFileSize"

LocalVar    dweof_BytePos,DWORD         ; Store current position in file
LocalVar    dufs_Vollock_type,WORD
LocalVar    dufs_hVPB,WORD
LocalVar    dufs_firclus,WORD

        EnterProc <DX,AX,0>             ; Set local variable
        push    bp                      ; Preserve stack frame pointer
        push    bx
        mov     bx,CurrTCB
        les     di,TASKAREA:[bx].TCBThisSFT
        mov     ax,es:[di].sf_fsd.sfdFAT_firFILEclus
        mov     dufs_firclus,ax
        mov     ax,dweof_BytePos.loword
        mov     bx,TASKAREA:[bx].TCBThishVPB
        mov     dufs_hVPB,bx
        mov     bp,bx
        pop     bx
        VPBContext ES,BP
        MOV     CX,AX
        OR      CX,DX
        LJZ     KILLFIL                 ; Truncate the file
        SUB     AX,1
        SBB     DX,0                    ; (dx:ax) = zero based byte position

; NOTE: must divide by sectors per cluster first or DIV may overflow

        MOV     CL,ES:[bp].vpb_fsd.vpdFAT_cluster_shift
        xor     ch,ch                   ; (cx) = number of bits to shift
        jcxz    dufs2           ; no shifting

dufs1:  shr     dx,1
        rcr     ax,1
        loop    dufs1                   ; (dx:ax) = (dx:ax) / (sectors/cluster)

dufs2:  mov     cx,es:[bp].vpb_fsi.vpi_cbSector ; (cx) = bytes/sector
        cmp     dx,cx
        jae     dufs3                   ; div will overflow

        div     cx                      ; (ax) = (dx:ax) / (bytes/sector)
                                        ;      = ordinal cluster number

        MOV     CX,AX
        CALLFAR FNDCLUS
        jnc     dufs2b

; see if we have cluster wrap around error
; ax = ordinal cluster number

        inc     ax
        jz      dufs3                   ; cluster number overflow
        xor     ax,ax                   ; FNDCLUS doesn't return error code
        jmp     dweof_errpopbp          ; other errors

dufs2b: JCXZ    RELFILE                 ; File has shrunk. Release some clusters

;
; The file has grown in size. Allocate the required clusters on the disk.
;
        pop     bp
        push    cx
        mov     cx,VPB_ALLOC_SHARE
        mov     dufs_Vollock_type,cx
        CALLFAR Volume_Alloc_Lock
        pop     cx
        push    bp
        mov     bp,dufs_hVPB
        call    Allocate
        pop     bp                      ; Restore BP of entry
        jc      godweofx                ; return with error if alloc failed
        jmp     UpdateSize              ; allocation was successful

dufs3:  mov     ax,ERROR_DISK_FULL      ; cluster > ffff
        stc
        pop     bp
godweofx: jmp   dweofx

;
; The file has shrunk in size. Release the clusters that are no longer needed
; for the file.
;
; DX = ord of last cluster in file
; ES:BP -> VPB
;
RELFILE: pop    bp
        push    cx
        mov     cx,VPB_ALLOC_EXCL
        mov     dufs_Vollock_type,cx
        CALLFAR Volume_Alloc_Lock
        pop     cx
        push    bp
        mov     bp,dufs_hVPB
        chkvselhd es,bp                 ; Ensure ES:BP -> VPB
        mov     ax,dx                   ; (ax) = ord of last cluster
        MOV     DX,0FFFFH
        CALLFAR CMShrink
        call    RelBlks
        pop     bp                      ; Restore stack frame pointer
        LJC     dweof_err
;
; Reset sf_cluspos and sf_lstclus to first cluster of file to avoid
; problem: if sf_lstclus contained the number of a cluster that we have
; just released, we could still do I/O to that cluster (because FNDCLUS
; will return success, instead of failing and forcing an allocation),
; and the file size (in the directory) would be updated to include that
; cluster, BUT the cluster WOULD NOT be linked in our FAT chain!
;
; sf_fsd.sfdFAT_lstclus - the cluster where I/O was last performed
; sf_fsd.sfdFAT_cluspos - the ordinal in the file of the sf_lstclus cluster
;
        SaveReg <si,ds>
        mov     si,CurrTCB
        lds     si,TASKAREA:[si].TCBThisSFT ; (ds:si) -> SFT
    assume ds:nothing
        mov     ax,ds:[si].sf_fsd.sfdFAT_firFILEclus   ; (ax) = first cluster of file
        mov     ds:[si].sf_fsd.sfdFAT_lstclus,ax       ; set cluster
        mov     ds:[si].sf_fsd.sfdFAT_cluspos,0        ; set cluster ordinal
        RestoreReg <ds,si>
        JMP     SHORT UpdateSize

;
; The new size of the file is zero. Release all the clusters that had been
; allocated to it previously.
;
; (TOS) = BP of entry
; ES:BP -> VPB
;
KILLFIL: pop    bp
        push    cx
        mov     cx,VPB_ALLOC_EXCL
        mov     dufs_Vollock_type,cx
        CALLFAR Volume_Alloc_Lock
        pop     cx
        push    bp
        mov     bp,dufs_hVPB
        chkvselhd es,bp                 ; Ensure ES:BP -> VPB
        SegAssert SS,<DS>, "DiskUpdateFileSize"
    ASSUME DS:TASKAREA
        XOR     BX,BX
        PUSH    ES
        mov     di,CurrTCB
        les     di,TASKAREA:[di].TCBThisSFT
        MOV     ES:[DI.sf_fsd.sfdFAT_cluspos],BX
        MOV     ES:[DI.sf_fsd.sfdFAT_lstclus],BX
        XCHG    BX,ES:[DI.sf_fsd.sfdFAT_firFILEclus]
        POP     ES
        OR      BX,BX
        JZ      dweof30                 ; No clusters to release. 'C' clear.
        CALLFAR CMShrink                ; shrink cluster map
        call    Release
dweof30:pop     bp                      ; Restore stack frame pointer
        jc      dweof_err               ; Error from Release. Return.

;
; Update the SFT to reflect the new size of the file.
;
; BP points to the stack frame.
;
UpdateSize:
        mov     si,CurrTCB
        les     di,TASKAREA:[si].TCBThisSFT
        mov     ax,[dweof_BytePos].LowWord
        mov     es:[di].sf_fsi.sfi_size.LowWord,ax
        mov     ax,[dweof_BytePos].HighWord
        mov     es:[di].sf_fsi.sfi_size.HighWord,ax
;
; Make sure directory entry is updated when we "commit"
;
        and     es:[di].sf_flags,NOT DEVID_FILE_CLEAN

;
; Make sure that all other SFT's see this growth also.
;
        mov     ax,2
        CALLFAR ShSU
        xor     cx,cx
        UpdateSFTPosition
dweofx: pushf
        test    dufs_Vollock_type,-1
        jz      dufsx
        popf
        pushf
        push    cx
        mov     cx,dufs_Vollock_type
        jc      dufs200
        test    cx,VPB_ALLOC_EXCL
        jnz     dufs190
        cmp     dufs_firclus,0
        jnz     dufs200
dufs190: pusha
         call   FileCommit
        popa
dufs200: CALLFAR        Volume_Alloc_Unlock
        pop     cx
dufsx:  popf
        LeaveProc
        ret

;
; There was some kind of error encountered. We call SetAccessDenied to return
; the appropriate error code in AX.
;
dweof_errpopbp:
        pop     bp                      ; Restore stack frame pointer
dweof_err:
        call    SetAccessDenied
        jmp     short dweofx

EndProc DiskUpdateFileSize


;***    DoDiskWrite - Does the actual write of data to the disk
;
;       DoDiskWrite is the worker used to write clusters to the disk.
;       This routine takes care of WRITE operations for files, direct disk
;       writes and for extended attributes. We differentiate between the
;       file/direct I/O and extended attribute I/O by the flag bit passed
;       in BX.
;
;       WARNING
;
;           For extended attributes, the caller **MUST** allocate enough
;           clusters on disk to hold the full FEAlist before calling the
;           routine to write it out to disk.
;           This routine assumes that no growth or shrinking of the EA chain
;           will happen.
;
;
;       ENTRY   DX:AX = byte position to start WRITE from
;               BX = IOTYPE_FILE if this is for a file or direct access
;                    EA_IO_FLAG   if this is for extended attributes
;               CX = byte count (It *MUST NOT* be zero) (Caller should take
;                    care of that)
;               TCBThishVPB = handle to VPB segment if disk file
;               TCBThisSFT set up with pointer to current SFT
;               ES:DI Points to SFT
;               [TCBNextAdd] = Displacement of disk transfer within segment
;               SI = Cluster on disk where WRITE should start for extended
;                    attributes ONLY (not significant otherwise)
;               DS = TASKAREA
;
;       EXIT    Carry Clear
;                       CX:AX = Number of bytes by which data on disk was grown
;                               (Valid for files only)
;               Carry set
;                       AX has error code
;
;       USES    AX, BX, CX, DX, DI, SI, DS, ES, Flags
;
;       NOTE    There are two possible ways to get errors.
;
;                   - Access denied - We return this if we are unable to
;                     access the data on the disk.
;
;                   - Other errors - These occur in cases where we are trying
;                     to allocate space on the disk.
;       PSEUDOCODE:
;          Find out whether this is a DASD or not by looking at SFT
;          Call BreakDown for the full Count to split the write into
;               head, middle, tail parts (TCBBytCnt1,2,TCBSecCnt set)
;          If (some full sectors to write i.e. TCBSecCnt != 0)
;          {
;               call FATRWLockMem to lock the memory and get the
;                       contig.break info
;          }
;          call FATWriteInit to do some initialisation of TCB Vars
;          for (each set of Contig. Memory) do
;          {
;               call BreakDown again for the current count  and set
;                       TCBBytCnt1 etc properly
;               If (TCBBytCnt1 i.e. some head part to write)
;                       call BufWrt to write it through FileSystem buffer
;               Call DoCPDiskWrite to write middle portion (full sector writes)
;               if (TCBBytCnt2 i.e. some tail to write)
;                       call BufWrt to write it through system buffers
;               go to next set of contig. memory;
;          }
;          Free tempbuffer if allocated for page info buffer
;
;
; equates used for ddw_flags:

fDDW_VOLUME_LOCKED      equ     VPB_ALLOC_SHARE

fDDW_STRAT2             equ     2



Procedure   DoDiskWrite,NEAR
        ASSUME  ES:NOTHING, DS:TASKAREA

LocalVar    ddw_NorDirFlg,WORD          ; 0 => Normal write, 1 => direct write
LocalVar    ddw_Trans,WORD                      ; 0 => No transfers done so far
LocalVar    ddw_GrowCnt,DWORD           ; Amount by which file is to grow
LocalVar    ddw_firEAclus,WORD          ; Start of EA allocated chain
LocalVar    ddw_IOType,WORD             ; File/Direct I/O vs Extended Attribute
LocalVar    ddw_count,WORD              ; no of bytes to write
LocalVar    ddw_NoOfPgBrks,WORD         ; no of breaks in transf.addr contiguity
LocalVar    ddw_flags,WORD              ; 1 = Vol Lock taken;2 = Strat2
LocalVar    ddw_hVPB,WORD
LocalVar    ddw_PageList,DWORD          ; ptr to tempbuffer for pageinfo
LocalVar    ddw_sffirclus,WORD
LocalVar    ddw_PgLstBuffer,PGLSTSIZE   ; tempbuffer for pageinfo
LocalVar    ddw_EntryCount,WORD         ;
LocalVar    ddw_Remnant,WORD            ;
LocalVar    ddw_bytepos,DWORD

        SegAssert SS,<DS>,"DoDiskWrite"
        EnterProc <0,0,0,0,SI,BX,CX,1,0>
;
; Initialize local variables as follows:
;       - NorDirFlg set to 0 (presume this is not a direct WRITE)
;       - Trans set to 0 since we have not yet had a transfer
;       - GrowCnt set to 0 (presume that we will not be growing the file)
;       - firEAclus set to SI on entry
;       - IOType set to BX on entry
;       - Count set to no bytes to transfer (CX on entry)
;       - no of breaks in memory contiguity set to 1 -> no breaks
;       - flags -> no volume lock + strat1 (0)

        mov     si,es:[di].sf_fsd.sfdFAT_firFILEclus
        mov     ddw_sffirclus,si
        test    es:[di].sf_fsi.sfi_mode,open_direct
                                        ; SFT open for direct access?
        jz      ddw10                   ; Our assumption was correct
        inc     [ddw_NorDirFlg]         ; Actually is direct access
ddw10:  mov     si,CurrTCB
        mov     si,TASKAREA:[si].TCBThishVPB
        mov     [ddw_hVPB],si
        VPBContext ES,SI                ; Point at the vpb
        mov     ddw_bytepos.LowWord,dx
        mov     ddw_bytepos.HighWord,ax
        CALL    BreakDown               ; to set TCBSecCnt

        mov     bx,[ddw_hVPB]
        call    UseCache
        LJC     ddw17
        mov     dx,ddw_bytepos.LowWord
        mov     ax,ddw_bytepos.HighWord  ; dx:ax ->byte position in file
        SaveReg <es,di>
        mov     cx,ddw_count
        call    FATWriteInit
        RestoreReg <di,es>
        LJC     ddw85

        mov     si,[ddw_hVPB]
        call    Init_VPB_Cache
        LJC     ddw85

        TaskContext DS
        mov     di,CurrTCB
        test    [ddw_NorDirFlg],-1
        jz      ddw13
        mov     dx,TASKAREA:[di].TCBSecPos.LowWord
        mov     si,TASKAREA:[di].TCBSecPos.HighWord
        mov     cx,TASKAREA:[di].TCBSecCnt
        cmp     TASKAREA:[di].TCBBytCnt1,0
        jne     ddw11
        inc     cx
ddw11:  cmp     TASKAREA:[di].TCBBytCnt2,0
        jne     ddw12
        inc     cx
ddw12:  xor     ax,ax
        mov     bx,cx
        add     bx,dx
        adc     ax,si                   ; AX:BX = last sector + 1
        xchg    si,cx                   ; CX:DX = first sector
        SaveReg <di,bp,ds>
        mov     bp,[ddw_hVPB]
        CALLFAR BufFree
        RestoreReg <ds,bp,di>

CpuMode 386
ddw13:  mov     di,CurrTCB
        les     si,TASKAREA:[di].TCBThisSFT
        mov     edx,es:[si].sf_fsd.sfdFAT_pSFT
        movzx   eax,TASKAREA:[di].TCBThisSFT.Offst
        add     edx,eax
        xor     eax,eax
        mov     esi,eax
        mov     ebx,eax
        SaveReg <si,di>
        test    ddw_IOType,IOTYPE_FILE  ; EA Write ?
        jz      ddw14                   ; Yes, make flag TRUE
        mov     ecx,11                           ; dir entry file name length
        DosContext <ds>
        mov     si,TASKAREA:[di].TCBThisSFT.Offst
        lea     di,[si].sf_fsd.sfdFAT_name      ; es:di -> sfdFAT_name
        lea     si,EADirName                    ; ds:si -> ea file name
        repe    cmpsb                           ; copy name into SFT
        jnz     ddw15
ddw14:  inc     ebx                     ; Yes, make flag TRUE
ddw15:  RestoreReg <di,si>
        movzx   ecx,[ddw_count]
        movzx   edi,[ddw_NorDirFlg]
        SaveReg <ebp>
        movzx   ebp,[ddw_hVPB]
        FlatContext <ds,es>
        CCALL32 _BufWrite,<esi,eax,ecx,edx,edi,ebx,ebp>
        RestoreReg <ebp>
        TaskContext DS
        or      eax,eax
        stc
        LJNZ    ddwerrNoRst

        mov     cx,ddw_count
        clc
        jmp     ddw80
ddw17:
;
; Check whether the device driver supports strategy2
; and if so, also see whether this is a removable media.
; Use strategy2 (scatter/gather) ONLY in case of
;       devices that support strategy2
;       and for non-remov media
; Use Strat1 (the old, pre-2.0 method) for floppies for
; the time being.   -- Nagara 5/17/90

        les     si,es:[si].vpb_fsi.vpi_pDPB
        mov     dx,word ptr es:[si].dpb_strategy2+2
        or      dx,word ptr es:[si].dpb_strategy2
        jz      ddw20
        test    es:[si].dpb_flags,DPBNONREMOV
        jnz     strat2w                 ; hardfile? always use strat 2
        push    ds                      ; b722276
        DosContext <ds>
        test    ABIOS_present,1             ; abios machine?
        pop     ds
        assume ds:nothing
        jz      ddw20                   ; family 1, no disk support strat2
strat2w:
        or      ddw_flags,fDDW_STRAT2   ; Use Strat2
;
ddw20:  mov     dx,ACCTYPE_READ         ; (dh) need read access to segment
        mov     cx,ddw_count
        TaskContext     es
        lea     di,ddw_PgLstBuffer      ; temp buffer for pg info
        mov     bx,CurrTCB
        cmp     TASKAREA:[bx].TCBSecCnt,0 ; Whole sectors ?
        jne     ddw25                     ; Yes.
        movzx   ecx,cx
        mov     es:[di].pl_cb,ecx   ; This can never be more than 64k
        mov     es:[di].pl_paddr,-1
        mov     cx,1                ; no page breaks ; contiguous memory
        jmp     ddw26
ddw25:  call    FATRWLockMem
        LJC     ddwx                    ; jump if memory not valid
ddw26:  mov     [ddw_PageList].Offst,di
        mov     [ddw_PageList].Segmt,es
        mov     [ddw_NoOfPgBrks],cx
ddw30:  mov     dx,ddw_bytepos.LowWord
        mov     ax,ddw_bytepos.HighWord  ; dx:ax ->byte position in file
        SaveReg <es,di>
        mov     cx,ddw_count
        call    FATWriteInit
        RestoreReg      <di,es>
        LJC     ddw85
;
; if strat2 no need to break the req based on memory contig; and so no need
; to call BreakDown etc...
        test    ddw_flags,fDDW_STRAT2
        jz      ddw35                   ; strat1 ; break up the req. and loop

; strat2;just set up the registers and go for the full transfer at one stroke

        mov     cx,ddw_count
        SaveReg <es,di>
        VPBContext      ES,,NOCHECK
        jmp     short ddw38             ;

; Here the loop starts; for each set of contiguous memory we go write them
; es:di -> next entry in pagelist_s  structure

ddw35:  mov     cx,es:[di].pl_cb.LowWord  ; This can never be more than 64k
;
; cx = current transfer count ( for this range of contig. memory)
; es:di = ptr to page break info buffer  (not set in case of less than a sector
;       write since the page break count will be 1 in that case)

        SaveReg <es,di>
        mov     si,[ddw_hVPB]
        VPBContext ES,SI                ; Point at the vpb
        CALL    BreakDown
;
ddw38:  mov     si,CurrTCB
        cmp     TASKAREA:[si].TCBBytCnt1,0
        JZ      ddw40               ; We do not need to write at the "head"
        mov     [ddw_Trans],-1
        mov     cx,TASKAREA:[si].TCBBytCnt1
        SaveReg <bp,si>
        mov     si,[ddw_NorDirFlg]      ; SI = Normal/Direct I/O flag
        mov     bp,[ddw_hVPB]
        chkvselhd es,bp
        CALLFAR BufWrt
        RestoreReg <si,bp>              ; SI -> Current TCB; BP -> stack frame
        LJC     ddwerr
        test    ddw_flags,fDDW_STRAT2   ;
        jz      ddw40                   ; strat1; go for middle transfer
;
; strat2; We need to adjust the first (1 or 2) entries in the pagelist
; since we have done the head portion of transfer
;
        lds     di,[ddw_PageList]
        mov     cx,TASKAREA:[si].TCBBytCnt1
        mov     dx,ddw_NoOfPgBrks
        call    AdjustPageList
        mov     [ddw_PageList].Offst,di
        mov     [ddw_NoOfPgBrks],dx
;
ddw40:  cmp     TASKAREA:[si].TCBSecCnt,0 ; Anything to transfer in the middle?
        Je      ddw50                   ; no, go transfer remainder
        call    DoCPDiskWrite
        LJC     ddwerr
ddw50:  mov      ax,TASKAREA:[si].TCBBytCnt2
        OR      AX,AX
        JZ      ddw70                   ; No "tail" to write. Go finish operation
        mov     TASKAREA:[si].TCBBytCnt1,ax
        test    [ddw_Trans],-1          ; do we need to advance sector pos info?
        JZ      ddw60                   ; no, go skip advance of pos info
        SaveReg <bp>
        mov     si,[ddw_NorDirFlg]      ; SI = Normal/Direct I/O flag
        mov     bp,[ddw_hVPB]
        call    NextSec
        RestoreReg <bp>
        LJC     ddwerr

ddw60:  mov     si,CurrTCB
        mov     TASKAREA:[si].TCBBytSecPos,0
        mov     [ddw_Trans],-1
        mov     cx,TASKAREA:[si].TCBBytCnt1
        SaveReg <bp>
        mov     si,[ddw_NorDirFlg]      ; SI = Normal/Direct I/O flag
        mov     bp,[ddw_hVPB]
        CALLFAR BufWrt
        RestoreReg <bp>

ddw70:  RestoreReg      <di,es>
        jc      ddwerrNoRst                     ; get out; error in write
;
        test    ddw_flags,fDDW_STRAT2           ; if strat2, no need to go
        jnz     ddw80                           ; for the nxt round of read

; strat1; go read the next portion of read for the next contig. memory

        dec     ddw_NoOfPgBrks
        jz      ddw80                           ; no more ranges to write
        mov     si,CurrTCB
        mov     ax,TASKAREA:[si].TCBBytCnt1
        add     TASKAREA:[si].TCBBytSecPos,ax
        add     di,     SIZE    pagelist_s
        jmp     ddw35
;
;   The write is complete.  We need to return the growth information to the
;   caller so that it can update any data structures necessary.
;
;   Carry is clear when we get here.
;
ddw80:  mov     ax,[ddw_GrowCnt].LowWord
        mov     cx,[ddw_GrowCnt].HighWord   ; CX:AX = growth of data on disk
ddw85:  pushf
        jc      ddw88
        or      ax,cx                   ; was there any growth?
        mov     di,CurrTCB
        les     di,TASKAREA:[di].TCBThisSFT
        jz      ddw87
;
; update our sft
; update the sft, calling shsu,setsft,and filecommit should not be done
;for EAs. But whenever FATEA calls us, it preallocates the necessary clusters,
; and so we would not have grown the EA file within this procedure .
; We WOULD NOT BE HERE IF WE ARE CALLED FOR AN EA WRITE.

        mov     ax,[ddw_GrowCnt].LowWord
        add     es:[di].sf_fsi.sfi_size.LowWord,AX
        adc     es:[di].sf_fsi.sfi_size.HighWord,CX
;
; Make sure that all other SFT's see this growth also.
;
        MOV     AX,1
        CALLFAR ShSU
        call    SetSFT

; write is over and there was some growth; check whether we started from
; 0 length ; if so, we need to do a file commit on this sft

        test    ddw_sffirclus,-1
        jnz     ddw88                           ; not started from 0 length

        pusha
        mov     bx,[ddw_hVPB]
        call    UseCache                ; use cache for this volume
        jc      short ddw91             ; no, don't flush FAT

        pusha
        SaveReg <ds,es>

        mov     ax,es:[di].sf_fsi.sfi_hVPB
        CALLFAR FlushBuf                ; flush FAT buffers to cache

        FlatContext <ds,es>
        movzx   eax,[ddw_hVPB]          ; EAX = hVPB
        CCALL32  _BufFlushFAT,<eax>     ; flush FAT
        RestoreReg <es,ds>
        popa
      ASSUME DS:NOTHING,ES:NOTHING

ddw91:  call    FileCommit
        popa
ddw88:  test    ddw_flags,fDDW_VOLUME_LOCKED
        jz      ddw90
        push    cx
        mov     cx,VPB_ALLOC_SHARE
        CALLFAR Volume_Alloc_Unlock
        pop     cx
ddw90:  popf
ddwx: LeaveProc
        ret

;* Error
;  We call SetAccessDenied to set up the correct return error code in AX.
;
ddwerr: RestoreReg <di,es>

ddwerrNoRst:
        push    ax                      ; j200324 preserve error code
        call    SetAccessDenied
        pop     ax
        jmp     ddw85


ddw87:  test    ddw_IOType,IOTYPE_FILE
        jz      ddw88                   ; set sft need not be called for
        call    SetSFT                  ; ea write
        jmp     ddw88

EndProc DoDiskWrite

BREAK   <DoCPDiskWrite - write out integ. no of sectors to disk>

;** DoCPDiskWrite - write out the middle portion of a DOS_WRITE request
;
;               This writes integral sectors to disk.
;
;  *****WARNING***: This uses the stack frame of DoDiskWrite
;
;       IN:     [ddw_NorDirFlg] = 0/1 for fat fs/direct writes
;               BP points to the stack frame of dodiskwrite
;               CX = no of bytes to transfer
;               SI -> CurrTCB
;
;       OUT:    CY -> error
;                 AX = error code
;               NC -> success
;                 Several TCBVars updated (TCBSecPos,TCBLastPos etc)
;                       (TCBNextAdd,TCBBytCnt1,TCBBytSecPos,,,)
;
;       USES:   ALL
;
;       PSEUDOCODE:
;               Advance to next sector if needed
;               While (sectors to write) {
;                       Call SetupPhysIO to get the next contig. clusters
;                       Call DWRITE to write them out
;                       go to next set of sectors
;               }
;               Update TCBSecPos for normal file IO
;

Procedure       DoCPDiskWrite,NEAR,LOCAL

        mov     TASKAREA:[si].TCBBytCnt1,0
        mov     TASKAREA:[si].TCBBytSecPos,0
        test    [ddw_Trans],-1          ; do we need to advance sector pos info?
        JZ      dcdw10                  ; no, go skip advance of pos info
        SaveReg <bp>
        mov     si,[ddw_NorDirFlg]      ; SI = Normal/Direct I/O flag
        mov     bp,[ddw_hVPB]
        chkvselhd es,bp
        call    NextSec
        RestoreReg <bp>
        LJC     dcdwx
dcdw10: mov     si,CurrTCB
        mov     [ddw_Trans],1           ; A transfer has taken place
        mov     dl,TASKAREA:[si].TCBSecClusPos
        mov     bx,TASKAREA:[si].TCBClusNum
        mov     cx,TASKAREA:[si].TCBSecCnt

;
;   The following loop uses the FAT to optimize contiguous I/O.  We call
;   Setup_PhysIO to find the largest contiguous piece we can write.  We write
;   it.
;
;   DL = sector within cluster at which to start WRITE
;   BX = Physical cluster
;   CX = No. of sectors to transfer
;   [TCBNextAdd] = transfer address
;
WRTLP:  MOVFAROFFSET di,BufFree,HIGH2CODE
        mov     si,[ddw_NorDirFlg]      ; SI = Normal/Direct I/O flag
        mov     ax,[ddw_hVPB]           ; AX = hVPB
        call    Setup_PhysIO            ; Sector number in SI:DX
        jc      dcdwx
;
;   (AX)    number of sectors remaining (after this transfer)
;   (BX)    transfer address (segment offset)
;   (CX)    number of sectors in this transfer
;   (SI:DX) physical sector number (transfer start)
;   (DI)    next cluster
;
        SaveReg <DI,AX,SI,DX>

;
;  set AH = 0 for cache based write
;         = WriteBypassAdj for swapper/no-caching
;  call DWrite to do disk Output;
;
    ASSUME      DS:NOTHING
        xor     ax,ax                   ; assume normal write
        mov     di,[CurrTCB]
        test    TASKAREA:[di].TCBfSwapping,SM_TCB_SWAPPING
        jz      dcdw20                  ; not swapping, go do normal write
        mov     ax,WriteBypassAdj       ; tell DWrite to bypass cache
dcdw20: XCHG    DI,SI                   ; DI:DX = sector number
;
; Mark the fact that it is OK for the low level routine to handle any hard
; error action. This is because it is OK to free any buffer that we might
; be using as part of this write operation.
;
        mov     si,TASKAREA:[CurrTCB]
        and     TASKAREA:[si].TCBBufHE,NOT (BufHE_Percolate+BufHE_CurBufFreed)
;
; branch off according to strat2/strat1 and call DWrite2/DWrite accordingly
;
        test    [ddw_flags],fDDW_STRAT2 ; strat2 request ?
        jnz     dcdw80                  ; yes, go call DWrite2
;
; strat1 request ; call DWRite
;
        SaveReg <bp>                    ; Save stack frame pointer
        mov     bp,[ddw_hVPB]           ; Set BP for call to DWrite
        CALLFAR DWrite
        RestoreReg <bp>
;
; joining point for strat1 and strat2 calls
; at this point write is over

dcdw30: RestoreReg <DX,SI,CX,BX>
;
; (SI:DX) = phys. sect. num.
; CX = Number of sectors remaining
; BX = Next cluster
;

        TaskContext DS
        jc      dcdwx
        JCXZ    dcdw40                  ; if direct I/O, CX will ALWAYS == 0
        MOV     DL,0
        mov     si,CurrTCB
        inc     TASKAREA:[si].TCBLastPos         ; We'll be using next cluster
        JMP     WRTLP                   ; See if any more data to write out

;
; For regular file I/O, we optimize by updating TCBSecPos so that the buffer
; code does not do a "redundant READ". This speeds up the throughput.
;
; NOTE: This is **NOT** done for Direct I/O since the field has already been
;       updated in Optimize (in rom.asm).
;
dcdw40:  mov     si,CurrTCB
        test    [ddw_NorDirFlg],-1      ; Direct write?
        jnz     dcdwx           ; yes Direct, go skip TCBSecPos update
        mov     ax,TASKAREA:[si].TCBSecCnt
                                ; advance the position within the file
        add     word ptr TASKAREA:[si].TCBSecPos,ax
        adc     word ptr TASKAREA:[si].TCBSecPos+2,0
                                ; over the large block just written

;   The write is complete.
;   Carry is properly set/reset when we come here
;
dcdwx:  ret

; Strat2 write ; setup registers and call DWrite2
;
; ds,si is the only register that is free now

dcdw80: SaveReg <ax,ds>

        SaveReg <dx>
        mov     dx,di                   ; di:dx move into edx
        shl     edx,16
        RestoreReg <dx>
        mov     di,bx                   ; ds:di points to transfer area
                                        ; b724480
        lds     si,[ddw_PageList]       ; get the pointer
        SaveReg <bp>
        mov     bp,[ddw_hVPB]
        call    AdjPgLstBefIO           ; adjust the count for the last entry
        RestoreReg      <bp>            ; si = no of entries in the page list
        mov     [ddw_Remnant],ax        ; for this transfer
        mov     [ddw_EntryCount],si     ; ax = Remnant for the last entry
        RestoreReg    <ds,ax>           ;
        SaveReg <bp>                    ; Save stack frame pointer
        CpuMode 386
        mov     ebx,[ddw_PageList]      ; si = no of entries for this transfer
        mov     bp,[ddw_hVPB]           ; Set BP for call to DWrite
        CpuMode 286
        CALLFAR DWrite2
        RestoreReg <bp>
        pushf                           ; Save flags returned by DRead
        lds     si,     [ddw_PageList]  ;
        mov     bx,[ddw_EntryCount]     ;
        dec     bx
.errnz  ( SIZE  pagelist_s - 8 )
        rol     bx,3                    ;
        add     si,bx                   ; ds:si -> last entry of this transfer
        cmp     [ddw_Remnant],0         ;did the transfer ended on a entry exact
        jz      dcdw90                  ; yes, just increment the pointer
CPUMode 386
        movzx   eax,ds:[si].pl_cb.LowWord ; update the address part
        add     ds:[si].pl_paddr,eax    ;
CPUMode reset
        mov     ax,[ddw_Remnant]
        mov     ds:[si].pl_cb.LowWord,ax ; update the count
        jmp     short dcdw95
dcdw90: add     si, SIZE        pagelist_s
        add     bx, SIZE        pagelist_s
dcdw95: mov     [ddw_PageList].Offst,si ; update the pagelist pointer
        ror     bx,3                    ; no of entries to move forward
        sub     [ddw_NoOfPgBrks],bx     ;

        popf
        jmp     dcdw30                  ; go back and finish the call

EndProc DoCPDiskWrite

BREAK   <FATWriteInit   - inits some TCBVars for a DOS_Write>

;** FATWriteInit - do some init for DOS_WRITE
;       IN:     dx:ax -> byte pos in file
;               bp      -> frame pointer of DoDiskWrite
;       OUT:    CY      -> init failed
;                 AX = error code
;                       DISK_FULL etc
;               NC      -> file grown if needed, for normal file IO
;                       TCBClusNum,TCBSecClusPos,TCBBytSecPos,TCBLastPos set
;
;       USES:   ax,bx,cx,dx,si,di,es,flags
;
Procedure FATWriteInit,NEAR,LOCAL

; If this is a WRITE for extended attributes, then we do not need to do any
; cluster allocation. We just set up BX and DX so that the TCB variables
; will be set up by the caller (DoDiskWrite) correctly.
;
        test    [ddw_IOType],IOTYPE_FILE
        jnz     fwi30                   ; This is file or direct disk I/O
;
; It is I/O for extended attributes.
;
; Set as follows:
;   First cluster at which to start write (TCBClusNum) = ddw_firEAclus
;   (TCBLastPos) = 0 (since we are starting at the beginning of the chain)
;
        mov     si,CurrTCB
        mov     bx,[ddw_firEAclus]
        mov     TASKAREA:[si].TCBClusNum,bx
        mov     TASKAREA:[si].TCBLastPos,0
        jmp     short fwi40             ; Begin the requested write

;
;   If we are doing a direct write, we are entirely set up.
;
;   [ddw_NorDirFlg] == 0/1 for fat fs/direct write
;   TASKAREA:SI -> TCB
;       dx:ax           -> byte position in file
;
fwi30:  test    [ddw_NorDirFlg],-1      ; are we performing direct I/O?
        jnz     fwi40                   ; yes, go skip cluster allocation

        mov     si,[ddw_hVPB]
        VPBContext      es,si
        call    FileGrowth
fwi40:  ret

EndProc FATWriteInit

;***    FileGrowth - Do any growth of the file during WRITE
;
;       FileGrowth checks to see if the file that we are writing to will
;       grow as a result of the transfer. It tries to allocate the necessary
;       space on the disk.
;
;       ENTRY   DX:AX = Current file position
;               CX    = Size of transfer
;               ES:SI -> VBP
;               BP points to the stack frame of DoDiskWrite
;
;       EXIT    'C' if error
;                   AX = error code
;               'NC' if no error
;                   The following variables are set up
;                       TCBValSec
;                       ddw_GrowCnt
;                       TCBClusNum
;                       TCBlastpos
;                   ddw_flags set to the lock type if we take a volume
;                       alloc lock (i.e. if we grow the file).
;
; **** WARNING: This procedure uses the stack frame of DoDiskWrite *******
;
;       USES    AX, BX, CX, DX, DI, SI, Flags
;
Procedure   FileGrowth,NEAR,LOCAL
    ASSUME DS:TASKAREA,ES:NOTHING,SS:TASKAREA

        SegAssert SS, <DS>, "FileGrowth"
        chkvselhd es,si         ; Ensure ES:SI -> VPB
        ADD     AX,CX
        ADC     DX,0            ; AX:DX=byte after last byte accessed
;
; Do a 32-bit divide to get a 32-bit quotient as the number of full sectors
;

        mov     bx,es:[si].vpb_fsi.vpi_cbSector
        xchg    ax,dx           ; AX:DX = byte after last byte accessed
        push    dx
        xor     dx,dx
        div     bx
        xchg    ax,cx           ; CX = high word of sector number
        pop     ax              ; Divide will not overflow
        div     bx              ; CX:AX = sector #, DX = size of tail

        SaveReg <CX,AX>         ; Save for later use
        or      dx,dx
        jnz     fgr20
        sub     ax,1            ; DEC does not affect 'C'
        sbb     cx,0


fgr20:  xchg    bx,cx           ; BX:AX = sector #
        xor     cx,cx
        mov     cl,ES:[si].vpb_fsd.vpdFAT_cluster_shift
        jcxz    fgr40
fgr30:  shr     bx,1
        rcr     ax,1
        loop    fgr30

; AX = last cluster to be accessed
; DX = size of tail
; BX = 0 (should be, otherwise assume error)
;
fgr40:  or      bx,bx
        jz      fgr50

;
; The sector number is too large. We return a "disk full" error.
        add     sp,4            ; Clean sector# off the stack and return error
        mov     ax,ERROR_DISK_FULL
        STC
        jmp     fgrx            ; Return with 'C' set to indicate error


fgr50:  PUSH    AX              ; AX = cluster #
        PUSH    DX              ; Save the size of the "tail"
        PUSH    ES

;       Place into ValSec the number of sectors allocated to the file.  This
;       is used in BufWrt to decide whether we need to preread a sector before
;       writing to it.

        mov     di,CurrTCB
        les     di,TASKAREA:[di].TCBThisSFT
        mov     dx,es:[di].sf_fsi.sfi_size.LowWord
        mov     ax,es:[di].sf_fsi.sfi_size.HighWord
        pop     es
; Do another 32-bit divide to get a 32-bit quotient. AX:DX = file size
        push    dx
        xor     dx,dx
        div     es:[si].vpb_fsi.vpi_cbSector
        xchg    cx,ax
        pop     ax
        div     es:[si].vpb_fsi.vpi_cbSector    ; CX:AX = last full sector, DX = last tail
        SaveReg <CX,AX>                 ; Save current last full sector
        or      dx,dx
        jz      fgr60                   ; No need to round up
        ADD     AX,1                    ; INC does not affect 'C'
        ADC     CX,0                    ; Round up if any remainder
fgr60:  mov     si,CurrTCB
        mov     word ptr TASKAREA:[si].TCBValSec,ax  ; Number of sectors that have
        mov     word ptr TASKAREA:[si].TCBValSec+2,cx    ; been written
        RestoreReg <si,di>              ; DI:SI = last full current sector
        pop     ax                      ; AX = tail required
        pop     cx                      ; CX = cluster #
;
; TOS = last full sector
; DI:SI = current last full sector of file
; CX = last cluster number required
; AX = new "tail" size
; DX = old "tail" size
;
        RestoreReg <BX>                 ; Low word of new last sector
        sub     bx,si
        RestoreReg <SI>                 ; SI = high word of new last sector
        push    cx                      ; Save cluster number for later use
        sbb     si,di
        jb      fgr100                  ; We will not be growing the file

        or      bx,bx                   ; does number of sectors need to grow?
        jnz     fgr65                   ;   YES! grow the number of sectors

        or      si,si                   ; does number of sectors need to grow?
        jz      fgr80                   ;   NO! see if tail needs to grow

;* We are growing the file
;
; (SI:BX) = amount to grow (in sectors)
; AX = new "tail" size
; DX = old "tail" size
; TOS = last cluster
; After the following routine,
; (DX:AX) = (SI:BX) * es:[bp].vpb_fsi.vpi_cbSector + new tail size - old tail size
;
fgr65:  xchg    cx,dx                   ; CX = old "tail" size
        xchg    si,ax                   ; (AX) = hiword of sectors to grow
        SaveReg <bp>
        mov     bp,[ddw_hVPB]
        mul     es:[bp].vpb_fsi.vpi_cbSector ; (SI) = new tail size
        or      dx,dx                   ; is dx 0?
        jnz     fgrier                  ; NO! growth > 4 gigabytes, error

        xchg    si,ax                   ; (si) = intermediate result
                                        ; (ax) = new tail size
        xchg    bx,ax                   ; BX = new tail size, AX = growth sec cnt
        MUL     ES:[bp].vpb_fsi.vpi_cbSector    ; Bytes of full sector growth = DX:AX
        RestoreReg <bp>
        sub     ax,cx                   ; Subtract old "tail" size
        sbb     dx,0
        add     ax,bx                   ; Add in new "tail" size
        adc     dx,0                    ; dx:ax = bytes for growth
        jc      fgrier                  ; growth > 4 gigabytes, error

        add     dx,si                   ; (dx:ax) = bytes for growth
        js      fgrier                  ; error if growth > 2 gigabytes

        jnc     short fgr90             ; We are set to grow the file by DX:AX bytes

; if nc, (dx:ax) = bytes to grow file

fgrier: InternalError <FileGrowth: Growth greater than 2 Gigabytes>

;
; We have found the last cluster in the file (i.e. have reached EOF). We
; now need to allocate more space on the disk for the write.
;
fgr70:  MOV     CX,AX
        SaveReg <bp>
        mov     bp,[ddw_hVPB]
        CALLFAR SKPCLP
        JCXZ    fgr75                   ; Carry clear. Exit.
        RestoreReg      <bp>
        push    cx
        mov     cx,VPB_ALLOC_SHARE
        or      ddw_flags,cx
        CALLFAR Volume_Alloc_Lock
        pop     cx
        SaveReg <bp>
        mov     bp,[ddw_hVPB]
        call    Allocate
fgr75:  RestoreReg <bp>                 ; Restore pointer to stack frame
        jmp     fgrx                    ; Return with 'C' set if error
                                        ; ('NC' if no error)
;
; We are not going to grow the number of sectors for the file. See if we need
; to grow the "tail" of the file.
;
; AX = new "tail" size
; DX = old "tail" size
;
fgr80:  SUB     AX,DX
        JBE     fgr100                  ; No. The tail does need to be grown.
        XOR     DX,DX
;
; We are going to grow the file by (DX:AX) bytes
;
fgr90:  mov     si,CurrTCB
        mov     [ddw_GrowCnt].LowWord,ax
        mov     [ddw_GrowCnt].HighWord,dx

fgr100: pop     ax                      ; Cluster number
        mov     si,CurrTCB
        mov     cx,TASKAREA:[si].TCBClusNum      ; First cluster accessed
        SaveReg <bp>
        mov     bp,[ddw_hVPB]
        CALLFAR FNDCLUS
        RestoreReg <bp>                 ; BP points to stack framepop
        jc      fgrerr
        mov     si,CurrTCB
        mov     TASKAREA:[si].TCBClusNum,bx
        mov     TASKAREA:[si].TCBLastPos,dx
        SUB     AX,DX           ; Last cluster minus current cluster
        JZ      fgrx            ; If we have last clus, we must have first
        JCXZ    fgr70           ; See if no more data
        PUSH    CX              ; No. of clusters short of first
        MOV     CX,AX
        SaveReg <bp>
        push    cx
        mov     cx,VPB_ALLOC_SHARE
        or      ddw_flags,cx
        CALLFAR Volume_Alloc_Lock
        pop     cx
        mov     bp,[ddw_hVPB]
        call    Allocate
        RestoreReg <bp>                 ; BP points to stack framepop
        POP     cx              ; CX = No. of clusters short of first
        jc      fgrx            ; Return with 'C' set to indicate error
        MOV     ax,cx
        mov     si,CurrTCB
        mov     dx,TASKAREA:[si].TCBLastPos
        INC     DX
        DEC     CX
        JZ      fgr110              ; We are at the right position in the file
        SaveReg <bp>
        mov     bp,[ddw_hVPB]
        CALLFAR SKPCLP
        RestoreReg <bp>             ; BP points to stack framepop
        jc      fgrerr
fgr110: mov     si,CurrTCB
        mov     TASKAREA:[si].TCBClusNum,bx
        mov     TASKAREA:[si].TCBLastPos,dx
fgrx:   ret

;* Error
;  We call SetAccessDenied to set up the correct return error code in AX and
;  to set 'C'.
;
fgrerr: call    SetAccessDenied
        jmp     short fgrx

EndProc FileGrowth


;***    WrtErr - perform write-error cleanup
;
;       WrtErr is used to clean up when an error is detected (either due
;       to a disk being too full or because of a disk error).
;
;       ENTRY   (ds)    TASKAREA
;               (ax)    error code (0 is no error)
;
;       EXIT    (CX)    0 (for number of bytes transferred)
;               (es:di) points to sft of transfer (not needed)
;               carry is set iff
;                       ax != 0 && ax != ERROR_DISK_FULL
;
;       USES    cx, di, es, flags
;
Procedure   WrtErr,near,local
        ASSUME  ES:NOTHING, ds:TASKAREA

        XOR     CX,CX                   ; No bytes transferred
        mov     di,CurrTCB
        les     di,TASKAREA:[di].TCBThisSFT ; (es:di) point to SFT of interest
        or      ax, ax                  ; was an error code passed in?
        jz      we2                     ; no, go return no error (carry clear)

;### b726425  Put this error map back in because it breaks compatiblity and
;             HPFS and FAT consistancey
;             This was previously removed by PTR B724081
        cmp     ax, ERROR_DISK_FULL     ; did we have a full disk condition?
        je      we2                     ; yes, go return no error (expected)

;
; We DID get an error that must be reported
;
        stc
we2:    ret
EndProc WrtErr


BREAK   <Setup_PhysIO - common setup for the middle part of a transfer>

;***    Setup_PhysIO - common setup for the middle part of a transfer
;
;       Setup_PhysIO is used by DiskRead and DiskWrite to do common
;       operations for the middle part of the transfer, namely that
;       part which is transferred directly to/from the user's memory.
;
;       ENTRY   Variables and registers set appropriately for calling
;               Optimize:
;                       BX = Physical cluster
;                       CX = No. of records
;                       DL = sector within cluster
;                       ES:AX point to VPB
;                       [NextAdd] = transfer address
;                       SI = 0 for FAT FS I/O,
;                            1 for DASD I/O.
;                       DI = address of routine to do buffer cache prep.
;                               For diskread this is BufIncRef
;                               For diskwrite this is BufFree
;
;       EXIT    Variables and registers set appropriately for calling
;               DRead or DWrite.
;               If Optimize returns an error,
;                       Setup_PhysIO will return with carry set,
;                       caller is reponsible for reporting error.
;               Otherwise,
;                       AX = No. of records remaining
;                       DS:BX = Transfer address
;                       CX = No. or records to be transferred
;                       SI:DX = Physical sector address
;                       DI = Next cluster
;                       [ClusNum] = Last cluster accessed
;                       [NextAdd] updated
;
;       USES    AX, BX, CX, DX, DI, SI
;
;       WARNING This routine may reserve multiple disk buffers.  In
;               order to avoid deadlock and assure I/O consistency,
;               the SFT in question must be locked before calling (LockMFT)
;               and no buffer may be assigned at the time of the call.
;
Procedure   Setup_PhysIO,<hybrid,far16>

    ASSUME  DS:NOTHING,ES:NOTHING

LocalVar    spio_BufCachRoutine,DWORD
LocalVar    spio_LocalhVPB,WORD

        EnterProc <HIGH2CODE,di,ax>     ; Set up local variables from input parms
        chkvselhd es,ax
        call    Optimize
        jc      spio1                   ; error, go return to caller

        cmp     spio_BufCachRoutine.Offst,-1
        je      spio1
                                        ; cx = number of sectors to read/write
CpuMode 386                             ; si:dx = first sector

        PUSHAD
        shl     esi,16
        mov     si,dx                   ; (esi) = first sector
        mov     eax,esi                 ; (eax) = first sector
        movzx   ecx,cx                  ; (ecx) = number of sectors
        add     eax,ecx                 ; (eax) = last sector + 1

        lea     di,spio_BufCachRoutine  ; SS:DI -> address of FAR routine to call
        mov     bp,spio_LocalhVPB       ; BP gets restored by POPA later on
        call    dword ptr ss:[di]       ; BufFree or BufIncRef - FAR call

        mov     di,CurrTCB
        mov     TASKAREA:[di].TCBAllowed,ALLOWED_RETRY + ALLOWED_FAIL + ALLOWED_IGNORE
        mov     ds,TASKAREA:[di].TCBDMAAdd.Segmt
        VPBContext ES,BP                ; Reassert proper VPB selector mapping
        POPAD                           ; BP -> LocalVar frame

CpuMode reset

spio1:  LeaveProc
        ret
EndProc     Setup_PhysIO


;***    ABSDRDWRT - Interrupt 25/26 handler.  Performs absolute disk read/write
;
;       ENTRY:  AL = 0-based drive number
;               AH = 0 - Do read 1 - Do write
;               CX = number of sectors (but != 0ffffh)
;                       DS:BX = pointer to destination buffer
;                       DX    = starting logical sector number (0-based)
;                  = -1 (0ffffh)
;                       DS:BX = pointer to packet
;                       DX    = not used
;
;               packet:
;                 DD ssn   ; starting sector number (32-bit)
;                 DW nsect ; number of sectors
;                 DD buf   ; buffer pointer (paragraph + offset)
;
;       EXIT:   Original flags still on stack
;               Carry set
;                   AH error from BIOS
;                   AL same as low byte of DI from INT 24
;
        ASSUME  DS:NOTHING,ES:NOTHING,SS:NOTHING

Procedure ABSDRDWRT,far16

LocalVar  hLockAX,WORD
LocalVar  hLockBX,WORD
LocalVar  fRdWrt,BYTE
LocalVar wsf_dummycds,<SIZE curdir>

        EnterProc
        push    bp
        lea     di,wsf_dummycds
        ASSUME  SS:TASKAREA
        push    bx                              ;### PTR B720732
        mov     bx,TASKAREA:[CurrTCB]                   ;### PTR B720732
        mov     TASKAREA:[bx].TCBpTmpCDS.Offst,di       ; Initialize TCBpTmpCDS ;### PTR B720732
;       mov     TASKAREA:[bx].TCBpTmpCDS.Segmt,ss       ; Initialize TCBpTmpCDS ;### PTR B720732
;### PTR B720732        mov     TASKAREA:[ptda_pCDS],di ; Initialize ptda_pCDS
        pop     bx                              ;### PTR B720732
        mov     [fRdWrt],ah
        or      ah,ah
        jne     short arw10
        mov     ah,DO_DIRECT_READ       ; (AH) = indicate we are reading
        jmp     short arw20

arw10:  mov     ah,DO_DIRECT_WRITE      ; (AH) = indicate we are writing
arw20:  call    AbsSetup
        mov     si,bp                   ; (ES:SI) = pointer to VPB
        pop     bp
        jc      short arw60             ; go map error

        xor     di,di                   ; (DI) = high word of sector number
        cmp     cx,-1                   ; extended int25?
        je      short arw25             ; yes, skip special handling
        push    dx                      ; save dx                       ; 1.7 begin
        mov     ax,es:[si].vpb_fsd.vpdFAT_max_cluster   ; get max cluster #
        movzx   dx,es:[si].vpb_fsd.vpdFAT_cluster_mask  ; sectors/cluster - 1
        inc     dx                      ; dx = sectors/cluster
        mul     dx                      ; dx:ax = max sector #
        or      dx,dx                   ; greater than 32mb?
        pop     dx                      ; restore dx
        jz      short arw30             ; no, continue
        mov     ax,207h                 ; yes, return error 0x207
        stc                             ; indicate error
        jmp     short arw60             ; and return                    ; 1.7 end
arw25:  call    XAbsSetup               ; copy packet into registers

arw30:  pusha
        SaveReg <ds,es>
        mov     ax,ds
        mov     bl,MW_BVALID
        xor     bh,bh
        CALLFAR SegLock
        mov     [hLockAX],ax
        mov     [hLockBX],bx
        RestoreReg <es,ds>
        popa
        jc      short arw60

        cmp     [fRdWrt],0
        push    bp
        mov     bp,si
        jne     short arw40
        CALLFAR DskRead
        jmp     short arw50

arw40:  CALLFAR DskWrite
arw50:  pop     bp

        push    ax
        pushf
        mov     ax,[hLockAX]
        mov     bx,[hLockBX]
        CALLFAR SegUnlock
        popf
        pop     ax

arw60:  LeaveProc
        RETP
EndProc   ABSDRDWRT

BREAK   <AbsSetup - setup for abs disk functions>
;***    AbsSetup - setup for abs disk functions
;
;       ENTRY   (AH) = DO_DIRECT_READ, if doing a read
;                    = DO_DIRECT_WRITE, if doing a write
;               Other registers as from INT 25/26 call
;
;       EXIT    Carry Clear, everthing is fine.
;               Carry Set, failed.
;                   (AL) = error return code
;
;       USES    AX, BP, ES, Flags
;
        ASSUME  DS:NOTHING,ES:NOTHING,SS:TASKAREA

Procedure AbsSetup,NEAR,LOCAL
        sti
        call    DirectIOAllowed         ; see if direct disk I/O is allowed
        jc      absx                    ; (AL) = device driver error code
        SaveReg <bx,ds>
        TaskContext <ds>
        mov     bx,CurrTCB              ; (ds:bx) = current PDB
        mov     TASKAREA:[bx].TCBModeFlags,fOPENDASD ; force VPB creation
        call    GetVPB                  ; (ES:BP) = VPB
    assume  ds:nothing,es:nothing
        mov     TASKAREA:[bx].TCBModeFlags,0 ; reset mode flags
        RestoreReg <ds,bx>
    ASSUME  DS:NOTHING
        jc      absx                    ; (AL) = device driver error code
        chkvselhd es,bp

;       Do not trust user at all (REALLY!)

        mov     es:[bp].vpb_fsd.vpdFAT_free_cnt,-1
        mov     es:[bp].vpb_fsd.vpdFAT_next_free,0

        cmp     word ptr es:[bp].vpb_FSC.Offst,FSC_LOCAL ; is it a FAT
        je      short abs10
        stc
        mov     ax,ERROR_NOT_DOS_DISK
        jmp     short absx

abs10:  clc
absx:   ret
EndProc   AbsSetup



;**     XAbsSetup - registers setup for for extended int25/26 call
;
;       ENTRY:  DS:BX -> packet (see ABSDSKRD header for packet format)
;
;       EXIT:   CX    = nsect (number of sectors)
;               DI:DX = ssn (starting sector number)
;               DS:BX = selector + offset to buffer
;
;       USES:   AX, BX, CX, DX, DI, DS, ES, Flags
;
;       CALLED by: ABSDRDWRT

; Structure of packet for extended int25/26 call

int25parm       struc
ssn     dd      ?               ; starting sector number
nsect   dw      ?               ; number of sectors to transfer
bufp    dd      ?               ; pointer to buffer (paragraph + offset)
int25parm       ends

Procedure XAbsSetup,NEAR,LOCAL
        mov     ax,es:[si].vpb_fsi.vpi_cbSector ; get sector size          ;6.17
        mul     [bx].nsect              ; calculate no. of bytes           ;6.17
        ;DX:AX==(starting sect #)*(sector size in bytes)=length of block
        add     ax,[bx].bufp.loword     ; Add in starting offset of buffer ;6.17
        adc     dx,0                                                    ;56273;
        ;DX:AX==length of block + offset of buffer=selector length
        mov     cx,dx                                                   ;56273;
        ror     ecx,16                                                  ;56273;
        mov     cx,ax                   ; (CX) = no. of bytes              ;6.17
        ;ECX=selector length
        mov     ax,[bx].bufp.hiword     ; paragraph address of buffer
        SaveReg <ds,es>
        FlatContext <ds,es>
        ;entry: AX=segment to map
        ;       ECX=segment length
        CALLF32 demMapSegLenCX          ; (AX) = selector                  ;6.17
        assume  ds:nothing,es:nothing
        ;exit:  AX=selector
        RestoreReg <es,ds>
        mov     cx,[bx].nsect           ; (CX) = number of sectors
        mov     dx,[bx].ssn.loword      ; (DX) = low word of sector#
        mov     di,[bx].ssn.hiword      ; (DI) = hi word of sector#
        mov     bx,[bx].bufp.loword     ; (BX) = offset address of buffer
        mov     ds,ax                   ; (DS) = selector of buffer
        ret
EndProc   XAbsSetup

BREAK   <GetVPB - Find Volume Parameter Block>
;***    GetVPB - Find Volume Parameter Block
;
;       ENTRY   AL = Logical unit number (A = 0)
;
;       EXIT    if carry clear
;                       ES:BP points to VPB
;                       ThishVPB = BP
;               if carry set
;                       (AL) = device driver error code
;
;       USES    AX, BP, DS, ES, Flags

Procedure GetVPB,NEAR,LOCAL
      ASSUME DS:TASKAREA, ES:NOTHING
        SegAssert SS,<DS>
        push    ax
        add     al,1                    ; No increment; need carry flag
        jc      gvb1
        CALLFAR GetThisDrv
gvb1:   pop     ax
        jc      gvb2

        ASSUME  SS:TASKAREA
        mov     bp,ss:CurrTCB             ; (bp) = current TCB     ;### PTR B720732
        les     bp,ss:[bp].TCBThisCDS     ; (es:bp) = CDS pointer  ;### PTR B720732
;### PTR B720732        les     bp,ss:[ThisCDS]
        test    es:[bp].cd_flags,CD_ISNET
        jnz     gvb2                    ; jump if network drive

        test    es:[bp].cd_flags,CD_JOIN   ; spliced drive or c.d.?
        jz      gvb3                       ; no
        SaveReg <ax,ds,si>
        add     al,"A"-1
        CALLFAR InitCDS                 ; get physical CDS
        RestoreReg <si,ds,ax>
        jnc     gvb3

gvb2:   mov     ExtErr,ERROR_BAD_UNIT
        mov     al,ERROR_I24_BAD_UNIT
        stc
        jmp     short gvb4

gvb3:   SaveReg <bx,dx,si,di>
        lds     si,es:[bp].cd_devptr
    ASSUME  DS:NOTHING
        CALLFAR WhatVolumeCheck         ; Determine volume parameters
        mov     bp,[si].dpb_hVPB
        RestoreReg <di,si,dx,bx>
        jc      gvb4                    ; (AL) = device driver error code

        VPBContext es,bp
        push    si
        mov     si,CurrTCB
        mov     TASKAREA:[si].TCBThishVPB,bp
        pop     si
gvb4:   ret

EndProc   GetVPB

ifdef 0

; sudeepb 22-Oct-1989
; This procedure may be used by get_setup in mvdm\demmisc2.asm.
; for doing the exact size locking and finding media byte.

;***    Get_SecSize - Get the sector size
;
;       ENTRY   AL = Logical unit number (A = 0)
;
;       EXIT    if carry clear
;                       AX = seector size
;               if carry set
;                       (AL) = error code
;
;       USES    AX, BP, DS, ES, Flags

    ASSUME  DS:NOTHING,ES:NOTHING,SS:TASKAREA
Procedure Get_SecSize,far16

        TaskContext <DS>

        CALLFAR GetThisDrv              ; Get CDS structure
        jnc     short gc2               ; Drive is valid

gc1:    stc
        RETP

gcf:    mov     ax,ERROR_I24_BAD_UNIT
        jmp short gc1

gc2:    mov     di,TASKAREA:CurrTCB               ; (di) = current TCB     ;### PTR B720732
        les     di,TASKAREA:[di].TCBThisCDS       ; (es:di) = CDS pointer  ;### PTR B720732
;### PTR B720732 gc2:   les     di,[ThisCDS]            ; check for net CDS
        test    es:[DI.cd_flags],CD_ISNET
        jnz     gcf                     ; No DPB to point at on NET stuff
        lds     si,es:[di].cd_devptr    ; point at DPB
        assume  ds:nothing
        CALLFAR WhatVolume              ; figure out what volume is there
        jc      gc1                     ; User FAILed to I 24, only error we
                                        ;   have.
        mov     si,TASKAREA:CurrTCB               ; (si) = current TCB     ;### PTR B720732
        lds     si,TASKAREA:[si].TCBThisCDS       ; (ds:si) = CDS pointer  ;### PTR B720732
;### PTR B720732        lds     si,ThisCDS              ; (DS:SI) = specified CDS
        les     di,[si].cd_devptr       ; (ES:DI) = DPB

        mov     di,es:[di].dpb_hVPB     ; (DI) = VPB handle
        VPBContext ES,DI                ; (ES:DI) -> VPB

        mov     ax,es:[di].vpb_fsi.vpi_cbSector
        clc
        RETP
EndProc   Get_SecSize

endif


HIGH4CODE    ENDS
        END
