/*static char *SCCSID = "@(#)diskw.c    6.14 92/02/06";*/

#define SCCSID "@(#)diskw.c     6.14 92/02/06"
/********************************************************/
/*                                                      */
/*       IBM/Microsoft Confidential                     */
/*                                                      */
/*       Copyright (c) IBM Corporation 1987, 1989       */
/*       Copyright (c) Microsoft Corporation 1987, 1989 */
/*                                                      */
/*       All Rights Reserved                            */
/********************************************************/
/* 12-15-93 RAC 76699 Make perfview optional with compile flag PERFVIEW */

#define FNO_NUM FNO_DISKW_C
#define INCL_TYPES                      // for basedef.h typedefs
#include <osconst.h>
#include <ostypes.h>
#include <basemac.h>
#include <basedef.h>
#include <error.h>
#include <fnofs.h>
#include <osmac.h>
#include <mi.h>
#include <vmexport.h>
#include <selxport.h>
#include <pgexport.h>
#include <tkexport.h>
#include <smexport.h>
#include <sft.h>
#include <cache.h>
#include <strat2.h>
#include <vmowner.h>
#include <string.h>
#include <fslocal.h>
#include <filesys.h>

#include <dossymc.h>                                                   //b722131
#include <sigdef.h>                     /* for tkptda.h */             //b722131
#include <except.h>                     /* for tkptda.h */             //b722131
#include <curdir.h>                     /* for tkptda.h */             //b722131
#include <dirent.h>                     /* for tkptda.h */             //b722131
#include <find.h>                       /* for tkptda.h */             //b722131
#include <screen.h>                     /* for tkptda.h */             //b722131
#include <sem.h>                        /* for tkptda.h */             //b722131
#include <tkptda.h>                                                    //b722131
#include <string.h>                                                    //b722131

#ifdef PERFVIEW
#include <pvwxport.h>                                                  //b722131
#include <perfvw.h>                                                    //b722131
#endif


#ifdef FSSTRICT
// #define BUFWRITEDEBUG

#ifdef BUFWRITEDEBUG
PBUFHDR lazyreadoff[200];
ulong_t lazyreadofftime[200];
ushort_t lazyreadoff_index = 0;
#endif
#endif
#ifdef FSSTRICT
ulong_t PextattrRBA[200];
ulong_t FextattrRBA[200];
ushort_t FextattrRBAcnt[200];
ushort_t PextattrIndex = 0;
ushort_t FextattrIndex = 0;
ushort_t FFirstWord[200];
ushort_t PFirstWord[200];
ushort_t FFirstWordIndex = 0;
ushort_t PFirstWordIndex = 0;
#endif

/***LP  diskw_read - read buffer for diskw
 *
 *      diskw_read reads data from the disk into a cache block.
 *
 *      ENTRY   hVPB          - Handle to Volume
 *              rba           - rba to read
 *              pBuf          - pointer to buffer header to read into
 *
 *      EXIT:   rc            - NULL or Error Code.
 *
 *
 *
 */
int diskw_read(ulong_t hVPB,ulong_t rba,PBUFHDR pBuf)

{
    int rc,count;
    ulong_t Rrba;

    count = 1;
    SetRBA_Count(SSToDS(&Rrba),         // Get starting rba and count
                 rba,
                 SSToDS(&count),
                 hVPB);
    BufLock(pBuf);                      // Lock Buffer
    rc = DRead(Rrba,                    // Read data into cache buffer
               count,
               pBuf->BufPhysAddr + (BUFOFFSET(Rrba)),
               hVPB);
    BufUnLock(pBuf);                    // Unlock Buffer
    return (rc);
}

/***LP  BufWritePartial - perform user diskwrite for one sector
 *
 *      BufWritePartial writes data from a user's buffer into a cache block
 *      and optionally writes the cache block out to disk. It may or maynot
 *      be necessary to read the corresponding block from disk.
 *
 *      ENTRY   cbCount       - count of bytes in partial sector
 *              pSFT          - pointer to SFT
 *              SecBytPos     - Byte position with sector
 *              pFound        - pointer to found flag
 *              fDirect       - Direct access flag
 *              fExtAttr      - Extended Attributes flag
 *              fWriteToDisk  - Write to disk flag
 *
 *      EXIT:   rc            - NULL or Error Code.
 *
 *
 *
 */
int BufWritePartial(ulong_t cbCount,
                    sf_entry *pSFT,
                    PBUFHDR  *ppBuf,
                    ulong_t SecBytPos,
                    int *pFound,
                    int fDirect,
                    int fExtAttr,
                    int fWriteToDisk)

{
    register tcb_t *ptcb = pTCBCur;
    ulong_t rba,Rrba,VIDrba;
    ulong_t SecClusPos;
    int rc = 0,found,fUpdatingVID = FALSE,i,cnt,fMustRead,fWrite;
    ulong_t ByteIndex,fullbytes;
    PBUFHDR pBuf;
    BYTE BitMask;
    sfdFATFS *pSFTFAT;
    ulong_t hVPB;
    VMAC    ac;

    pSFTFAT = (sfdFATFS *)&(pSFT->sf_fsd);
    hVPB = (ulong_t)pSFT->sf_fsi.sfi_hVPB;

    // For Direct access the rba is the SecPos in the TCB. For non-direct
    // access we go call FigRec to calculate it for us.

    if (fDirect)
       rba = ptcb->TCBSecPos;
    else {
       SecClusPos = (ulong_t)ptcb->TCBSecClusPos;
       rba = FigRec (ptcb->TCBClusNum,SecClusPos,hVPB);
       if (fExtAttr) {
          WriteDosBuffer(cbCount,hVPB);
          ptcb->TCBNextAdd -= (ushort_t)cbCount;
          --(ulong_t)ptcb->TCBSecPos;
       }
    }

    MMPHPreSDW((ushort_t)1);            /* performance hook (320A) */
    VIDrba = GetVIDSector(hVPB);        // Get the volume id sector
    if (VIDrba == rba)                  // if the write is for the VID sector
       fUpdatingVID = TRUE;             // set the fUpdatingVID flag

    Rrba = ROUNDRBA(rba);
    found = TRUE;
    pBuf = BufFind(rba,
                   (fUpdatingVID) ? NULL:BFND_WRITE,
                   hVPB);
    if (!pBuf) {                        // Sector in cache ?

#ifdef PERFVIEW
       PvwCnt(fbc,WrtMiss,0); // counter for write cache misses        //b722131
#endif

       found = FALSE;                   // No, Set found to false
       pBuf = BufAlloc(rba,hVPB);       // Allocate new buffer
    }
#ifdef PERFVIEW
    else                                                               //b722131
        PvwCnt(fbc,WrtHit,0); // counter for write cache hit           //b722131
#endif

    fMustRead = TRUE;

    // If this is not a direct access write then we may not need to read in
    // the sector before modifying it. FATWriteInit initializes TCBValSec to
    // be the number of valid sectors (previously written) this is based upon
    // the size of the file upon entry to DoDiskWrite. TCBSecPos is current
    // sector position we are currently writing to. TCBSecPos is incremented
    // before the comparison to TCBValSec and if it is greater then it is not
    // necessary to read in this sector.

    if (!fDirect){
       ++(ulong_t)ptcb->TCBSecPos;
       if ((ulong_t)ptcb->TCBSecPos > (ulong_t)ptcb->TCBValSec)
          fMustRead = FALSE;
    }
    if (!found){

       // The buffer was not found in the cache. Add it to the hash table and
       // place it on the LRU chain. If we are updating the VID or Writing to
       // disk and we must read in the sector then read in the entire cache
       // block.

       if ((rc = AddHash(pBuf,hVPB)) != 0)
          panic ("BufWritePartial: AddHash found duplicate buffer");
       PlaceBuf(pBuf,PLCBUF_MRU);

       if (fUpdatingVID || (fWriteToDisk  && fMustRead)){
          if ((rc = diskw_read(hVPB,rba,pBuf)) != 0){
             BufUnlink(pBuf);
             BufFree(pBuf);
          }
       }
       else {

          // Initialize LazyRead Bit Map

          pBuf->BufFlag = BUF_LAZYREAD;           // Set LazyRead flag in hdr
          memset((void *)&pBuf->BufDirtyBits[0],  // initialize bit map to 0
                 0,
                 BYTEBITSPERSEC * SECPERBUF);
       }
    }

    // It is important not to BLOCK between setting all the bits and putting
    // data assoicated with the bits into the buffer. TKBeginNoBlock and
    // TKEndNoBlock asserts that we will not Block while setting the bits.
    // We also have the user's buffer locked down at this point. BufLock is
    // called before we call FuBuff386 but we are assured that this call will
    // not BLOCK because for a "found" buffer BUFFIND gurantees that the buffer
    // is not locked. For a "unfound buffer'' we do not yield the cpu after
    // determining that the buffer was not found and before locking the buffer
    // so no one could have slipped in and claim the buffer and locked it.

    if (!fMustRead) {
       ByteIndex = (ushort_t)((rba % (ushort_t)SECPERBUF) * (ushort_t)BYTEBITSPERSEC);

       // Since this sector was free we optimize this by clearing the
       // the entire sector worth of bits.

       memset ((void *)&pBuf->BufDirtyBits[ByteIndex],
               0xff,
               (SECSIZE/8));
    }
    else {

       // We must read sector at some point so we have to determine which bits
       // to set in the bitmap.

       ByteIndex = ((rba % SECPERBUF) * BYTEBITSPERSEC) + (SecBytPos/8);
       BitMask = ByteBitMasks[SecBytPos % 8];

       // First, we want to start off by setting those bits which keep us
       // from being at the first bit in a byte.

       cnt = cbCount;
       while (BitMask != 0x01 && cnt){
           pBuf->BufDirtyBits[ByteIndex] |= BitMask;
           BitMask <<= 1;
           if (BitMask == 0){
              ++ByteIndex;
              BitMask = 0x01;
           }
           --cnt;
       }

       // Secondly, now that we are aligned at the first bit in a byte, we
       // can optimize the setting of most of the remainer of bits by
       // determining how many full bytes are left and just putting 0xff
       // into those bytes.

       if (cnt){
          if ((fullbytes = cnt / 8) != 0){
             memset ((void *)&pBuf->BufDirtyBits[ByteIndex],
                     0xff,
                     fullbytes);
             ByteIndex += fullbytes;
             cnt -= fullbytes * 8;
          }

          // cnt contains the number of bits that are remaining. Our only
          // alternative is to use a loop to process the remaining bits which
          // will be < 8; BitMask should also be set to the first bit position
          // in the byte. We have assertions to check these two conditions.

          BufAssert (BitMask == 0x01);
          BufAssert (cnt < 8);

          for (i=0; i < cnt; ++i) {
             pBuf->BufDirtyBits[ByteIndex] |= BitMask;
             BitMask <<= 1;
             if (BitMask == 0){
                ++ByteIndex;
                BitMask = 0x01;
             }
          }
       }
    }

    // The following lock is to prevent another thread from lazy reading the
    // buffer and possibly freeing the buffer if there was an error. We are
    // assured at this point that we can lock the buffer successfully without
    // blocking.

    BufLock(pBuf);
    if ((rc = FuBuff386(cbCount,
                        (ulong_t)ptcb->TCBNextAdd,
                        pBuf->BufLinAddr + BUFOFFSET(rba) + SecBytPos)) !=0){
       BufUnLock(pBuf);
       if (!found) {
          BufUnlink(pBuf);
          BufFree(pBuf);
       }
       MMPHPostSDW();    /* performance hook (328A) */
       return (rc);
    }
    BufUnLock(pBuf);

    // If we are lazy reading the buffer and we have set all the bits then
    // turn off the lazy read flag.

    if (pBuf->BufFlag & BUF_LAZYREAD){
       if ((rc = FindNonChar(&pBuf->BufDirtyBits[0],
                             0xff,
                             BYTEBITSPERSEC * SECPERBUF)) == FALSE){
          pBuf->BufFlag &= ~BUF_LAZYREAD;
#ifdef BUFWRITEDEBUG
lazyreadoff[lazyreadoff_index]=pBuf;
lazyreadofftime[lazyreadoff_index++] = msNow();
if (lazyreadoff_index == 200) lazyreadoff_index = 0;
#endif
       }
    }
    if (fUpdatingVID){
       BufLock(pBuf);
       if (rc = SELAllocKHB(VM_HKRH_PUBRW,SECSIZE,PG_CONTIG,0,FSBUFOWNER,SSToDS(&ac)))
          panic ("BufWritePartial: Cannot allocate 512 contiguous memory");
       memcpy((PBYTE)ac.ac_va,(PBYTE)pBuf->BufLinAddr + BUFOFFSET(rba),SECSIZE);
       UpdateVID(hVPB,ac.ac_sel,0,rba,1);
       SELFreeKHB(VM_HKRH_PUBRW,0,ac.ac_sel);
       BufUnLock(pBuf);
    }
    /*  If the buffer was already dirty, just set
     *  its dirty bits--it should already be in the
     *  lazy write queue.  Otherwise, set the appropriate
     *  dirty bits and put it in the lazy write queue.
     *
     *  Either way, make sure we mark it dirtied by
     *  the current file.
     *
     *
     */
    if (pSFTFAT->sfdFAT_fldMask == 0){
       pSFTFAT->sfdFAT_fldMask = FileDirtyMsk;
       if ((FileDirtyMsk <<=1) == 0)
          FileDirtyMsk = DMASK_INIT;
    }
    pBuf->BufFileDirtyMsk |= pSFTFAT->sfdFAT_fldMask;

    pBuf->BufDirtyMsk |= abDirtOrd[REMAINDERRBA(rba) * SECPERBUF
                                  + REMAINDERRBA(rba)];
    rc = 0;

    // If we are updating the VID then check to see if the
    // buffer is writable. If it is not writable then call LazyRead to read
    // in the buffer. Once the buffer is writable call BufPreWrt to write it.

    if (fUpdatingVID){
       fMustRead = fBufWritable(pBuf,
                                BWRITABLE_GAPS | BWRITABLE_PARTIALSEC);
       if (fMustRead) {
          rc = LazyRead(pBuf,
                        NULL,
                        SSToDS(&fMustRead));
       }
       if (!rc) {
          BufIncRef(pBuf);
          fWrite = FALSE;
          while (!fWrite && pBuf->BufDirtyMsk != 0) {
              rc = BufPreWrt(pBuf,SSToDS(&fWrite));
          }
          BufDecRef(pBuf);
       }
    }
    else
       AddLW(pBuf);

    *pFound = found;
    *ppBuf = pBuf;
    MMPHPostSDW();    /* performance hook (328A) */
    return (rc);
}

/***EP  BufWrite - perform user diskwrite through buffers
 *
 *      BufWrite copies data from user's buffer into cache blocks and
 *      optionally writes the cache blocks to disk, or copies data from user's
 *      buffer into cache blocks putting the cache blocks on the lazy write
 *      chain for latter writing to disk.
 *
 *      ENTRY   ReqListSel    - Selector of request list
 *              ReqListOff    - Offset of request list
 *              _pSFT         - pointer to SFT
 *              fDirect       - Direct access flag
 *              fExtAttr      - Extended Attributes flag
 *              hVPB          - Handle to volume
 *
 *      EXIT:   rc            - NULL or Error Code.
 *
 *      HISTORY:
 *  @75084  27Oct93  D.Azzarito - when we do direct writes to partial
 *                   cache buffers, we have to send them through the
 *                   cache so a reader doesn't try to pull them in.
 *
 */
int BufWrite(ulong_t ReqListSel,
             ulong_t ReqListOff,
             ulong_t wrtCount,
             sf_entry *_pSFT,
             int fDirect,
             int fExtAttr,
             ulong_t hVPB)
{
    register tcb_t *ptcb = pTCBCur;
    ulong_t rba,Rrba,UserRba,VIDrba,rbaStart;    // @75084
    ulong_t cMiddleSectors,nextcluster;
    register PBUFHDR pBuf;
    PBUFHDR pBufTmp;
    int rc,found,EOF = FALSE,fIO = FALSE;
    register sf_entry *pSFT = _pSFT;
    ulong_t cbCount,sgcount,cExtent,contig;
    ulong_t xferaddr,BufOff,UsercExtent,ByteIndex;
    ulong_t SecClusPos,cUserSecs,cCacheSecs;
    ulong_t cusSectsToDirty,cusSectsToWrite;
    sfdFATFS *pSFTFAT;
    int fWriteToDisk = TRUE,fMustRead;
    ulong_t rbaNextRun,culRemaining;


    pSFTFAT = (sfdFATFS *)&(pSFT->sf_fsd);

    // The following IF statement determines whether or not we need to write
    // the user's data to disk immediately.

    if ((!(ptcb->TCBJfnFlag & JFN_WRTTHR)) &&
        (!(ptcb->TCBJfnFlag & JFN_NCACHE)) &&
        (wrtCount <= LWData.cbWriteCache) &&
        (fLWDaemon) &&
        (!fDirect || !pSFT) &&
        (!fHotFixing) &&
        (!fExtAttr) &&
        (!(IsDriveLocked(hVPB))))
       fWriteToDisk = FALSE;

    if (!fWriteToDisk) {
       if (((wrtCount / BUFPAGESIZE)+2) >= cBufs - cbufDirty - cbufLWLock)
          fWriteToDisk = TRUE;
    }
    if (fDirect && !pSFT)                        // @75084
      fWriteToDisk = FALSE;                      // @75084

    // If there is a parital sector at the beginning then call
    // BufWritePartial to handle it.

    if (ptcb->TCBBytCnt1) {
        if ((rc = BufWritePartial((ulong_t)ptcb->TCBBytCnt1,
                                  pSFT,
                                  SSToDS(&pBufTmp),
                                  (ulong_t)ptcb->TCBBytSecPos,
                                  SSToDS(&found),
                                  fDirect,
                                  fExtAttr,
                                  fWriteToDisk)) != 0){
           if (ReqListSel != 0)
              FreeReqList(ReqListSel,ReqListOff);
           return(rc);
        }
        pBuf = pBufTmp;
        ptcb->TCBNextAdd += ptcb->TCBBytCnt1;
        fIO = TRUE;
    }
    cMiddleSectors = ptcb->TCBSecCnt;
    ptcb->TCBBytCnt1 = 0;
    ptcb->TCBBytSecPos = 0;
    if (cMiddleSectors) {
       if (fIO){
          if (EOF = NextSec(fDirect,hVPB)){
             rc = ERROR_ACCESS_DENIED;
             goto dske;
          }
       }
       fIO = TRUE;
       SecClusPos = (ushort_t)ptcb->TCBSecClusPos;
       nextcluster = ptcb->TCBClusNum;
    }

    // For each contiguous chunk copy data into cache blocks or add the extent
    // to the request list.

    while (cMiddleSectors){
        if (rc = SetupPhysIO(SSToDS(&contig),        // ptr to # of contig secs
                             SSToDS(&nextcluster),   // ptr to next cluster
                             cMiddleSectors,         // # of secs remaining
                             nextcluster,            // cluster to start with
                             SSToDS(&rba),           // ptr to starting sector
                             SSToDS(&cMiddleSectors),// ptr to # secs
                             SSToDS(&xferaddr),      // ptr to user address
                             SecClusPos,             // Sector cluster position
                             fDirect,                // direct access flag
                             hVPB)){                 // handle to volume
           if (ReqListSel != 0)
              FreeReqList(ReqListSel,ReqListOff);
            return(rc);
        }
        MMPHPreSDW((ushort_t)contig); /* performance hook (320A) */
        cExtent = contig;
        if (!fDirect && fExtAttr) {
           FreeDosBuffer(rba,cExtent,hVPB);
        }

        VIDrba = GetVIDSector(hVPB);
        if (VIDrba >=rba  && VIDrba < rba+cExtent){
           UpdateVID(hVPB,(SEL)((ptcb->TCBDMAAdd) >>16),
                     xferaddr,rba,cExtent);
        }
        if (!fWriteToDisk) {

           if (cExtent) {
              UserRba = rba;
              UsercExtent = cExtent;
              cbCount = cExtent * (ushort_t)SECSIZE;
              SetRBA_Count(SSToDS(&Rrba),
                           rba,
                           SSToDS(&cExtent),
                           hVPB);
              rba = Rrba;

              while (cbCount) {
                 found = TRUE;
                 pBuf = BufFind(rba,BFND_WRITE,hVPB);

                 if (!pBuf)
                    found = FALSE;

                 // Figure out the number sectors that were actually part of
                 // of the users I/O request.

                 BufAssert (UsercExtent != 0);
                 BufOff = BUFOFFSET(UserRba);
                 cUserSecs = MIN (UsercExtent,BufOff == 0 ?(ushort_t)SECPERBUF:
                                 ((ushort_t)BUFPAGESIZE - BufOff)/(ushort_t)SECSIZE);

                 // Figure out the total sectors that are part of the overall
                 // rounded request. Rounding occurs in two parts, head and
                 // tail. See SetRBA_Count for details.

                 BufOff = BUFOFFSET(rba);
                 cCacheSecs = MIN (cExtent,BufOff == 0 ?(ushort_t)SECPERBUF:
                                  ((ushort_t)BUFPAGESIZE - BufOff)/(ushort_t)SECSIZE);
                 if (!found) {
                    pBuf = BufAlloc(rba,hVPB);
                    pBuf->BufFlag = 0;
                 }
                 if (!found){

                    if ((rc = AddHash(pBuf,pBuf->BufhVPB)) != 0)
                       panic ("BufWrite:AddHash found unexpected dup. buffer");
                    PlaceBuf(pBuf,PLCBUF_MRU);

                    // if we are not starting off on a rounded rba or if the
                    // remaining request does not take up a whole cache block
                    // then we need to read in the previous or suceeding
                    // sectors so that they will contain valid data.

                    if ((UserRba % SECPERBUF) || UsercExtent < SECPERBUF){
                        fMustRead = TRUE;
                        if (!fDirect){
                           if ((ulong_t)ptcb->TCBSecPos + 1 >
                               (ulong_t)ptcb->TCBValSec)
                                    fMustRead = FALSE;
                        }
                        pBuf->BufFlag |= BUF_LAZYREAD;
                        memset((void *)&pBuf->BufDirtyBits[0],
                               0,
                               BYTEBITSPERSEC * SECPERBUF);
                    }
                 }
                 sgcount = cUserSecs * (ushort_t)SECSIZE;
                 if (pSFT != NULL){
                    if (pSFTFAT->sfdFAT_fldMask == 0){
                       pSFTFAT->sfdFAT_fldMask = FileDirtyMsk;
                       if ((FileDirtyMsk <<=1) == 0)
                          FileDirtyMsk = DMASK_INIT;
                    }
                    pBuf->BufFileDirtyMsk |= pSFTFAT->sfdFAT_fldMask;
                 }
                 else
                    pBuf->BufFileDirtyMsk = -1L;

                 // The following lock is to prevent another thread from lazy
                 // reading the buffer and possibly freeing the buffer if
                 // there was an error. We are assured at this point that we
                 // can lock the buffer successfully without blocking.

                 BufLock(pBuf);
                 if ((rc = FuBuff386(sgcount,
                                     xferaddr,
                                     pBuf->BufLinAddr + BUFOFFSET(UserRba)
                                     ))!= 0){
                     BufUnLock(pBuf);
                     if (!found) {
                        BufUnlink(pBuf);
                        BufFree(pBuf);
                     }
                     if (ReqListSel != 0)
                        FreeReqList(ReqListSel,ReqListOff);
                     MMPHPostSDW();    /* performance hook (328A) */
                     return(rc);
                 }
                 BufUnLock(pBuf);
                 if (pBuf->BufFlag & BUF_LAZYREAD){

                    ByteIndex = (ushort_t)((UserRba % (ushort_t)SECPERBUF) * (ushort_t)BYTEBITSPERSEC);

                    // We know that number of bytes to set is a multiple of 8
                    // and that there are no fragments. So, we can just memset
                    // 0xff into the array.

                    memset ((void *)&pBuf->BufDirtyBits[ByteIndex],
                            0xff,
                            (ushort_t)(sgcount/8));
                    if (rc = FindNonChar(&pBuf->BufDirtyBits[0],
                                        0xff,
                                        BYTEBITSPERSEC * SECPERBUF) == FALSE){
                       pBuf->BufFlag &= ~BUF_LAZYREAD;
                    }
                 }

                 /*  Determine how many sectors to move and set
                  *  the appropriate dirty bits.
                  */

                 cusSectsToDirty = MIN (((ulong_t)SECPERBUF - REMAINDERRBA(UserRba)),
                                        cUserSecs);

                 pBuf->BufDirtyMsk |= (abDirtBits[UserRba % SECPERBUF] &
                                      abDirtBits2[(UserRba + cusSectsToDirty) %
                                      SECPERBUF]);
                 AddLW (pBuf);
                 UserRba += cUserSecs;
                 UsercExtent -= cUserSecs;
                 xferaddr += sgcount;
                 cbCount -= sgcount;
                 sgcount = sgcount / (ushort_t)SECSIZE;
                 rba += cCacheSecs;
                 cExtent -= cCacheSecs;
              }
           }
        }
        else {
          if (pSFT) {
             if (ptcb->TCBfIOLock == 0){
                if (rc = ioBeginTransfer((SEL)((ptcb->TCBDMAAdd) >>16),
                                         (ushort_t)(ptcb->TCBDMAAdd),
                                         wrtCount,
                                         0,            // buf not dirtied
                                         (struct pagelist_s *)VM_NO_ADDR,
                                         (ulong_t *)VM_NO_ADDR) != 0){
                   if (ReqListSel != 0)
                      FreeReqList(ReqListSel,ReqListOff);
                   MMPHPostSDW();    /* performance hook (328A) */
                   return (rc);
                }
             }
          }
          if (ReqListSel == 0){
             ReqListAlloc(SSToDS(&ReqListSel),
                          SSToDS(&ReqListOff));
             ReqListInit(ReqListSel,
                         ReqListOff,
                         hVPB,
                         0);
          }
          rc = ReqListAdd_Extent(rba,
                                 cExtent,
                                 (ushort_t)PB_WRITE_X,
                                 ReqListSel,
                                 ReqListOff,
                                 xferaddr);
          if (rc) {
             if (ReqListSel != 0)
                FreeReqList(ReqListSel,ReqListOff);
             MMPHPostSDW();    /* performance hook (328A) */
             return(rc);
          }
          rbaStart = rba;                        // @75084
          while (cExtent) {

             rbaNextRun = rba - (rba % SECPERBUF);
             culRemaining = cExtent + (rba % SECPERBUF);

             do {
                 pBuf = BufFind(rbaNextRun,BFND_WRITE,hVPB);
                 if (pBuf == NULL) {
                    /*
                     * Defect 75084 - if we're doing a direct write to a fragment
                     * of a cache buffer, a cache request for the other part of the
                     * fragment could kill us (since it will buffer OUR data, possibly
                     * before we can write it).  To avoid this, we will cache ALL
                     * fragments
                     */
                    if ((culRemaining < SECPERBUF) ||
                        (rbaNextRun < rbaStart)) {

                       // Not in cache.  Force into cache

                       pBuf = BufAlloc(rbaNextRun,hVPB);
                       pBuf->BufFlag = 0;
                       if ((rc = AddHash(pBuf,pBuf->BufhVPB)) != 0)
                          panic ("BufWrite:AddHash found unexpected dup. buffer");
                       PlaceBuf(pBuf,PLCBUF_MRU);

                      // if we are not starting off on a rounded rba or if the
                      // remaining request does not take up a whole cache block
                      // then we need to read in the previous or suceeding
                      // sectors so that they will contain valid data.

                      pBuf->BufFlag |= BUF_LAZYREAD;
                      memset((void *)&pBuf->BufDirtyBits[0],
                             0,
                             BYTEBITSPERSEC * SECPERBUF);
                    }
                    else {  // Not in cache, and it's a full block (END of @75084)
                      if (culRemaining >= SECPERBUF) {
                          rbaNextRun += SECPERBUF;
                          culRemaining -= SECPERBUF;
                      } else {
                           rbaNextRun += culRemaining;
                           culRemaining = 0;
                      }
                    }
                 }
              } while ((pBuf == NULL) && (culRemaining));

               /*  Increment the holds on our cache buffer, if we
                *  found one.
                *
                */
               if (pBuf != NULL) {
                   BufIncRef (pBuf);
               }
              /*  We've found the end of a subrun, either because we
               *  reached the end of the run or because there was a
               *  cache hit.
               *
               *  If the read is non-zero length, build a request
               *  block for it.
               *
               */
              if ( cExtent > culRemaining ) {
                  cusSectsToWrite = cExtent - culRemaining;
              }
              else {
                  cusSectsToWrite = 0;
              }
              if (cusSectsToWrite) {
                 xferaddr += cusSectsToWrite * SECSIZE;
              }
              /*  We've posted our write, if any.  If there are
               *  sectors left in this run, we know that the next
               *  MIN (SECPERBUF, cExtent - cusSectsToWrite) sectors
               *  are in a cache block.
               *
               *  Note that we may not be at the first sector in
               *  the block (if the beginning of our original
               *  run length is in the cache).
               *
               */

              rba += cusSectsToWrite;
              cExtent -= cusSectsToWrite;

              if (cExtent) {
                 BufOff = BUFOFFSET(rba);
                 sgcount = MIN(((ulong_t)BUFPAGESIZE - BufOff),
                               (cExtent * (ulong_t)SECSIZE));

                 // The following lock is to prevent another thread from lazy
                 // reading the buffer and possibly freeing the buffer if
                 // there was an error. We are assured at this point that we
                 // can lock the buffer successfully without blocking.

                 BufLock(pBuf);
                 if ((rc = FuBuff386(sgcount,
                                     xferaddr,
                                     pBuf->BufLinAddr + BUFOFFSET(rba)))!= 0){
                     BufUnLock(pBuf);
                     BufDecRef (pBuf);
                     if (ReqListSel != 0)
                        FreeReqList(ReqListSel,ReqListOff);
                     MMPHPostSDW();    /* performance hook (328A) */
                     return(rc);
                 }
                 BufUnLock(pBuf);
                 if (pBuf->BufFlag & BUF_LAZYREAD){

                    ByteIndex = (ushort_t)((rba % (ushort_t)SECPERBUF) * (ushort_t)BYTEBITSPERSEC);

                    // We know that number of bytes to set is a multiple of 8
                    // and that there are no fragments. So, we can just memset
                    // 0xff into the array.

                    memset ((void *)&pBuf->BufDirtyBits[ByteIndex],
                            0xff,
                            (ushort_t)(sgcount/8));
                    if (rc = FindNonChar(&pBuf->BufDirtyBits[0],
                                         0xff,
                                         BYTEBITSPERSEC * SECPERBUF) == FALSE){
                       pBuf->BufFlag &= ~BUF_LAZYREAD;
                    }
                 }
                 if (pSFT != NULL){
                    if (pSFTFAT->sfdFAT_fldMask == 0){
                       pSFTFAT->sfdFAT_fldMask = FileDirtyMsk;
                       if ((FileDirtyMsk <<=1) == 0)
                          FileDirtyMsk = DMASK_INIT;
                    }
                    pBuf->BufFileDirtyMsk |= pSFTFAT->sfdFAT_fldMask;
                 }
                 else
                    pBuf->BufFileDirtyMsk = -1L;

                 /*  Determine how many sectors to move and set
                  *  the appropriate dirty bits.
                  */

                 cusSectsToDirty = MIN (((ulong_t)SECPERBUF - REMAINDERRBA(rba)),
                                        sgcount / SECSIZE);

                 pBuf->BufDirtyMsk |= (abDirtBits[rba % SECPERBUF] &
                                      abDirtBits2[(rba + cusSectsToDirty) %
                                      SECPERBUF]);
                 AddLW (pBuf);
                 xferaddr += sgcount;
                 rba += sgcount / SECSIZE;
                 cExtent -= sgcount / SECSIZE;
              }
              /*  Decrement the holds on our cache buffer, if we
               *  found one.
               *
               */
              if (pBuf != NULL) {
                  BufDecRef (pBuf);
              }
          }
        }
        if (cMiddleSectors){
           if (rc = IsEOF(nextcluster,hVPB)) // test for EOF on FAT size
              cMiddleSectors = 0;       // Done with transfer. No error.
           SecClusPos = 0;              // Start at first sector within cluster
           ++ptcb->TCBLastPos;          //  We'll be using next cluster
        }
        MMPHPostSDW();    /* performance hook (328A) */
    }
    if (!fDirect) {
       (ulong_t)ptcb->TCBSecPos += ptcb->TCBSecCnt;
    }

    if (ReqListSel != 0){
       rc = ReqListExecute(ReqListSel,ReqListOff);
       if (rc) return(rc);
    }

    // If there is a parital sector at the end then call
    // BufWritePartial to handle it.

    if (ptcb->TCBBytCnt2){

        // If any I/O has been done then advance to the next sector.

        if (fIO){
           if (EOF = NextSec(fDirect,hVPB)){
              rc = ERROR_ACCESS_DENIED;
              goto dske;
           }
        }
        if (rc = BufWritePartial((ulong_t)ptcb->TCBBytCnt2,
                                 pSFT,
                                 SSToDS(&pBufTmp),
                                 (ulong_t)ptcb->TCBBytSecPos,
                                 SSToDS(&found),
                                 fDirect,
                                 fExtAttr,
                                 fWriteToDisk) != 0){
           if (ReqListSel != 0)
              FreeReqList(ReqListSel,ReqListOff);
           return(rc);
        }
        pBuf = pBufTmp;
        ptcb->TCBNextAdd += ptcb->TCBBytCnt2;
    }
    rc = 0;
dske:
    return(rc);
}
