/*static char *SCCSID = "@(#)sdr.c      6.28 92/02/27";*/
#define INCL_DOSERRORS
#include <os2def.h>
#include <bse.h>
#include <fsh.h>
#include <strat2.h>
#include <pvwxport.h>

#include "const.h"
#include "misc.h"
#include "chain.h"
#include "buf.h"
#include "dir.h"
#include "volume.h"
#include "filemode.h"
#include "storage.h"
#include "tables.h"
#include "pb_ifs.h"
#include "lw.h"
#include "frame.h"
#include "mmphiseg.h"
#define  INCL_DOSINFOSEG /* for mmph */
#include "log.h"
#include "fsdata.h"
#include "pbmacros.h"
#include "asyncio.h"
#include "heap.h"
#include "pvw.h"
#include "assert.h"

#include "pr_cache.h"
#include "pr_debug.h"
#include "pr_heap.h"
#include "pr_read.h"
#include "pr_misc.h"
#include "mmph.h"

/*
 ***    SDR - Synchronous Disk Read
 *
 *      SDR does a synchronous disk read from the disk into client memory.
 *
 *      We want to read as much as we can directly
 *      into the client's address space in reads as big as we can.
 *
 *      The algorithm is as follows:
 *
 *      0. Compute run length
 *
 *      1. If the request starts non-sector aligned, transfer as much
 *              as possible via the cache.
 *
 *      The remaining request now starts on a sector boundary.
 *
 *      2. If (request is not for integral # of sectors)
 *           round down request
 *           remember round down amount
 *
 *      The request is now for an integral number of sectors.
 *
 *      4. if (run exhausts request) AND (there is more run)
 *         and (RAFLAG || have tail round down), trim back run to end
 *         at a whole cache block.  Store as round down amount
 *
 *      5. Scan run for cache hit.
 *              Submit request for read up to hit
 *              take hit sectors from cache
 *      6. loop to 5 until run exhausted
 *
 *      7. if all sectors not transfered, compute run len and goto 4
 *
 *      8. transfer through cache any "round down" tail amount.
 *
 *      9. Wait until all of the async requests are done
 *
 *      We want to transfer as much of the request as possible directly
 *      to the reader's buffer via direct disk transfer, except the last
 *      part which we want to put in the cache so that we'll be read
 *      ahead for next time.  We take the last part of the from-disk-transfer,
 *      the part that would fit in the last cache block if we were caching, and
 *      read that part thorough the cache, if:
 *
 *      1) the last sector of the trimmed request would not be the
 *         last sector in a cache block, AND
 *      2) the next sector beyond our read belongs to the file,
 *      3) AND ((read-ahead-advisory set) OR (remainder count to xfer))
 *
 *      Note that we always take stuff out of the cache when we find it
 *      there.  In theory, if you have a big transfer and the middle
 *      SPB sectors are in a cache block, and they're clean, it might
 *      be faster to ignore the cache and re-read the stuff into user
 *      memory.  But this probably wouldn't be worthwhile if there were
 *      nearby dirty cache blocks, since you'd have to break the
 *      contiguous read anyway.  Likewise, if there were 2*SPB sectors
 *      in the cache, it's probably a win to drop the read and copy the
 *      data.  Because the payoff of ignoring the cache is modest, and
 *      we'd have to have a lot of code to check for cases when the
 *      trick is actually a net loss, we ignore this and always take
 *      anything that we find in the cache.
 *
 *      I/O notification fields.
 *
 *        For some requests, we may post multiple parallel disk
 *        reads to the driver.  Each such request has the
 *        address of S_NOTE stored in it, and a bit mask.
 *        When we queue the request, we set that bit in S_NOTE.
 *        When the request is done, the driver clears the bit
 *        in S_NOTE.  When the bit count goes to zero, the
 *        driver does a wakeup on S_NOTE.
 *
 *        We don't want to accidentially reuse mask bits, and we don't
 *        want 32 requests outstanding anyway, so as soon as
 *        NoteMsk reaches 8 bits we'll block until they all
 *        complete and then we'll reset the mask to 1.
 *        Of course, we don't reset the error mask part of NoteMsk,
 *        but let it accumulate.
 *
 *      ENTRY   fReadAhead = 0 if read ahead ruled out, 1 if not
 *              cusXfer = transfer count (must be non-zero)
 *              ulSeekPtr = seek pointer
 *              pbXferAddr = transfer address
 *              poft = OFT pointer
 *
 *      RETURNS NO_ERROR if successful, else error code.
 *      BUGBUG  (ebx) = address of buffer header if last byte read
 *                        from a buffer, else 0. --- consider this ---
 *                        read ahead guy wants it
 *
 *      HISTORY
 *  @76193  23-Nov-1993 by D.Azzarito - Change holds for LOG support
 *          13-Feb-90 by JR (John Rogers, JohnRo@Microsoft)
 *              - Added assertion checking.
 */

USHORT
SDR (cusXfer, ulSeekPtr, pbXferAddr, fSectorIO, pfht, pfrm)
USHORT          cusXfer;
ULONG           ulSeekPtr;
PBYTE           pbXferAddr;
BOOL            fSectorIO;
PFHT            pfht;
PFRAME          pfrm;
{
    PBUFNODE pbuf;               /* ptr to buffer for final portion      */
    PSECTOR psec;
    LSN lsnStart;                   /* Starting sector of current run    */
    ULONG culRunLen;             /* Run Length (in sectors)                    */
    ULONG culSectsToXfer;     /* Remaining sectors in request       */
    USHORT cusRemnant;        /* bytes in partial-sector tail       */
    USHORT usRet = NO_ERROR;
    USHORT usRetCode;
    BYTE bEventFlags = 0;             /* Event flags */
    POFT poft = pfht->poft;
    BOOL ReadDirDone = FALSE;
    PNOTEREC pNoteRec = NULL;         /*        Notification Record for async Reads */
    BYTE byCreclOn = FALSE ;  /* Should CRECL force req to cache   */
    USHORT usFirstToCheck;
    USHORT usBytesToRead;
    PBLOCK pbWork;
    UINT ui;


#ifndef MINIFSD

   /*
    * IF this request is smaller than the crecl size(global variable)
    * THEN set local flag for caching to be read throughout HPFS
    *
    */

   if (pfht->usMode & OPEN_NO_CACHE)
       byCreclOn = FALSE;

   else {

     if ( (pfht->usMode & SHARING_DENY_NONE) ||
          (pfht->usMode & SHARING_DENY_WRITE) ) {

           byCreclOn = TRUE;

       if (pfht->usMode2 & OPEN_BY_SERVER) {
          usRetCode = FSH_QUERYOPLOCK();
          if (usRetCode)
            byCreclOn = FALSE;
       }
     }

   }
#endif

 if (byCreclOn) {

  if (fSectorIO) {
   if ( ((usRdIOSize/SECSIZE) < cusXfer) ||
             ((cusXfer / SPB) > (cBufs / 4)) ) {
      byCreclOn = FALSE;
   }

  } else {  /* not sector I/O */

   if ( (usRdIOSize < cusXfer) ||
             ( (cusXfer / (SPB*SECSIZE) ) > (cBufs / 4) ) ) {
      byCreclOn = FALSE;
   }
  }
 }

    if (fSectorIO) {

        /* Do BigDisk DASD I/O by sectors, not bytes */

        lsnStart = ulSeekPtr;
        culRunLen = poft->fst.a.aall[0].csecRun - ulSeekPtr;

        culSectsToXfer = cusXfer;
        cusRemnant = 0;

    } else {

        do {

            /*  Determine the starting lsn and run length of the
             *  next run.  If the first block is partial, invoke
             *  CachXfer to read it.  CachXfer will update lsnStart,
             *  cusXfer, culRunLen, ulSeekPtr, pbXferAddr, and
             *  pbufLastUsed.
             *
             */
#ifdef DEBUG
            if (cusXfer == 0)
                INTERR (__FILE__" cusXfer == 0");
#endif
            if((lsnStart = CPS(ulSeekPtr,poft,&culRunLen,pfrm)) == LSN_BOGUS) {

               MMPHPreSDR(); /* performance hook (3313) */
                                                            //#86865
               usRet = ERROR_GEN_FAILURE;
               goto ReadDone;
            }
                                                            //@82290
            lsnStart &= SECMASK; /* wipe out volume bits */
            lsnStart |= pfrm->pvol->ulVolBits; /* get volume bits */

            MMPHPreSDR(); /* performance hook (3313) */

            /*  If we are not on a sector boundary or if the entire
             *  transfer will fit within a cache buffer then
             *  transfer it via the cache
             */

        if ((ulSeekPtr % SECSIZE) != 0 ||
                ((lsnStart % SPB +
                USSHIFTRUL (cusXfer + ulSeekPtr % SECSIZE + SECSIZE - 1, SECSHIFT)
                <= SPB) && (!(pfht->usMode & OPEN_NO_CACHE)))) {

               PBUFNODE pbufLastUsed;

               if (usRet = CachXfer(&lsnStart, &cusXfer,
                                    &culRunLen, &ulSeekPtr,
                                    &pbXferAddr, &pbufLastUsed, pfrm)) {
                 goto ReadDone;
               }
        }


        } while (((ulSeekPtr % SECSIZE) || (culRunLen == 0)) && cusXfer);

        /*  Our request is now sector-aligned at the front.  See if
         *  we have a non-sector sized tail.
         *
         */
        culSectsToXfer = cusXfer/SECSIZE;
        cusRemnant = cusXfer % SECSIZE;

    }

    while (culSectsToXfer) {

#ifdef DEBUG
        if (culRunLen == 0)
            INTERR (__FILE__" culRunLen == 0 (2)");
#endif
        /*
         *  If the run that we have available is longer than what we
         *  need to transfer...
         */
        if (culRunLen > culSectsToXfer) {

            culRunLen = culSectsToXfer;

            /*  lsnStart + culRunLen is the sector following
             *  the end of the run length.  If it is a multiple
             *  of SPB, the run length ends at a block boundary.
             *
             */
            if ((cusRemnant) && ((lsnStart + culRunLen) % SPB)) {

                /*  This is the last run in the request.  However,
                 *  it includes the partial sector at the end of
                 *  the request (cusRemnant), and some entire
                 *  sectors before it which belong in the same
                 *  block.  If we read those sectors directly
                 *  to disk, we'll turn right around and read
                 *  them into the cache when we read the remnant.
                 *  Instead, we'll shorten the run to end at
                 *  the start of that last cache block, and add
                 *  the extra to cusRemnant.
                 *
                 */
                ULONG cul;

                cul = MIN( (lsnStart + culRunLen) % SPB, culSectsToXfer );
                culSectsToXfer -= cul;
                culRunLen = culSectsToXfer;
                cusRemnant += (USHORT)( cul * SECSIZE );
            }

        }

        /*  Entire run length, if any, may be transferred.  */

        if (culSectsToXfer) {

            /*  Update seek pointer and number of sectors left
             *  to transfer before the actual read.
             */
            ulSeekPtr += (fSectorIO) ?
                          culRunLen : culRunLen * SECSIZE;

            culSectsToXfer -= culRunLen;

            lsnStart |= poft->pVolume->ulVolBits;

            if (!ReadDirDone) {

         if (IsAsyncIOable(lsnStart)) {

            ReadDirDone = TRUE;    /* set physical read event flag */
            pNoteRec = CNR (pfrm);

           /* Set byCrecl Flag */
            pNoteRec->byCreclOn = byCreclOn;

            ASSERT_NOTEREC(pNoteRec);
         }
            }

            if (fSectorIO) {
                cusXfer *= SECSIZE;
            }

            if (usRet = ReadDir (lsnStart, culRunLen,
                                 &pbXferAddr, &cusXfer,
                                 pNoteRec, &bEventFlags, pfrm)) {

               if (!(bEventFlags & IO_DONE_EVENT)) {
                    MAYBLOCK (FSH_SEMCLEAR (&(pNoteRec->block)));
               }
               goto ReadDone;
            }

            if (fSectorIO) {
                cusXfer /= SECSIZE;
            }

            ASSERT_NOTEREC(pNoteRec);

            /*  Since we've changed lsnStart, we need to
             *  determine the new device lsn for the next
             *  subrun or the remnant, if either is to be
             *  done.
             *
             */
            if (culSectsToXfer || cusRemnant) {
                if ((lsnStart = CPS(ulSeekPtr, poft, &culRunLen,pfrm)) ==
                                                          LSN_BOGUS) {
                                                            //#86865
                  usRet = ERROR_GEN_FAILURE;
                  goto ReadDone;
                }
            }
        }

    }


    if (cusRemnant) {

        /*  If there is a remnant portion, we need to read it.
         *  Find the data in cache buffer, (possibly reading it
         *  from disk) and copy from the buffer's data area to
         *  pbXferAddr.  Note that the remnant may be more than
         *  one sector's worth, but it all fits in one block.
         *
         *  Before we copy the bytes, verify that the user's
         *  data address is valid and in memory.  If this can't
         *  be done, return the error.  Note that, since ProbeAddress
         *  may block, we hold the buffer over the call.
         */

        psec = RdBuf(lsnStart, BP_KEEP, &pbuf, pfrm);

        if ( psec == NULL ) {
                                                            //#85759
          usRet = ERROR_GEN_FAILURE;
          goto ReadDone;
        }


        if (pbuf->bBadMask != 0) {

            /*
             * There was a read error in one of the other sectors
             * in the block--see if we're going to spill over
             * into that sector.
             */
            if (usRet = ChkError(psec, cusRemnant, pbuf, pfrm)) {
                goto ReadDone;
            }
        }

                                                 //#114986
#ifndef MINIFSD
        if (pbuf->fFlag & BFL_LZREAD) {

          usFirstToCheck = (lsnStart % SPB) * SECSIZE;  /* offset of first byte */

          usBytesToRead = (cusRemnant + 7) / 8;

          pbWork = (PBYTE) pbuf->pLzRdBitMap + (usFirstToCheck / 8);

          for (ui=0; ui < usBytesToRead ; ui++, pbWork++) {
             if (*pbWork != 0x00) {
               if ((usRet = LazyRead(pbuf,pfrm)) == NULL)
                 break;
                else   /* LazyRead error */
                 goto ReadDone;
             }
          }

        }
#endif

        IncrHoldsBUF(pbuf);  // @76193
        pfrm->fRecheckAddr = TRUE;
        usRet = ProbeAddress(PB_OPWRITE, pbXferAddr, cusRemnant, pfrm);
        DecrHoldsBUF(pbuf);  // @76193

        if (usRet) {
         goto ReadDone;
        }

        BYTE_COPY ( (PBYTE)psec, pbXferAddr, cusRemnant);

    }

ReadDone:
    /*
     * We've handled the entire transfer, but we may have some
     * async reads that we queued which are still waiting.
     *
     * RNM unlocks the segments & releases the IOHandle for each PIOH
     * attached to pNoteRec
     */

    if ((ReadDirDone) && (pNoteRec)) {

       ASSERT_NOTEREC(pNoteRec);
       RNM (pNoteRec, FALSE, pfrm);
       ASSERT_NOTEREC(pNoteRec);

       /* Check to see if any async requests resulted in errors */
       if (pNoteRec->errmsk) {
#ifdef DEBUG
          dprintf (__FILE__": Async I/O read error\n");
          Int3 ();
#endif
//#114986
         usRet = ERROR_GEN_FAILURE;
       }

       /* return the Notification record */
       RetHeap (pNoteRec);
    }

    MMPHPostSDR(); /* performance hook (3313) */

    return GetHardErr (usRet);
}
