CBuffFileStream/CBuffFileStream.c

/*
    File:       CBuffFileStream.c
 
    Contains:   Buffer handling.
 
    Written by: Tim Carroll
 
    Copyright:  Copyright (c) 1999 Apple Computer, Inc., All Rights Reserved.
 
                You may incorporate this Apple sample source code into your program(s) without
                restriction. This Apple sample source code has been provided "AS IS" and the
                responsibility for its operation is yours. You are not permitted to redistribute
                this Apple sample source code as "Apple sample source code" after having made
                changes. If you're going to re-distribute the source, we require that you make
                it clear in the source that the code was descended from Apple sample source
                code, but that you've made changes.
 
*/
 
#include <Errors.h>
#include <Files.h>
#include "FilePermUtils.h"
#include "CBuffFileStream.h"
 
// flush out the buffer we have.
// will write if it is a writing buffer
// otherwise just empty it
static OSErr    BFSFlushOutBuffer(CBuffFileStreamData *fdata);
 
// Check to see if we are changing from read buffering to write
// buffering or vice-versa. If so, flush the buffer.
static OSErr    BFSFlushOnModeSwap(CBuffFileStreamData *fdata,int nowMode);
 
// Trash the buffer. Nasty? Yes. Calling this make you lose data if you
// were in the middle of a buffered write. Don't call it if you don't know
// why you would.
static void     BFSTrashBuffer(CBuffFileStreamData *fdata,int nowMode);
 
// Fill the buffer from the current location in the file. This is used when
// the read buffer runs dry
static OSErr    BFSReadInBuffer(CBuffFileStreamData *fdata);
 
#define DOINGDEBUGCHECKING  0
#define STOPEACHONENTRY     0
 
#define BYPASSBUFFER        0
 
 
// ---------------------------------------------------------------------------
//      ¥ BFSOpenFIle(CBuffFileStreamData **newdata, FSSpec *inFileInfo, long inPrivileges)
// ---------------------------------------------------------------------------
//  Open a file from an FSSpec
//
//  This opens a file given the specified permissions (fsRdWrPerm, etc.)
//  and creates a file stream buffer record.
 
OSErr   BFSOpenFile(CBuffFileStreamData **newdata, FSSpec *inFileInfo, long inPrivileges)
{
    CBuffFileStreamData     *makeBlock;
    OSErr                   fileErr;
 
#if STOPEACHONENTRY
    Debugger();
#endif
 
    if (0 == *newdata)
        return paramErr;
 
#if DOINGDEBUGCHECKING
    if (0 != ((unsigned long) *newdata & 1))
        return paramErr;
#endif
 
    makeBlock = (CBuffFileStreamData *) NewPtrClear(sizeof(CBuffFileStreamData));
 
    if (0 == makeBlock)
        return MemError();
 
    fileErr = FSpOpenDF(inFileInfo,inPrivileges,&(makeBlock->mFileRefNum));
    if (noErr != fileErr)
    {
        (void) DisposePtr((Ptr) makeBlock);
        *newdata = 0;
 
        return fileErr;
    }
 
    fileErr = GetPermission(makeBlock->mFileRefNum,&(makeBlock->mFilePerms));
    if (noErr != fileErr)
    {
        (void) FSClose(makeBlock->mFileRefNum);
        (void) DisposePtr((Ptr) makeBlock);
        *newdata = 0;
 
        return fileErr;
    }
 
    *newdata = makeBlock;
 
    return noErr;
}
 
// ---------------------------------------------------------------------------
//      ¥ BFSCloseFile(CBuffFileStreamData *fdata)
// ---------------------------------------------------------------------------
//  Close an opened file
//
//  This closes an already opened buffered file.
//  It also deletes the storage associated with the file stream buffer record.
 
OSErr   BFSCloseFile(CBuffFileStreamData *fdata)
{
    OSErr           fileErr;
    OSErr           file2Err;
 
#if STOPEACHONENTRY
    Debugger();
#endif
 
#if DOINGDEBUGCHECKING
    if (0 != ((unsigned long) fdata & 0x1))
        return paramErr;
#endif
 
    if (0 == fdata)
        return paramErr;
 
    fileErr = BFSFlushOutBuffer(fdata);
 
    file2Err = FSClose(fdata->mFileRefNum);
    if (noErr == fileErr)
        fileErr = file2Err;
 
    return fileErr;
}
 
// ---------------------------------------------------------------------------
//      ¥ BFSSetMarker
// ---------------------------------------------------------------------------
//  Place the Read/Write Marker at an offset from a specified position
//
//  fromWhere can be fsAtMark (useless) or fsFromStart, fsFromLEOF, fsFromMark
 
OSErr   BFSSetMarker(CBuffFileStreamData *fdata, long inOffset, long fromWhere)
{
    OSErr           fileErr;
 
#if STOPEACHONENTRY
    Debugger();
#endif
 
#if DOINGDEBUGCHECKING
    if (0 != ((unsigned long) fdata & 0x1))
        return paramErr;
#endif
 
    if (0 == fdata)
        return paramErr;
 
    if (fromWhere < fsAtMark || fromWhere > fsFromMark)
        return paramErr;
 
#if BYPASSBUFFER
    fileErr = SetFPos(fdata->mFileRefNum,fromWhere,inOffset);   
#else
// Flush cache out
    fileErr = BFSFlushOutBuffer(fdata);
    if (noErr != fileErr)
        return fileErr;
 
// Now seek
    fileErr = SetFPos(fdata->mFileRefNum,fromWhere,inOffset);
 
// update where our window starts
    fdata->mWindowLocation = BFSGetMarker(fdata);
#endif
 
    return fileErr;
}
 
long    BFSGetMarker(CBuffFileStreamData *fdata)
{
#if DOINGDEBUGCHECKING
    OSErr           fileErr;
#endif
 
#if STOPEACHONENTRY
    Debugger();
#endif
 
#if DOINGDEBUGCHECKING
    if (0 != ((unsigned long) fdata & 0x1))
        return -1;
#endif
 
    if (0 == fdata)
        return -1;
 
#if BYPASSBUFFER
    {
        Int32           returnMarker;
 
        fileErr = GetFPos(fdata->mFileRefNum,&returnMarker);
        if (noErr != fileErr)
            return -1;
 
        return returnMarker;
    }
#else
#if DOINGDEBUGCHECKING
    {
        long            returnMarker;
 
        fileErr = GetFPos(fdata->mFileRefNum,&returnMarker);
        if (noErr != fileErr)
            return -1;
 
        if (returnMarker != fdata->mWindowLocation)
            DebugStr("\pmarker matching now working.");
    }
#endif
 
// rejigger marker
    return fdata->mWindowLocation + fdata->mBufferCurPos;
#endif
}
 
// ---------------------------------------------------------------------------
//      ¥ BFSSetLength
// ---------------------------------------------------------------------------
//  Set the length, in bytes, of the data fork a buffered file
 
OSErr   BFSSetLength(CBuffFileStreamData *fdata, long inLength)
{
#if STOPEACHONENTRY
    Debugger();
#endif
 
#if DOINGDEBUGCHECKING
    if (0 != ((unsigned long) fdata & 0x1))
        return -1;
#endif
 
    if (0 == fdata)
        return -1;
 
#if BYPASSBUFFER
    return SetEOF(fdata->mFileRefNum,inLength);
#else
    {
        OSErr               fileErr;
 
        fileErr = BFSFlushOutBuffer(fdata);
        if (noErr != fileErr)
            return fileErr;
 
    // If we get an error, continue so we keep an accurate marker
        fileErr = SetEOF(fdata->mFileRefNum,inLength);
 
        fdata->mWindowLocation = BFSGetMarker(fdata);
 
        return fileErr;
    }
#endif
}
 
// ---------------------------------------------------------------------------
//      ¥ BFSGetLength
// ---------------------------------------------------------------------------
//  Return the length, in bytes, of the data fork of a buffered file
 
long    BFSGetLength(CBuffFileStreamData *fdata)
{
    OSErr               fileErr;
 
#if STOPEACHONENTRY
    Debugger();
#endif
 
#if DOINGDEBUGCHECKING
    if (0 != ((unsigned long) fdata & 0x1))
        return -1;
#endif
 
    if (0 == fdata)
        return -1;
 
#if BYPASSBUFFER
    {
        Int32               returnLength;
 
    // Find file system length
        fileErr = GetEOF(fdata->mFileRefNum,&returnLength);
        if (noErr != fileErr)
            return -1;
 
        return returnLength;
    }
#else
    {
        long                returnLength;
        long                markerPos;
 
    // Find file system length
        fileErr = GetEOF(fdata->mFileRefNum,&returnLength);
        if (noErr != fileErr)
            return -1;
 
    // adjust length in case buffer passes current end of file
        markerPos = BFSGetMarker(fdata);
        if (-1 != markerPos && markerPos > returnLength)
            returnLength = markerPos;
 
        return returnLength;
    }
#endif
}
 
// ---------------------------------------------------------------------------
//      ¥ BFSWrite
// ---------------------------------------------------------------------------
//  Write data from a buffer to a buffered file stream
//
//  Returns an error code and passes back the number of bytes actually
//  written, which may be less than the number requested if an error occurred.
//  If no error is returned, the full amount was written.
 
OSErr   BFSWrite(CBuffFileStreamData *fdata, long *writeAmt, const void *inBuffer)
{
#if STOPEACHONENTRY
    Debugger();
#endif
 
#if DOINGDEBUGCHECKING
    if (0 != ((unsigned long) fdata & 0x1) || 0 != ((unsigned long) writeAmt & 0x1))
        return paramErr;
#endif
 
    if (0 == fdata || 0 == writeAmt || *writeAmt < 0)
        return paramErr;
 
#if BYPASSBUFFER
    return FSWrite(fdata->mfRefNum,writeAmt,inBuffer);
#else
    {
        const void  *subBuffer = inBuffer;
        long        remainByteCount = *writeAmt;
        OSErr       modeErr;
        long        freeBytes;
 
    //
    // Road map: here's what we are doing
    //
    // 1st: Check to see if we have write permissions
    // 2nd: Is our data less the remaining section of the buffer plus an empty buffer?
    //     Yes: 3rd: put as much data as you can in the current buffer
    //          4th: flush the buffer if full
    //          5th: put the rest of the data in the buffer
    //      No: 3rd: Is there data already in the buffer?
    //              Yes: 4th: put as much data as you can in the current buffer
    //                   5th: flush the buffer
    //               No: 6th: bypass the buffer to write the rest
    //
 
 
    // We only allow writing to the end of the buffer.
    // This is okay since any seeks flush the buffer and begin with a fresh one.
    // We keep mBufferCurPos in sync with mBufferLength though to make some
    // operations easier.
 
    // check for write permissions
        if (fsRdPerm == fdata->mFilePerms ||
            fsRdWrShPerm == fdata->mFilePerms)
        {
            return opWrErr;     // no write permissions
        }
 
    // Check to see if we changed modes from read to write
        modeErr = BFSFlushOnModeSwap(fdata,eBuffFileStreamIamWriting);
        if (noErr != modeErr)
            return modeErr;
 
        freeBytes = eBuffFileStreamBufferMaxSize - fdata->mBufferLength;
 
    // Can we write by using the available space plus another buffer?
        if (remainByteCount < (freeBytes + eBuffFileStreamBufferMaxSize))
        {
            long                addBytes = freeBytes;
 
        // Take the lesser number of the available space or the bytes we have
            if (remainByteCount < addBytes)
                addBytes = remainByteCount;
 
        // Move in the data
            BlockMoveData(subBuffer,&(fdata->mFileWindow[fdata->mBufferLength]),addBytes);
 
        // Adjust the buffer pointer, source pointer, and bytes left to write
            fdata->mBufferLength += addBytes;
            fdata->mBufferCurPos += addBytes;
            subBuffer = (const void *) ((char *) subBuffer + addBytes);
            remainByteCount -= addBytes;
 
        // Now flush the buffer if it is full
            if (eBuffFileStreamBufferMaxSize == fdata->mBufferLength)
            {
                OSErr flushErr = BFSFlushOutBuffer(fdata);
 
                if (flushErr != noErr)
                    return flushErr;
            }
 
        // Now, do we have more data to write? If so, put it in the buffer
            if (remainByteCount > 0)
            {
            // Move in the data
                BlockMoveData(subBuffer,fdata->mFileWindow,remainByteCount);
 
            // Adjust the buffer pointer
                fdata->mBufferLength = remainByteCount;
                fdata->mBufferCurPos = remainByteCount;
            }
        }
        else
        {
        // We have so much data we are going to at least partially bypass the buffer.
        // First, if we have a partial buffer, we should put as much of our data as
        // possible into the buffer before we write the rest as a bypass.
        // If we don't have a partial buffer, then we would be turning one write
        // into two, and we don't want that, so skip this step and write it all
        // as a bypass.
            if (fdata->mBufferLength > 0)
            {
                OSErr flushErr;
 
            // Take as much data as we can
                BlockMoveData(subBuffer,&(fdata->mFileWindow[fdata->mBufferLength]),freeBytes);
 
            // Adjust the buffer pointer, source pointer, and bytes left to write
                fdata->mBufferLength = eBuffFileStreamBufferMaxSize;
                fdata->mBufferCurPos = eBuffFileStreamBufferMaxSize;
                subBuffer = (const void *) ((char *) subBuffer + freeBytes);
                remainByteCount -= freeBytes;
 
            // Now flush this full buffer
                flushErr = BFSFlushOutBuffer(fdata);
 
                if (flushErr != noErr)
                    return flushErr;
            }
 
        // Now we are going to write the rest as a bypass
            {
                long            writeCount = remainByteCount;
                OSErr           bypassErr = FSWrite(fdata->mFileRefNum,&writeCount,subBuffer);
 
                if (bypassErr)
                    return bypassErr;
            }
        }
 
    // We've written all we have, and we have a empty buffer (because it was
    // empty, or we flushed it), we are ready for further writes
    }
 
    *writeAmt = 0;
 
    return noErr;
#endif  // !BYPASSBUFFER
}
 
// ---------------------------------------------------------------------------
//      ¥ BFSRead
// ---------------------------------------------------------------------------
//  Read data from a buffered data stream to a buffer
//
//  Returns an error code and passes back the number of bytes actually
//  read, which may be less than the number requested if an error occurred.
//  If no error is returned, the full amount was read.
 
OSErr   BFSRead(CBuffFileStreamData *fdata, long *readAmt, void *outBuffer)
{
#if STOPEACHONENTRY
    Debugger();
#endif
 
#if DOINGDEBUGCHECKING
    if (0 != ((unsigned long) fdata & 0x1) || 0 != ((unsigned long) readAmt & 0x1))
        return paramErr;
#endif
 
    if (0 == fdata || 0 == readAmt || *readAmt < 0)
        return paramErr;
 
#if BYPASSBUFFER
    return FSRead(fdata->mfRefNum,writeAmt,inBuffer);
#else
    {
        void    *subBuffer          = outBuffer;
        long    remainByteCount     = *readAmt;
        long    gotBytes            = fdata->mBufferLength - fdata->mBufferCurPos;
 
    //
    // Road map: here's what we are doing
    //
    // 1st: Is our request less than the remaining data in the buffer plus another buffer?
    //     Yes: 2nd: get as much data as you can/need from the current buffer
    //          3rd: need more?
    //          Yes: 4th: refill buffer
    //               5th: read the rest of the data from the buffer (or eof)
    //      No: 2nd: Is there data already in the buffer?
    //          3rd: get what you can from the buffer
    //          4th: bypass to read the rest
    //
 
    // Check to see if we changed modes from write to read
        OSErr modeErr = BFSFlushOnModeSwap(fdata,eBuffFileStreamIamReading);
 
        if (noErr != modeErr)
        {
            *readAmt = 0;
            return modeErr;
        }
 
    // See if we could possibly satisfy the request from the current buffer contents
    // and another less than full buffer?
 
        if (*readAmt < (gotBytes + eBuffFileStreamBufferMaxSize))
        {
        // Yes, so we want to use up what we have, read a whole buffer and
        // use part of this. However, this is written as a while loop to handle
        // operations at the end of a file.
 
            while (remainByteCount > 0)
            {
                long            addBytes;
 
            // Is the buffer empty and we still need more?
                if (remainByteCount > 0 && fdata->mBufferCurPos == fdata->mBufferLength)
                {
                // Yes, refill it
                    OSErr           fillErr = BFSReadInBuffer(fdata);
 
                    if (noErr != fillErr)
                    {
                    //  Probably an eof, we ran out of data.
                        if (fillErr == eofErr)
                            *readAmt -= remainByteCount;
                        else
                            *readAmt = 0;
 
                        return fillErr;
                    }
                }
 
                addBytes = fdata->mBufferLength - fdata->mBufferCurPos;
 
            // Take the lesser number of the available data or the bytes we want
                if (remainByteCount < addBytes)
                    addBytes = remainByteCount;
 
            // Move in the data
                BlockMoveData(&(fdata->mFileWindow[fdata->mBufferCurPos]),subBuffer,addBytes);
 
            // Adjust location in buffer, destination pointer, and bytes left to read
                fdata->mBufferCurPos += addBytes;
                subBuffer = (void *) ((char *) subBuffer + addBytes);
                remainByteCount -= addBytes;
 
            // Is the buffer empty and we still need more?
                if (remainByteCount > 0 && fdata->mBufferCurPos == fdata->mBufferLength)
                {
                // Yes, refill it
                    OSErr       fillErr = BFSReadInBuffer(fdata);
 
                    if (fillErr)
                    {
                    //  Probably an eof, we ran out of data.
                        if (fillErr == eofErr)
                            *readAmt -= remainByteCount;
                        else
                            *readAmt = 0;
 
                        return fillErr;
                    }
                }
            }
        }
        else
        {
        // Fast-track big read, use data available in the buffer, then switch
        // to big dedicated reads.
            long            readAttempt;
            OSErr           bypassErr;
 
            if (gotBytes > 0)       // use the data already in the buffer
            {
            // Move in the data
                BlockMoveData(&(fdata->mFileWindow[fdata->mBufferCurPos]),subBuffer,gotBytes);
 
            // Adjust location in buffer, destination pointer, and bytes left to read
                fdata->mBufferCurPos += gotBytes;
                subBuffer = (void *) ((char *) subBuffer + gotBytes);
                remainByteCount -= gotBytes;
            }
 
            readAttempt = remainByteCount;
 
            bypassErr = FSRead(fdata->mFileRefNum,&readAttempt,subBuffer);
            if (bypassErr)
            {
                if (bypassErr == eofErr)
                {
                    remainByteCount -= readAttempt;
                    *readAmt -= remainByteCount;
 
                    return bypassErr;
                }
                *readAmt = 0;
                return bypassErr;
            }
 
            remainByteCount -= readAttempt;
        }
 
        *readAmt -= remainByteCount;
 
        return noErr;
    }
#endif
}
 
// ---------------------------------------------------------------------------
//      ¥ BFSGetFilePermissions
// ---------------------------------------------------------------------------
//  Find the permissions acquired when opening a buffered data file.
//
//  This is useful if you specified fsCurPerm on open and want to know what
//  permissions you got.
 
long    BFSGetFilePermissions(CBuffFileStreamData *fdata)
{
#if STOPEACHONENTRY
    SysBreak();
#endif
 
#if DOINGDEBUGCHECKING
    if (0 != ((unsigned long) fdata & 0x1))
        return paramErr;
#endif
 
    if (0 == fdata)
        return paramErr;
 
    return fdata->mFilePerms;
}
 
// ---------------------------------------------------------------------------
//      ¥ BFSFlushFile
// ---------------------------------------------------------------------------
//  Find the permissions acquired when opening a buffered data file.
//
//  This is useful if you specified fsCurPerm on open and want to know what
//  permissions you got.
 
OSErr   BFSFlushFile(CBuffFileStreamData *fdata)
{
#if STOPEACHONENTRY
    SysBreak();
#endif
 
#if DOINGDEBUGCHECKING
    if (0 != ((unsigned long) fdata & 0x1))
        return paramErr;
#endif
 
    if (0 == fdata)
        return paramErr;
 
#if BYPASSBUFFER
    return noErr;
#else
    {
        OSErr           flushErr;
 
        flushErr = BFSFlushOutBuffer(fdata);
 
    // You should do a MacOS flush file here.
    // I'm not going to.
 
        return flushErr;
    }
#endif
}
 
// flush out the buffer we have.
// will write if it is a writing buffer
// otherwise just empty it
// This is not an externally accessible function so it does NOT
// check its parameters.
OSErr   BFSFlushOutBuffer(CBuffFileStreamData *fdata)
{
#if BYPASSBUFFER
    return noErr;
#else
    OSErr           returnValue = noErr;
 
    if (eBuffFileStreamIamWriting == fdata->mBufferMode)
    {
        long            writeAmount = fdata->mBufferLength;
 
    // write the data and update the file position
        returnValue = FSWrite(fdata->mFileRefNum,&writeAmount,fdata->mFileWindow);
 
    // update kept file position
        fdata->mWindowLocation += fdata->mBufferCurPos;
        fdata->mBufferLength = 0;
        fdata->mBufferCurPos = 0;
    }
    else
    {
    // update kept file position
        fdata->mWindowLocation += fdata->mBufferCurPos;
 
        SetFPos(fdata->mFileRefNum,fsFromStart,fdata->mWindowLocation);
 
        fdata->mBufferLength = 0;
        fdata->mBufferCurPos = 0;
    }
 
    return returnValue;
#endif
}
 
// Check to see if we are changing from read buffering to write
// buffering or vice-versa. If so, flush the buffer.
// This is not an externally accessible function so it does NOT
// check its parameters.
OSErr   BFSFlushOnModeSwap(CBuffFileStreamData *fdata,int nowMode)
{
#if BYPASSBUFFER
    return noErr;
#else
    OSErr       flushErr = noErr;
 
    if ((fdata->mBufferLength > 0) && (fdata->mBufferMode != nowMode))
    {
        flushErr = BFSFlushOutBuffer(fdata);
 
        fdata->mBufferLength = 0;       // flush FOR SURE (sorry)
        fdata->mBufferCurPos = 0;
    }
 
// set buffer mode
    fdata->mBufferMode = nowMode;
 
    return flushErr;
#endif
}
 
// Trash the buffer. Nasty? Yes. Calling this make you lose data if you
// were in the middle of a buffered write. Don't call it if you don't know
// why you would.
// This is not an externally accessible function so it does NOT
// check its parameters.
void        BFSTrashBuffer(CBuffFileStreamData *fdata,int nowMode)
{
#if BYPASSBUFFER
#else
// delete the data in the buffer
    fdata->mBufferLength = 0;
    fdata->mBufferCurPos = 0;
 
// set the buffer mode
    fdata->mBufferMode = nowMode;
#endif
}
 
// Fill the buffer from the current location in the file. This is used when
// the read buffer runs dry
// This is not an externally accessible function so it does NOT
// check its parameters.
OSErr   BFSReadInBuffer(CBuffFileStreamData *fdata)
{
    OSErr           returnValue = noErr;
    long            readAmount = eBuffFileStreamBufferMaxSize;
 
#if BYPASSBUFFER
    return noErr;
#else
#if DOINGDEBUGCHECKING
    if (eBuffFileStreamIamReading != fdata->mBufferMode)
    {
        DebugStr("\pTried to fill the buffer in write mode!");
    }
#endif
    fdata->mWindowLocation += fdata->mBufferCurPos;     // jump down the file some
 
    returnValue = FSRead(fdata->mFileRefNum,&readAmount,fdata->mFileWindow);
 
    if (eofErr == returnValue && 0 != readAmount)
        returnValue = noErr;            // ignore under reads unless we run dry
 
    fdata->mBufferLength = readAmount;
    fdata->mBufferCurPos = 0;
 
    return returnValue;
#endif
}