/************************************************************************/
/*                                                                      */
/*      FastFile -- Provide low-cost buffer-based file access           */
/*                                                                      */
/*      The typical file input/output facilities provided by            */
/*      the stdio library are more than adequate for the average        */
/*      user.  They are fast, flexible, reliable and coherent.          */
/*      However, while the services they provide do not cost            */
/*      much when measured relative to average applications, the        */
/*      costs do become substantial for applications that don't         */
/*      perform much processing yet strive to run very quickly.         */
/*                                                                      */
/*      FastFile is a module designed to provide extremely fast         */
/*      access to text-oriented files.  It works in partnership         */
/*      with the application to present the file in raw form            */
/*      in a memory-mapped buffer.  In addition, the module             */
/*      provides facilities for configuring and manipulating            */
/*      the memory immediately before and after the file buffer.        */
/*      This is because some of the biggest performance                 */
/*      improvements occur when the application can use markers         */
/*      and/or reserve memory such that it may ignore                   */
/*      edge-of-buffer issues while performing its work.                */
/*                                                                      */
/*      --------------------- File data --------------------            */
/*      | (1) | (2) |     ...                        | (n) |            */
/*      +-----+-----+                                +-----+            */
/*               v                                                      */
/*               +------------------+                                   */
/*                                  v                                   */
/*      <----- memory map ---------------------------------------->     */
/*                               | buffer |                             */
/*            | SpaceBeforeStart |        | SpaceAfterEnd |             */
/*                    | PreBytes |        | PostBytes |                 */
/*                                                                      */
/*      This version for Linux exploits mmap in order to reduce         */
/*      file handling costs, and is very similar to the way             */
/*      GNU Grep handles files.  A side-effect of this                  */
/*      optimisation is that the user must preserve the                 */
/*      trailing bytes of the buffer so that we do not need to          */
/*      re-read them here.                                              */
/*                                                                      */
/*      Copyright (C) 1995-2000 Grouse Software.  All rights reserved.  */
/*      Written for Grouse by behoffski (Brenton Hoff).                 */
/*                                                                      */
/*      Free software: no warranty; use anywhere is ok; spread the      */
/*      sources; note any mods; share variations and derivatives        */
/*      (including sending to behoffski@grouse.com.au).                 */
/*                                                                      */
/************************************************************************/


#include "ascii.h"
#include <compdef.h>
#include <errno.h>
#include "fastfile.h"
#include <memrchr.h>
#include <memory.h>
#include "platform.h"
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/times.h>
#include "tracery.h"
#include <unistd.h>

#include <sys/mman.h>
#include <sys/stat.h>

struct FastFile_FileContextStruct {
        /*Tracery control block for this object*/
        Tracery_ObjectInfo TraceInfo;

        /*stdio-related information*/
        INT FileNr;
        FILE *f;
        off_t BufPos;

        /*Buffer pointer and data prefix size storage*/
        BYTE *pBuf;
        size_t PrefixSize;

        /*Options specified by client*/
        size_t BufferSizeMin;
        size_t BufSize;
        LWORD Flags;

        /*Configuration of bytes before buffer*/
        size_t PreBytes;
        UINT PreSize;
        BYTE *pPreData;

        /*Configuration of bytes after buffer*/
        size_t SpaceAfterEnd;
        UINT PostSize;
        BYTE *pPostData;
        BYTE *pPostSave;

        /*Feedover bytes (from incomplete line at end of buffer)*/
        size_t FeedoverSize;
        BYTE *pFeedover;

        /*Additional information needed if using mmap*/
        struct stat FileStats;
        BOOL MayMap;

};

typedef struct {
        /*Tracery control block for this module*/
        Tracery_ObjectInfo TraceInfo;

        /*Operating system function to read bytes from a file*/
        FastFile_ReadFn *pReadFn;

        /*Buffer size suggestion provided when module started*/
        size_t BufferSizeSuggested;

        UINT32 PageSize;

} FASTFILE_MODULE_CONTEXT;

module_scope FASTFILE_MODULE_CONTEXT gFastFile;

/*There's no portable vfree at this stage*/

#define vfree_if_available(valloced_pointer)


/*Extra information for Tracery operation*/

#ifdef TRACERY_ENABLED

#define TRACERY_MODULE_INFO             (gFastFile.TraceInfo)

/*Debugging/tracing flags*/

#define FASTFILE_T_ENTRY                BIT0
#define FASTFILE_T_BUFFER               BIT1
#define FASTFILE_T_FEEDOVER             BIT2

module_scope Tracery_EditEntry gFastFile_TraceryEditDefs[] = {
        {"E", FASTFILE_T_ENTRY,    FASTFILE_T_ENTRY,    "Trace  entry"}, 
        {"e", FASTFILE_T_ENTRY,    0x00,                "Ignore entry"}, 
        {"B", FASTFILE_T_BUFFER,   FASTFILE_T_BUFFER,   "Trace  buffer"}, 
        {"b", FASTFILE_T_BUFFER,   0x00,                "Ignore buffer"}, 
        {"F", FASTFILE_T_FEEDOVER, FASTFILE_T_FEEDOVER, "Trace  feedover"}, 
        {"f", FASTFILE_T_FEEDOVER, 0x00,                "Ignore feedover"}, 
        TRACERY_EDIT_LIST_END
};

#endif /*TRACERY_ENABLED*/


/************************************************************************/
/*                                                                      */
/*      Init -- Prepare module for operation                            */
/*                                                                      */
/************************************************************************/
public_scope void
FastFile_Init(void)
{
        /*Use ANSI-compliant read function*/
        gFastFile.pReadFn = read;

        /*No tracing enabled by default*/
        TRACERY_CLEAR_ALL_FLAGS(&TRACERY_MODULE_INFO);

} /*Init*/


/************************************************************************/
/*                                                                      */
/*      Start -- Begin managing what has to be managed                  */
/*                                                                      */
/*      BufferSize names the default size (in bytes) to use for         */
/*      each file opened.  The client may choose a larger size          */
/*      for the file buffer when it is opened.                          */
/*                                                                      */
/************************************************************************/
BOOL
FastFile_Start(size_t BufferSize)
{
        TRACERY(FASTFILE_T_ENTRY, {
                printf("\nFastFile_Start()");
        });

        /*Get memory manager's page size so we may conform to mmap()*/
        gFastFile.PageSize = getpagesize();

        /*Did we receive a reasonable answer?*/
        if (gFastFile.PageSize == 0) {
                /*No, choose a page size somewhat arbitrarily*/
                gFastFile.PageSize = 1024;
        }

        TRACERY(FASTFILE_T_BUFFER, {
                printf("Page size: %lu\n", gFastFile.PageSize);
        });

        /*Remember buffer size suggested by caller*/
        gFastFile.BufferSizeSuggested = BufferSize;

        /*Started successfully*/
        return TRUE;

} /*Start*/


/************************************************************************/
/*                                                                      */
/*      Open -- Provide optimised access using specified file           */
/*                                                                      */
/*      Attempts to open the specified file for optimised access,       */
/*      and, if successful, returns a handle referencing the file.      */
/*      If the filename parameter is a NULL pointer, then standard      */
/*      input is used.                                                  */
/*                                                                      */
/*      BufferSizeMin names the minimum buffer size to use.  If         */
/*      size isn't important, specify 0 and FastFile will use           */
/*      a reasonable default.                                           */
/*                                                                      */
/*      Flags contains options, most notably Line mode selection.       */
/*      LineMode may be FALSE (Raw mode) or TRUE (Line mode).           */
/*      In Line mode, FastFile guarantees that (a) The start of         */
/*      each buffer is the start of a text line, and (b) The            */
/*      buffer ends at the end of a line (or, equivalently, at          */
/*      the end of the file).  The line terminator is LF.               */
/*      In Raw mode, the buffer does not have these properties:         */
/*      there isn't any relationship between the contents of the        */
/*      file and the portion of the file presented in each buffer.      */
/*                                                                      */
/*      The client may receive details of the file's stauus via         */
/*      the ppStat parameter if desired, or may specify NULL            */
/*      if not interested.  This parameter is provided so the           */
/*      application may minimise to an absurd degree the code           */
/*      execution times.  If we can't stat the file, any return         */
/*      stat pointer is set to NULL.                                    */
/*                                                                      */
/*      Returns FALSE if the file was not able to be opened.            */
/*                                                                      */
/************************************************************************/
BOOL
FastFile_Open(CHAR *pFilename, 
              size_t BufferSizeMin, 
              LWORD Flags, 
              struct stat *pStat, 
              FastFile_Context **ppHandle)
{
        FastFile_Context *pHandle;

        TRACERY(FASTFILE_T_ENTRY, {
                printf("\nFastFile_Open(%s, %u, %08lx, %p)", 
                       pFilename, BufferSizeMin, Flags, ppHandle);
        });

        /*Check arguments*/
        if (ppHandle == NULL) {
                /*gFastFile.Diagnosis = EARGS;*/
                return FALSE;
        }

        /*Destroy return value to reduce chances of being misunderstood*/
        *ppHandle = (FastFile_Context *) NIL;

        /*Obtain memory to use as file handle*/
        pHandle = (FastFile_Context *) Platform_SmallMalloc(sizeof(*pHandle));
        if (pHandle == NULL) {
                /*gFastFile.Diagnosis = EMEMORY;*/
                return FALSE;
        }

        /*Initialise memory to reduce indeterminism*/
        memset(pHandle, 0, sizeof(*pHandle));

        /*Round up buffer size if smaller than startup suggestion*/
        if (BufferSizeMin < gFastFile.BufferSizeSuggested) {
                BufferSizeMin = gFastFile.BufferSizeSuggested;
        }

        /*Does the client wish to use stdin?*/
        if (pFilename == NULL) {
                /*Yes, use stdin handle instead of opening another*/
                pHandle->f = stdin;

        } else {
                /*Attempt to open file specified by client*/
                pHandle->f = fopen(pFilename, "rb");
                if (pHandle->f == NULL) {
                        /*Open failed*/
                        Platform_SmallFree(pHandle);
                        /*gFastFile.Diagnosis = EOPEN;*/
                        return FALSE;
                }

        }

        pHandle->FileNr = fileno(pHandle->f);

        /*Remember buffer information but defer allocation til later*/
        pHandle->BufferSizeMin = BufferSizeMin;
        pHandle->BufSize       = BufferSizeMin;
        pHandle->pBuf          = NULL;
        pHandle->PrefixSize    = gFastFile.PageSize;
        while (pHandle->PrefixSize < (BufferSizeMin / 4)) {
                pHandle->PrefixSize *= 2;
        }

        /*Initialise file context*/
        pHandle->BufPos        = 0;
        pHandle->FeedoverSize  = 0;
        pHandle->pFeedover     = NULL;
        pHandle->Flags         = Flags;
        pHandle->PreSize       = 0;
        pHandle->pPreData      = NULL;
        pHandle->PostSize      = 0;
        pHandle->pPostData     = NULL;
        pHandle->pPostSave     = NULL;

        /*Obtain file stats so we can decide whether to use mem-mapped i/f*/
        pHandle->MayMap = FALSE;
        if (fstat(pHandle->FileNr, &pHandle->FileStats) == 0) {
                /*Obtained stats: Does client want to see stats as well?*/
                if (pStat != NULL) {
                        /*Yes, tell client where to find our structure*/
                        *pStat = pHandle->FileStats;
                }

                /*Is the file a regular file?*/
                if (S_ISREG(pHandle->FileStats.st_mode)) {
                        /*Yes, may try memory-mapped interface*/
                        pHandle->MayMap = TRUE;
                }
        }

        /*?? Warning: we don't handle error if can't stat file*/

        /*Report success to caller*/
        *ppHandle = pHandle;
        return TRUE;

} /*Open*/


/************************************************************************/
/*                                                                      */
/*      Reopen -- Close existing file and open new file for access      */
/*                                                                      */
/*      This function only exists to reduce the overall cost of         */
/*      file handling where multiple files each use the same            */
/*      buffer conditioning.  The new file receives the same            */
/*      configuration settings as the previous file.                    */
/*      Returns FALSE if the file was not able to be opened (the        */
/*      existing file may *not* be accessed again, but the handle       */
/*      may be reused in further Reopen calls).                         */
/*                                                                      */
/*      The client may receive details of the file's stauus via         */
/*      the pStat parameter if desired, or may specify NULL             */
/*      if not interested.  This parameter is provided so the           */
/*      application may minimise to an absurd degree the code           */
/*      execution times.  If we can't stat the file, any return         */
/*      stat pointer is set to NULL.                                    */
/*                                                                      */
/************************************************************************/
public_scope BOOL
FastFile_Reopen(FastFile_Context *pHandle, 
                CHAR *pFilename,
                struct stat *pStat)
{
        /*Does the client wish to use stdin?*/
        if (pFilename == NULL) {
                /*Yes, use stdin handle instead of opening another*/
                pHandle->f = stdin;

        } else {
                pHandle->f = freopen(pFilename, "rb", pHandle->f);
                if (pHandle->f == NULL) {
                        /*reopen failed*/
                        /*gFastFile.Diagnosis = EOPEN;*/
                        return FALSE;
                }

        }

        pHandle->FileNr = fileno(pHandle->f);

        /*Remember buffer information but defer allocation til later*/
        pHandle->BufSize = pHandle->BufferSizeMin;
        pHandle->PrefixSize  = gFastFile.PageSize;
        while (pHandle->PrefixSize < (pHandle->BufferSizeMin / 4)) {
                pHandle->PrefixSize *= 2;
        }

        /*Initialise file context*/
        pHandle->BufPos       = 0;
        pHandle->FeedoverSize = 0;
        pHandle->pFeedover    = NULL;

        /*Obtain file stats so we can decide whether to use mem-mapped i/f*/
        pHandle->MayMap = FALSE;
        if (fstat(pHandle->FileNr, &pHandle->FileStats) == 0) {
                /*Obtained stats: Does client want to see stats as well?*/
                if (pStat != NULL) {
                        /*Yes, tell client where to find our structure*/
                        *pStat = pHandle->FileStats;
                }

                /*Is the file a regular file?*/
                if (S_ISREG(pHandle->FileStats.st_mode)) {
                        /*Yes, may try memory-mapped interface*/
                        pHandle->MayMap = TRUE;
                }
        }

        /*?? Warning: we don't handle error if can't stat file*/

        /*Exchanged files successfully*/
        return TRUE;

} /*Reopen*/


/************************************************************************/
/*                                                                      */
/*      StartCondition -- Specify how to set up start of buffer         */
/*                                                                      */
/*      This function configures how FastFile sets up the start         */
/*      of each buffer before it is retrieved by the client.            */
/*      SpaceBeforeStart specifies the minimum bytes before the         */
/*      buffer start address that must be available for the client      */
/*      to use.  PreSize and pPreData describe values that              */
/*      will be written immediately preceding the buffer start          */
/*      (note this area overlaps SpaceBeforeStart).                     */
/*                                                                      */
/*      SpaceBeforeStart may be 0 if no memory reservation is           */
/*      required, and similarly PreSize may be 0 if no memory           */
/*      needs to be set.  The client must maintain the memory           */
/*      referenced by pPreData unchanged; if the data is changed,       */
/*      StartCondition should be called again.                          */
/*                                                                      */
/*      Currently this function may only be called after Open but       */
/*      before the first Read.  This restriction may be relaxed         */
/*      in the future.                                                  */
/*                                                                      */
/************************************************************************/
BOOL
FastFile_StartCondition(FastFile_Context *pHandle, 
                        UINT SpaceBeforeStart, 
                        UINT PreSize, 
                        BYTE *pPreData)
{
        TRACERY(FASTFILE_T_ENTRY, {
                printf("\nFastFile_StartCondition(%u, %u, %p)", 
                       SpaceBeforeStart, PreSize, pPreData);
        });

        /*Check arguments*/
        if ((PreSize != 0) && (pPreData == NULL)) {
                return FALSE;
        }

        /*Is SpaceBeforeStart smaller than PreSize?*/
        if (SpaceBeforeStart < PreSize) {
                /*Yes, increase it so we only need to think about one number*/
                SpaceBeforeStart = PreSize;
        }

        /*Okay, record new parameters*/
        pHandle->PreBytes = SpaceBeforeStart;
        pHandle->PreSize = PreSize;
        pHandle->pPreData = pPreData;

        return TRUE;

} /*StartCondition*/


/************************************************************************/
/*                                                                      */
/*      EndCondition -- Specify how to set up end of buffer             */
/*                                                                      */
/*      Specifies how FastFile will prepare the end of each             */
/*      buffer.  LineMode selects Raw mode or Line mode.                */
/*      SpaceAfterEnd specifies how many bytes after the last           */
/*      byte of file data are available for client to use.              */
/*      PostSize and pPostData specify bytes to write                   */
/*      immediately following the buffer (note overlap of this          */
/*      area with SpaceAfterEnd).  As with StartCondition, 0 may        */
/*      be specified for either SpaceAfterEnd and/or PostSize.          */
/*      The client must maintain the memory referenced by               */
/*      pPostData unchanged; if the data is changed, EndCondition       */
/*      should be called again.                                         */
/*                                                                      */
/*      Currently this function may only be called after Open but       */
/*      before the first Read.  This restriction may be relaxed         */
/*      in the future.                                                  */
/*                                                                      */
/************************************************************************/
BOOL
FastFile_EndCondition(FastFile_Context *pHandle, 
                        UINT SpaceAfterEnd, 
                        UINT PostSize, 
                        BYTE *pPostData)
{
        BYTE *pNewBuffer;

        TRACERY(FASTFILE_T_ENTRY, {
                printf("\nFastFile_EndCondition(%u, %u, %p", 
                       SpaceAfterEnd, PostSize, pPostData);
                printf(" [%02x %02x %02x...])", pPostData[0], 
                       pPostData[1], pPostData[2]);
        });

        /*Obtain memory to save buffer contents before overwriting*/
        pNewBuffer = realloc(pHandle->pPostSave, PostSize);
        if ((pNewBuffer == NULL) && (PostSize != 0)) {
                /*Sorry, unable to obtain memory for new buffer*/
                return FALSE;
        }

        /*Okay, record new parameters and report success*/
        pHandle->SpaceAfterEnd = SpaceAfterEnd;
        pHandle->PostSize      = PostSize;
        pHandle->pPostData     = pPostData;
        pHandle->pPostSave     = pNewBuffer;
        return TRUE;

} /*EndCondition*/


/************************************************************************/
/*                                                                      */
/*      Read -- Fetch next buffer of file (if any)                      */
/*                                                                      */
/*      This function retrieves a slab of file data and returns it      */
/*      in a contiguous memory space.                                   */
/*                                                                      */
/*      pBufferOffset receives the byte offset of the start of          */
/*      the buffer relative to the start of the file.  This allows      */
/*      the caller to work out the absolute offset of any byte          */
/*      in the buffer from the start of the file.  This parameter       */
/*      supercedes the Position function included in previous           */
/*      incarnations of this module.                                    */
/*                                                                      */
/*      Returns FALSE if unable to handle the request (usually          */
/*      because there isn't enough memory available).                   */
/*                                                                      */
/************************************************************************/
BOOL
FastFile_Read(FastFile_Context *pHandle, 
              BYTE **ppBuf, UINT32 *pBufferSize, 
              UINT32 *pBufferOffset)
{
        UINT NrChars;
        BYTE *pLastLF;
        BYTE *pLastByte;
        BYTE *pFirstByte;
        size_t AllocSize;
        BOOL UseMap;
        char *pMemBuf;
        BYTE *pDiscardedBuffer = NULL;

        TRACERY(FASTFILE_T_ENTRY, {
                printf("\nFastFile_Read()");
        });

        /*Did we write end-condition bytes over file data?*/
        if ((pHandle->FeedoverSize != 0) && (pHandle->PostSize != 0)) {
                /*Yes, reinstate the data at the end of the old buffer*/
                memcpy(pHandle->pFeedover, 
                       pHandle->pPostSave, 
                       pHandle->PostSize);

                TRACERY(FASTFILE_T_FEEDOVER, {
                        printf("\nReinstate %u end bytes at %p", 
                               pHandle->PostSize, 
                               pHandle->pFeedover);
                });

        }


GetBuffer:
        /*While prefix buffer is too small for precond bytes + feedover...*/
        while (pHandle->PrefixSize < (pHandle->PreBytes + 
                                      pHandle->FeedoverSize)) {
                /*... double the  prefix buffer size*/
                pHandle->PrefixSize *= 2;

                /*Have we overflowed the size variable?*/
                if (pHandle->PrefixSize == 0) {
                        /*Sorry, can't set up prefix properly*/
                        return FALSE;
                }

        }

        /*Ensure the file buffer is always 4 times larger than the prefix*/
        if (pHandle->PrefixSize != (pHandle->BufSize / 4)) {
                /*Yes, change (increase) the buffer size*/
                pHandle->BufSize = pHandle->PrefixSize * 4;
                if ((pHandle->BufSize / 4) != pHandle->PrefixSize) {
                        /*Sorry, unable to think about such large sizes*/
                        return FALSE;
                }

                /*Remember we're discarding the current buffer (if any)*/
                pDiscardedBuffer = pHandle->pBuf;
                pHandle->pBuf = NULL;

                TRACERY(FASTFILE_T_FEEDOVER, {
                        printf("\nLarge prefix, new buffer size: %u", 
                               pHandle->BufSize);
                });

        }

        /*Have we got a buffer to receive the file?*/
        if (pHandle->pBuf == NULL) {
                /*No, allocate one now, including prefix and post bytes*/
                AllocSize = pHandle->PrefixSize + pHandle->BufSize + 
                        pHandle->SpaceAfterEnd;

                /*Round up buffer size to be a multiple of page size*/
                AllocSize += gFastFile.PageSize - 1;
                AllocSize -= AllocSize % gFastFile.PageSize;

                pHandle->pBuf = (CHAR *) valloc(AllocSize);

                TRACERY(FASTFILE_T_BUFFER, {
                        printf("Valloc size:%u pBuf:%p\n", 
                               AllocSize, pHandle->pBuf);
                });

                /*Did we obtain memory to operate?*/
                if (pHandle->pBuf == NULL) {
                        /*Sorry, unable to obtain buffer*/
                        return FALSE;
                }

        }

        /*Determine actual start of buffer, counting bytes from prev buffer*/
        pFirstByte = pHandle->pBuf + pHandle->PrefixSize;
        *ppBuf = pFirstByte - pHandle->FeedoverSize;
        *pBufferOffset = pHandle->BufPos - pHandle->FeedoverSize;

        /*Are we feeding over bytes from the previous buffer?*/
        if (pHandle->FeedoverSize != 0) {
                /*Yes, move them now as otherwise we'd overwrite them*/
                memmove(pFirstByte - pHandle->FeedoverSize, 
                       pHandle->pFeedover, 
                       pHandle->FeedoverSize);

                /*Remember new position in case we need to move it again*/
                pHandle->pFeedover = pFirstByte - pHandle->FeedoverSize;

                TRACERY(FASTFILE_T_FEEDOVER, {
                        printf("\nFeedover data moved to %p: %u", 
                               pHandle->pFeedover, 
                               pHandle->FeedoverSize);
                });

        }

        /*Did we shift to a new buffer and still know about the old one?*/
        if (pDiscardedBuffer != NULL) {
                /*Yes, free it (now we've copied any feedover bytes from it)*/
                free(pDiscardedBuffer);
                pDiscardedBuffer = NULL;
        }

        /*Use map only if: (a) File is mappable,*/
        /*                 (b) File offset is on a page boundary, and*/
        /*                 (c) At least BufSize bytes remain to process*/
        UseMap = pHandle->MayMap;
        UseMap = UseMap && ((pHandle->BufPos % gFastFile.PageSize) == 0);
        UseMap = UseMap && (pHandle->FileStats.st_size > 
                                    (pHandle->BufPos + pHandle->BufSize));
        if (UseMap) {
                /*Okay, try to map file into memory buffer*/
                pMemBuf = mmap(pFirstByte, 
                               pHandle->BufSize, 
                               PROT_READ | PROT_WRITE, 
                               MAP_PRIVATE | MAP_FIXED, 
                               pHandle->FileNr, 
                               pHandle->BufPos);

                /*Did we succeed?*/
                if (pMemBuf != (char *) pFirstByte) {
                        /*No, but because we mapped at the wrong address?*/
                        UseMap = FALSE;
                        if (pMemBuf != MAP_FAILED) {
                                /*Yes, undo map so we may read*/
                                munmap(pMemBuf, pHandle->BufSize);
                        }
                }
                
                TRACERY(FASTFILE_T_BUFFER, {
                        printf("\nMap file at %p: %p", 
                               pFirstByte, pMemBuf);
                });

        }

        /*Did we use the memory-mapped interface?*/
        if (UseMap) {
                /*Yes, update buffer position*/
                pHandle->BufPos += pHandle->BufSize;
                NrChars = pHandle->BufSize;
                pLastByte = &pFirstByte[pHandle->BufSize - 1];

        } else {
                /*No, have we just abandoned the memory-mapped interface?*/
                if (pHandle->MayMap) {
                        /*Yes, issue file seek to sync normal file handing*/
                        lseek(pHandle->FileNr, pHandle->BufPos, SEEK_SET);
                        pHandle->MayMap = FALSE;

                }

                /*Read file using normal (ANSI standard) interface*/
                NrChars = gFastFile.pReadFn(pHandle->FileNr,
                                            pFirstByte, 
                                            pHandle->BufSize);
                pHandle->BufPos += NrChars;
                pLastByte = &pFirstByte[NrChars - 1];

        }

        /*Did we run out of file to read?*/
        if (NrChars == 0) {
                /*Yes, file finished*/
                *ppBuf = NIL;
                *pBufferSize = 0;
                pHandle->FeedoverSize = 0;
                pHandle->pFeedover = NULL;
                return TRUE;
        }

        /*Add feedover characters to buffer pointer and size*/
        NrChars += pHandle->FeedoverSize;
        pFirstByte -= pHandle->FeedoverSize;

        /*Do we have an incomplete last line (and client wants line mode)?*/
        if ((NrChars >= pHandle->BufSize) && 
                        ((pHandle->Flags & FASTFILE_P_MODE_MASK) == 
                         FASTFILE_P_MODE_LINE)) {
                /*Yes, use memrchr to search for last LF in buffer*/
                pLastLF = (CHAR *) memrchr(pLastByte, 
                                           LF, 
                                           pHandle->BufSize);

                /*Did we find any line break characters?*/
                if (pLastLF == NULL) {
                        /*No, increase buffer size and try again*/
                        pHandle->pBuf = NULL;
                        pHandle->BufSize *= 4;

                        TRACERY(FASTFILE_T_FEEDOVER, {
                                printf("\nNo LF, increase size");
                        });

                        goto GetBuffer;
                }

                /*Remember how many bytes are in first line of next buffer*/
                pHandle->FeedoverSize = (INT) (pLastByte - pLastLF);
                pHandle->pFeedover    = pLastLF + 1;

                TRACERY(FASTFILE_T_FEEDOVER, {
                        printf("\nFeedover size %u at %p", 
                               pHandle->FeedoverSize, 
                               pHandle->pFeedover);
                });

                /*Remove incomplete line from buffer*/
                NrChars -= pLastByte - pLastLF;
                pLastByte = pLastLF;

        } else {
                /*Buffer is unedited*/
                pHandle->FeedoverSize = 0;
                pHandle->pFeedover = NULL;

                TRACERY(FASTFILE_T_FEEDOVER, {
                        printf("\nNo feedover");
                });

        }

        /*Were we asked to perform any start-of-buffer conditioning?*/
        if (pHandle->PreSize != 0) {
                /*Yes, set bytes now*/
                memcpy(pFirstByte - pHandle->PreSize, 
                       pHandle->pPreData, 
                       pHandle->PreSize);

                /*?? Could optimise this copy if pFirstByte never varies*/
        }

        /*Were we asked to perform any end-of-buffer conditioning?*/
        if (pHandle->PostSize != 0) {
                /*Yes, are there bytes needing to be saved (partial line)?*/
                if (pHandle->FeedoverSize != 0) {
                        /*Yes, save read bytes before modifying*/
                        memcpy(pHandle->pPostSave, 
                               pHandle->pFeedover, 
                               pHandle->PostSize);

                        /*?? Could reduce copy if Save < PostSize*/
                }

                /*Write client's bytes to end of buffer*/
                memcpy(pLastByte + 1, 
                       pHandle->pPostData, 
                       pHandle->PostSize);

                /*?? Could possibly eliminate this copy in some cases*/
        }

        /*Report fetched characters to caller*/
        *pBufferSize = NrChars;
        return TRUE;

} /*Read*/


/************************************************************************/
/*                                                                      */
/*      ReadFunction -- Receive function to read bytes from a file      */
/*                                                                      */
/*      FastFile assumes that the ANSI read function is sufficient      */
/*      and correct to fetch file lines.  If this assumption is         */
/*      incorrect for some reason, the client may supply an             */
/*      alternative function via this interface.                        */
/*                                                                      */
/*      This interface was included when behoffski found that           */
/*      the operation of read for pipes was incorrect,                  */
/*      whereas _read worked correctly under TopSpeed C v3.10.          */
/*                                                                      */
/************************************************************************/
public_scope void
FastFile_ReadFunction(FastFile_ReadFn *pReadFn)
{
        /*Remember function in private variable*/
        gFastFile.pReadFn = pReadFn;

} /*ReadFunction*/


/************************************************************************/
/*                                                                      */
/*      Stats -- Report file stats (from fstat(2))                      */
/*                                                                      */
/************************************************************************/
public_scope void
FastFile_Stats(FastFile_Context *pHandle, struct stat **ppStats)
{
        /*Report stats structure obtained when we opened the file*/
        *ppStats = &pHandle->FileStats;

} /*Stats*/


#ifdef TRACERY_ENABLED
/************************************************************************/
/*                                                                      */
/*      TraceryLink -- Tell Tracery how to deal with us                 */
/*                                                                      */
/*      This procedure is used by Tracery to find out how to            */
/*      manipulate the trace flags for this module and/or object.       */
/*      The platform should be able to hand this routine to             */
/*      Tracery when setting up the system without needing to           */
/*      know too many details about how the traces are to be            */
/*      set up.                                                         */
/*                                                                      */
/*      This function may be used to get the flags for the              */
/*      module, or for any object created by the module.                */
/*      If the pObject parameter is NULL, the module information        */
/*      is returned; otherwise, the object's info is returned.          */
/*      Currently we report our flag register, our preferred            */
/*      set of default flags, and a list of edit specifiers and         */
/*      bits to edit in the flag register.  In the future this          */
/*      may change: Tracery is still rather tentative.                  */
/*                                                                      */
/************************************************************************/
public_scope BOOL
FastFile_TraceryLink(void *pObject, UINT Opcode, ...)
{
        Tracery_ObjectInfo **ppInfoBlock;
        LWORD *pDefaultFlags;
        Tracery_EditEntry **ppEditList;
        va_list ap;

        va_start(ap, Opcode);

        switch (Opcode) {
        case TRACERY_REGCMD_GET_INFO_BLOCK:
                /*Report module's block (we don't support objects as yet)*/
                ppInfoBlock  = va_arg(ap, Tracery_ObjectInfo **);
                *ppInfoBlock = &gFastFile.TraceInfo;
                break;

        case TRACERY_REGCMD_GET_DEFAULT_FLAGS:
                pDefaultFlags  = va_arg(ap, LWORD *);
                *pDefaultFlags = 
                        FASTFILE_T_ENTRY;
                break;

        case TRACERY_REGCMD_GET_EDIT_LIST:
                ppEditList  = va_arg(ap, Tracery_EditEntry **);
                *ppEditList = gFastFile_TraceryEditDefs;
                break;

        default:
                /*Unsupported opcode*/
                va_end(ap);
                return FALSE;

        }

        va_end(ap);
        return TRUE;

} /*TraceryLink*/
#endif /*TRACERY_ENABLED*/