Retired Document
Important: This sample code may not represent best practices for current development. The project may use deprecated symbols and illustrate technologies and techniques that are no longer recommended.
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 |
} |
Copyright © 2003 Apple Computer, Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2003-01-14