/*static char *SCCSID = "@(#)fs_read.c  6.3 91/11/13";*/
#define SCCSID "@(#)fs_read.c   6.3 91/11/13"
/*
 *      IBM/Microsoft Confidential
 *
 *      Copyright (c) IBM Corporation 1987, 1989
 *      Copyright (c) Microsoft Corporation 1987, 1989
 *
 *      All Rights Reserved
 *
 */

/*
 *  FS_READ.C
 *
 *    DESCRIPTION
 *
 *      Read from an open file.
 *
 *
 *    FUNCTIONS DEFINED
 *
 *      FS_READ ()  -   Read a specified number of bytes from a file to
 *                      a buffer location.
 *
 *
 *  REVISION HISTORY (in reverse chronological order)
 *  -----------------------------------------------------------------------
 *  3/08/96     Steve Pratt     Add async readahead call                      
 *  9/26/92     Frank Schroeder Changed GetNextDE to not return prdrRawDirRec.
 *  4/20/90     Brian Andrew    Revised to allow multiple threads into this
 *                              routine.
 *
 *  10/9/89     Brian Andrew    Added last sector to FindBuf.
 *  10/6/89     Brian Andrew    Added break if GetNextDE fails, early.
 *  9/25/89     Brian Andrew    Added STAT parameter to 'FindBuf'.
 *  9/12/89     Brian Andrew    Corrected error where cbThisBlock,
 *                                  cbToTransfer were not updated at end of
 *                                  while loop.  Also released buffer when
 *                                  exiting loop.
 *  9/6/89      Brian Andrew    Initial working version
 *  8/4/89      Brian Andrew    Initial Blank Template
 *
 *
 *  TO DO
 *  -------------------------------------------------------------------------
 */


#define INCL_DOSERRORS
#define INCL_NOPMAPI
#include <os2.h>
#include <fsd.h>
#include <fsh.h>

#include "cdfs.h"
#include "sem.h"
#include "stdlib.h"
#include "vol.h"
#include "path.h"
#include "file.h"
#include "buffer.h"
#include "mem.h"
#include "heap.h"
#include "message.h"
#include "pr_memin.h"
#include "pr_dir.h"
#include "pr_misc.h"
#include "pr_cache.h"

#ifdef STATS_H
#include "stats.h"
#endif

#include "fsdata.h"
#include "memory.h"

#ifdef DEBUG
#include "pr_DEBUG.h"
#endif


/***    FS_READ ()  -   Read a specified number of bytes from a file to
 *    a buffer location.
 *
 *  DESCRIPTION
 *
 *      This function is used to transfer bytes from a file (may be direct
 *      access) to a user's buffer area.  We return the number of bytes
 *      transferred as well.  Returning 0 bytes indicates end of file.
 *
 *      We use the information in the file independent and dependent
 *      areas to find the bytes on the disk.  We have to locate the extent,
 *      logical block and sector containing the target bytes.
 *
 *      The starting point for the search is current file position.  If
 *      it is the same as the file size then we are at the end of the file.
 *
 *      If we are not at the end of the file, we must first locate the
 *      extent which contains the data.  In the file dependent area are
 *      directory entries for two extents, the first extent for the file and
 *      the most recently accessed extent.  We start with the recent extent,
 *      if the starting byte lies in this extent or later, we walk forward
 *      through directory entries until we exhaust the file (it should be
 *      impossible not to find the correct entry, any problems should have
 *      been found in FS_OPENCREATE).
 *
 *      Once we find the extent, we need the logical block within the extent
 *      where the data begins.  We compute this from the starting offset of
 *      the extent, the XAR length and interleave size if present.
 *
 *      Once we have the starting logical block and offset, we convert this
 *      to the starting sector and offset.  With this sector in hand we
 *      transfer a block of bytes.  Its possible that the limiting factor
 *      for any transfer could be the sector, logical block, extent size or
 *      number of user's bytes so we have to maintain a variable for each
 *      of these.
 *
 *      We go to a little extra trouble not to release a sector until
 *      we're sure we are done with it.
 *
 *
 *  ENTRY
 *
 *      psffsi      = Pointer to file independent area.  Contains the file's
 *        time stamps, size and position information.
 *
 *      psffsd      = Pointer to file dependent area.  Contains the
 *        directory entry for the first and most recently accessed
 *        extents.  It also contains the semaphores and flags
 *        which control the access to this data.
 *
 *      pcData      = Pointer to user's buffer to store data
 *
 *      pusLen      = Pointer to location to store number of bytes
 *        transferred.  On entry this is the number of bytes
 *        to transfer.
 *
 *      IOflag      = Indicates information about the operation on the handle,
 *        not used here.
 *
 *
 *  LOCAL
 *
 *      usRet     = Return value.
 *      pdeThisDE       = Directory entry for extent to examine.
 *      pdePrevDE       = DE structure for last DE used.
 *      pdeTempDE       = Temporary DE pointer for performing DE switch.
 *      pbBuf     = Current sector to look at.
 *      pvpfsi    = Pointer to volume independent area.
 *      pvddVolData     = Pointer to volume dependent area.
 *      cbCurrentPos    = Current position in the bytes to transfer.
 *      cbToTransfer    = Number of bytes left to transfer.
 *      cbThisBlock     = Number of bytes in this interleave block.
 *      snThisSector    = Current sector needed to examine.
 *      snPrevSector    = Last sector examined.
 *      cbSectorOffset  = Offset in the current sector.
 *      lbThisLB  = Current logical block number.
 *      cbStartByte     = Starting file byte in the logical block.
 *      cbLastByte      = Last file byte in the logical block.
 *      cbLBOffset      = Offset into the logical block.
 *      cbLBSize  = Size of the logical block.
 *      lbLastLBN       = Last Logical Block of the extent.
 *      fNewExtent      = Flag indicating if new extent is used.
 *
 *
 *  JUMP POINTS
 *
 *      FS_READ_End_For_Loop -- End of main read loop.
 *
 *
 *  EXIT
 *
 *      Error returned from GETVOLPARM of PROBEBUF if failure.
 *      ERROR_INVALID_ACCESS if close has started.
 *      Return value from GetNextDE if failure.
 *      Return value from FindBuf if failure.
 *      NO_ERROR if transfer is successful.
 */

int far pascal
FS_READ (psffsi, psffsd, pcData, pusLen, IOflag)
PSFFSI    psffsi;
PSFFSD    psffsd;
PCHAR     pcData;
PUSHORT   pusLen;
USHORT    IOflag;
{

  USHORT        usRet;
  PDIR_ENTRY    pdeThisDE;
  PDIR_ENTRY    pdePrevDE;
  PDIR_ENTRY    pdeTempDE;
  PBUF    pbBuf;
  PVPFSI        pvpfsi;
  PVOL_DEP_DATA       pvddVolData;
  ULONG         cbCurrentPos;
  USHORT        cbToTransfer;
  USHORT        cbThisBlock;
  LONG    snThisSector;
  LONG    snPrevSector;
  USHORT        cbSectorOffset;
  ULONG         lbThisLB;
  ULONG         cbStartByte;
  ULONG         cbLastByte;
  USHORT        cbLBOffset;
  USHORT        cbLBSize;
  ULONG         lbLastLBN;
  BOOL          fNewExtent;
  BOOL          bFirstRead;              /* Is this the first read */
  LONG          snFirstSector;           /* first sector read in this read */


#ifdef DEBUG
  dprintf ("FS_READ: Bytes=%d, Position=%ld, Size=%ld\n",
       *pusLen, psffsi->sfi_position, psffsi->sfi_size);
#endif


  /* Check if first time initialization */
  if (usRet = CHECK_INIT)
    return usRet;

#ifdef STATS_H
  CurSt.st_read++;
#endif

  /*
   *  We perform the necessary structure allocations and obtain the
   *  the volume structures.
   */
  if (!(usRet = FSH_GETVOLPARM (  psffsi->sfi_hVPB, &pvpfsi,
                  (PPVPFSD) &pvddVolData)))
  {
    /* Get the DE structure for the current DE */
    if (!(usRet = GetDirEntry (&pdeThisDE)))
    {
      /* Get the DE structure for previous DE */
      if (!(usRet = GetDirEntry (&pdePrevDE)))
      {
        /* Wait on the file semaphore */
        SEM_REQ (((PFILE_DEP_DATA) psffsd)->sFileSem);

        /* Check if a close has started */
        if (!((FILE_DEP_DATA FAR *) psffsd)->fCloseFlag)
        {
          /* Register as a reader */
          ((PFILE_DEP_DATA) psffsd)->usCountRead++;

          /* Make a local copy of the file position */
          cbCurrentPos = psffsi->sfi_position;

          /* Give up the semaphore */
          SEM_CLEAR (((PFILE_DEP_DATA) psffsd)->sFileSem);

          /*
           *  Initially check whether we have any bytes to transfer.
           *  If the file position is the same as the file size,
           *  then we have no bytes to return.
           */
          if (cbCurrentPos >= psffsi->sfi_size)
          {
            *pusLen = 0;

            fNewExtent = FALSE;

          } /* end if past end of file */

          /* Else there are some bytes to consider */
          else
          {
            /* Make a local copy of the logical block sz */
            cbLBSize = pvddVolData->pvdVolDesc->cbLogBlk;

            /* Compute the maximum number of bytes to transfer */
            cbToTransfer = (USHORT)
                    (min (  (   psffsi->sfi_size -
                          cbCurrentPos),
                        ( *pusLen)));

            /* Set up the other variables */
            *pusLen = 0;
            snPrevSector = NULL_BUF;
            pbBuf = NULL;

            /*
             *  We need to find the directory entry which
             *  controls the extent which has the first byte to
             *  transfer.  If the first byte to transfer is equal
             *  to or after the first byte in the last directory
             *  entry we use that as a starting point.  Otherwise
             *  we use the first directory entry as a starting
             *  point.
             *
             *  We don't have to worry about going beyond the
             *  end of the file since we know that we have
             *  already checked if starting beyond the end of
             *  the file.
             */

            /* Grab the semaphore while looking at the DE's */
            SEM_REQ (((PFILE_DEP_DATA) psffsd)->sFileSem);

            if (cbCurrentPos <
              ((PFILE_DEP_DATA) psffsd)->pdeLastDirRec->cbStartOffset)
            {
              *pdeThisDE =
                *((PFILE_DEP_DATA) psffsd)->pdeFirstDirRec;

              /* Show that there is a new last extent */
              fNewExtent = TRUE;

            } /* end if use first DE as starting point */

            /* Else use last DE */
            else
            {
              *pdeThisDE =
                *((PFILE_DEP_DATA) psffsd)->pdeLastDirRec;

              /* Show that the last extent was used */
              fNewExtent = FALSE;

            } /* end else use most recently accessed DE */

            /* Give up the semaphore */
            SEM_CLEAR (((PFILE_DEP_DATA) psffsd)->sFileSem);

            bFirstRead = TRUE;               /* set first read flag  */
            /*
             *  We now enter a loop, where we continually
             *  transfer blocks of data to the user's area until
             *  we hit the end of the file or all the required
             *  bytes are transferred.
             *
             *  The following variables are valid at this point.
             *
             *      cbCurrentPos  Offset in the file of the
             *        next byte to transfer.
             *
             *      pdeThisDE     The directory entry to
             *        use as a starting
             *        point in the search.
             *
             *      *pusLen       The number of bytes
             *        transferred so far.
             *
             *      cbToTransfer  The number of bytes still
             *        to transfer.
             *
             *      cbThisBlock   The maximum number to
             *        transfer on this run
             *        through the loop.
             *
             *      pcData        The address in the user's
             *        buffer to put the next
             *        byte.
             */
            for (;;)
            {
              /* Set the max number for this try */
              cbThisBlock = cbToTransfer;

              /*
               *  March through extents until the correct
               *  extent is found
               */
              while ( cbCurrentPos >=
                  (   pdeThisDE->cbStartOffset +
                    pdeThisDE->cbDataLen))
              {
                /* Swap the pointers to the DE structs */
                pdeTempDE = pdeThisDE;
                pdeThisDE = pdePrevDE;
                pdePrevDE = pdeTempDE;

                if ((usRet = GetNextDE (pdeThisDE, pdePrevDE,
                            pvddVolData,
                            psffsi->sfi_hVPB,
                            NULL,
                            NULL)))

                {
                  /* Release pbBuf if it has a value */
                  if (pbBuf)
                  {
                    RELEASE_BUF (pbBuf);

                  }

                  goto FS_READ_End_For_Loop;

                } /* end if can't get the next sector */

                /* Indicate that another extent was used */
                fNewExtent = TRUE;

              } /* end while */

              /*
               *  We now determine how many of the bytes to transfer come
               *  from this extent.
               */

              cbThisBlock = (USHORT)
                  (min (  (   pdeThisDE->cbStartOffset +
                          pdeThisDE->cbDataLen -
                          cbCurrentPos),
                      cbThisBlock));

              /*
               *  pdeThisDE now points to the extent which
               *  contains the next byte to transfer to the
               *  user's area.  We now compute the next logical
               *  block to transfer data out of and the offset
               *  in the block where to start transferring.
               *  Once we have these, we have to determine
               *  which is the determining boundary:
               *  'cbThisBlock' or the bytes remaining in this
               *  block.  'cbThisBlock' will be given the
               *  minimum of these two values.
               */

              lbThisLB =  pdeThisDE->lbFileLoc +
                    pdeThisDE->clbXARLen;
              cbStartByte = pdeThisDE->cbStartOffset;

              /*
               *  Compute the last byte of the current file
               *  unit, (allows for interleave)
               */

              cbLastByte =    cbStartByte +
                      (   pdeThisDE->clbIntLeaveSize ?
                        (   (ULONG) (   cbLBSize *
                                pdeThisDE->clbIntLeaveSize)):
                        pdeThisDE->cbDataLen);

              /*
               *  If interleave, we may have to march through
               *  the interleave
               */
              while (cbCurrentPos >= cbLastByte)
              {
                lbThisLB += (   pdeThisDE->clbIntLeaveSize +
                        pdeThisDE->clbIntLeaveSkip);

                cbStartByte = cbLastByte;

                cbLastByte += ( cbLBSize *
                        pdeThisDE->clbIntLeaveSize);

              } /* end while */

              /*
               *  Compute how many bytes can be transferred
               *  from file unit
               */
              cbThisBlock = (USHORT)
                    (min (  cbLastByte - cbCurrentPos,
                        cbThisBlock));

              /* Convert to a logical block and an offset */
              cbLastByte = cbCurrentPos - cbStartByte;

              lbThisLB += (cbLastByte / cbLBSize);

              cbLBOffset =    ((USHORT) cbLastByte) &
                      (cbLBSize - 1);

              /*
               *  We now have the logical block and the logical
               *  block offset for the start of the next
               *  transfer.  We convert this to a sector and a
               *  sector offset.
               *
               *  At this point cbThisBlock is the number of
               *  bytes to transfer this time through the loop.
               *  Once we have the starting offset in the
               *  sector, we compare the remaining bytes in the
               *  sector to cbThisBlock and set cbThisBlock to
               *  the smaller of the two values.
               */

              /* Get the sector */
              snThisSector = lb_to_ls (lbThisLB, cbLBSize);

              cbSectorOffset =    lb_offset (lbThisLB, cbLBSize) +
                        cbLBOffset;

              /* Add any carryover offset to sector */

                      snThisSector += (cbSectorOffset/SECTOR_SIZE);

              /* Compute the sector offset */
              cbSectorOffset =    ((SECTOR_SIZE - 1) &
                        cbSectorOffset);

              cbThisBlock = min ( cbThisBlock,
                        (USHORT) (      SECTOR_SIZE -
                              cbSectorOffset));

              /*
               *  We now know the sector number, the sector
               *  offset and the number of bytes remaining in
               *  the sector in pbBuf.  If we haven't moved on
               *  to the next sector and there are still
               *  bytes left in 'pbBuf', we don't have to get
               *  the next buffer.
               */

              if (snPrevSector != snThisSector)
              {
                snPrevSector = snThisSector;

                /* Release 'pbBuf' if current */
                if (pbBuf)
                {
                  RELEASE_BUF (pbBuf);

                }

                /* Compute the last sector of the extent */
                if (pdeThisDE->snLastSectorFile == NULL_BUF)
                {
                  lbLastLBN =     pdeThisDE->lbFileLoc +
                          pdeThisDE->clbXARLen +
                          (   (   pdeThisDE->cbFileSize +
                              cbLBSize - 1) /
                            cbLBSize);

                  pdeThisDE->snLastSectorFile =
                        lb_to_ls (  lbLastLBN,
                              cbLBSize) -
                        (   lb_offset ( lbLastLBN,
                                cbLBSize) ?
                          0 :
                          1);

                } /* end if need to compute last sector */

                /* Save the start sector of the first read then turn off flag */
                if (bFirstRead) {
                   snFirstSector = snThisSector;
                   bFirstRead = FALSE;
                } /* endif */

                /* Get the next sector */
                if ((usRet = FindBuf (  pvddVolData, TRUE,
                            snThisSector,
                            psffsi->sfi_hVPB,
                            (PPBUF) &pbBuf,
                            pdeThisDE->snLastSectorFile
#ifdef STATS_H
                            , DATA_SEC
#endif
                                )))

                {
                  /* Break out of the loop */
                  break;

                } /* end can't get next sector */

              } /* end if need to read sector */

              /*
               *  We are now ready to transfer the number of
               *  bytes into the user's area.  'pcData' should
               *  already point to the user's area.  We update
               *  the bytes for the extent, LB and sector.
               *  These are the bytes still to transfer from
               *  the current structure.  We also check here
               *  whether we area done.
               */

              /*
               *  We check the user's area now, we can't do it
               *  once at the beginning of this function since
               *  the buffer may become invalid after a YIELD.
               */
              if (usRet = FSH_PROBEBUF (  PB_OPWRITE, pcData,
                            cbThisBlock))
              {
                break;
              }

              /* Copy the bytes to the user's area */
              COPY_BYTES (pcData,
                    (PCHAR) OVERLAY_STRUCT (pbBuf,
                                cbSectorOffset),
                    0,
                    cbThisBlock);

              /* Update the bytes to transfer and the bytes transferred */
              cbToTransfer -= cbThisBlock;
              *pusLen += cbThisBlock;

              /* Move forward in the user's buffer */
              pcData += cbThisBlock;

              /* Modify our copy of the file position */
              cbCurrentPos += cbThisBlock;

              /* Check if done */
              if (!cbToTransfer)
              {
                RELEASE_BUF (pbBuf);

                break;

              } /* end if all done */

          FSH_YIELD();    /* ptr b728795 */

            } /* end infinite for loop */

            CDReadAhead (  pvddVolData,
                        psffsi->sfi_hVPB,
                        TRUE,
                        snFirstSector,
                        snThisSector,             /*really last sector read  */
                        pdeThisDE->snLastSectorFile, /*last sector in extent */
                        psffsi,
                        (PFILE_DEP_DATA)psffsd )    ;
          } /* end else at least one byte to consider */

/* Common point at end of loop */
FS_READ_End_For_Loop:

          /* Grab the file semaphore */
          SEM_REQ (((PFILE_DEP_DATA) psffsd)->sFileSem);

          /* Decrease the number of file readers */
          ((PFILE_DEP_DATA) psffsd)->usCountRead--;

          /* If no error, update the file information */
          if (!usRet)
          {
            /* Update the file position */
            psffsi->sfi_position = cbCurrentPos;

            /* Copy the new DE if different */
            if (fNewExtent)
            {
              pdeTempDE =
                ((PFILE_DEP_DATA) psffsd)->pdeLastDirRec;

              ((PFILE_DEP_DATA) psffsd)->pdeLastDirRec =
                pdeThisDE;

              pdeThisDE = pdeTempDE;
            }

          } /* end if no error, update */

          /* Give up the semaphore */
          SEM_CLEAR (((PFILE_DEP_DATA) psffsd)->sFileSem);

        } /* end if close has not started */

        /* Else return invalid access */
        else
        {
          usRet = ERROR_INVALID_ACCESS;

          SEM_CLEAR (((PFILE_DEP_DATA) psffsd)->sFileSem);

        }

        FREE_DIR_ENTRY (pdePrevDE);

      } /* end if successful DE allocation for PrevDE */

#ifdef DEBUG
      /* Else print error message */
      else
      {
        dprintf ("\tFS_READ:  GetDirEntry (PrevDE)  %d\n", usRet);
      }
#endif

      /* Give up this DE */
      FREE_DIR_ENTRY (pdeThisDE);

    } /* end if successful DE allocation for ThisDE */

#ifdef DEBUG
    /* Else print error message */
    else
    {
      dprintf ("\tFS_READ:  GetDirEntry (ThisDE)  %d\n", usRet);
    }
#endif

  } /* end if volume parameters obtained */

#ifdef DEBUG
  /* Else print error message */
  else
  {

    dprintf ("\tFS_READ:  GETVOLPARM  --  %d\n", usRet);
  }
#endif

#ifdef DEBUG
  dprintf ("    FS_READ: Exit = %d, Position=%ld, Bytes=%d\n",
       usRet, psffsi->sfi_position, *pusLen);
#endif

  return usRet;

  /* no warnings */
  IOflag;

}   /* end FS_READ */
