nulib2/nufxlib-0/SourceSink.c
2003-02-09 03:16:13 +00:00

890 lines
22 KiB
C

/*
* NuFX archive manipulation library
* Copyright (C) 2000-2003 by Andy McFadden, All Rights Reserved.
* This is free software; you can redistribute it and/or modify it under the
* terms of the GNU Library General Public License, see the file COPYING-LIB.
*
* Implementation of DataSource and DataSink objects.
*/
#include "NufxLibPriv.h"
/*
* ===========================================================================
* NuDataSource
* ===========================================================================
*/
/*
* Allocate a new DataSource structure.
*/
static NuError
Nu_DataSourceNew(NuDataSource** ppDataSource)
{
Assert(ppDataSource != nil);
*ppDataSource = Nu_Malloc(nil, sizeof(**ppDataSource));
if (*ppDataSource == nil)
return kNuErrMalloc;
(*ppDataSource)->sourceType = kNuDataSourceUnknown;
return kNuErrNone;
}
/*
* Make a copy of a DataSource. Actually just increments a reference count.
*
* What we *really* want to be doing is copying the structure (since we
* can't guarantee copy-on-write semantics for the fields without adding
* more stuff) and refcounting the underlying resource, so that "auto-free"
* semantics work out right.
*
* We're okay for now, since for the most part we only do work on one
* copy of each. (I wish I could remember why this copying thing was
* needed in the first place.) Buffer sources are a little scary since
* they include a "curOffset" value.
*
* Returns nil on error.
*/
NuDataSource*
Nu_DataSourceCopy(NuDataSource* pDataSource)
{
Assert(pDataSource->common.refCount >= 1);
pDataSource->common.refCount++;
return pDataSource;
#if 0 /* we used to copy them -- very bad idea */
NuDataSource* pNewDataSource;
Assert(pDataSource != nil);
if (Nu_DataSourceNew(&pNewDataSource) != kNuErrNone)
return nil;
Assert(pNewDataSource != nil);
/* this gets most of it */
memcpy(pNewDataSource, pDataSource, sizeof(*pNewDataSource));
/* copy anything we're sure to free up */
if (pDataSource->sourceType == kNuDataSourceFromFile) {
Assert(pDataSource->fromFile.fp == nil); /* does this matter? */
pNewDataSource->fromFile.pathname =
strdup(pDataSource->fromFile.pathname);
}
/* don't let the original free up the resources */
if (pDataSource->common.doClose) {
DBUG(("--- clearing doClose on source-copy of data source\n"));
pDataSource->common.doClose = false;
}
return pNewDataSource;
#endif
}
/*
* Free a data source structure, and any type-specific elements.
*/
NuError
Nu_DataSourceFree(NuDataSource* pDataSource)
{
if (pDataSource == nil)
return kNuErrNone;
Assert(pDataSource->common.refCount > 0);
if (pDataSource->common.refCount > 1) {
pDataSource->common.refCount--;
return kNuErrNone;
}
switch (pDataSource->sourceType) {
case kNuDataSourceFromFile:
Nu_Free(nil, pDataSource->fromFile.pathname);
if (pDataSource->fromFile.fp != nil) {
fclose(pDataSource->fromFile.fp);
pDataSource->fromFile.fp = nil;
}
break;
case kNuDataSourceFromFP:
if (pDataSource->fromFP.fcloseFunc != nil &&
pDataSource->fromFP.fp != nil)
{
(*pDataSource->fromFP.fcloseFunc)(nil, pDataSource->fromFP.fp);
pDataSource->fromFP.fp = nil;
}
break;
case kNuDataSourceFromBuffer:
if (pDataSource->fromBuffer.freeFunc != nil) {
(*pDataSource->fromBuffer.freeFunc)(nil,
(void*)pDataSource->fromBuffer.buffer);
pDataSource->fromBuffer.buffer = nil;
}
break;
case kNuDataSourceUnknown:
break;
default:
Assert(0);
return kNuErrInternal;
}
Nu_Free(nil, pDataSource);
return kNuErrNone;
}
/*
* Create a data source for an unopened file.
*/
NuError
Nu_DataSourceFile_New(NuThreadFormat threadFormat, ulong otherLen,
const char* pathname, Boolean isFromRsrcFork, NuDataSource** ppDataSource)
{
NuError err;
if (pathname == nil ||
!(isFromRsrcFork == true || isFromRsrcFork == false) ||
ppDataSource == nil)
{
return kNuErrInvalidArg;
}
err = Nu_DataSourceNew(ppDataSource);
BailErrorQuiet(err);
(*ppDataSource)->common.sourceType = kNuDataSourceFromFile;
(*ppDataSource)->common.threadFormat = threadFormat;
(*ppDataSource)->common.rawCrc = 0;
(*ppDataSource)->common.dataLen = 0; /* to be filled in later */
(*ppDataSource)->common.otherLen = otherLen;
(*ppDataSource)->common.refCount = 1;
(*ppDataSource)->fromFile.pathname = strdup(pathname);
(*ppDataSource)->fromFile.fromRsrcFork = isFromRsrcFork;
(*ppDataSource)->fromFile.fp = nil; /* to be filled in later */
bail:
return err;
}
/*
* Create a data source for an open file at a specific offset. The FILE*
* must be seekable.
*/
NuError
Nu_DataSourceFP_New(NuThreadFormat threadFormat, ulong otherLen,
FILE* fp, long offset, long length, NuCallback fcloseFunc,
NuDataSource** ppDataSource)
{
NuError err;
if (fp == nil || offset < 0 || length < 0 ||
ppDataSource == nil)
{
return kNuErrInvalidArg;
}
if (otherLen && otherLen < (ulong)length) {
DBUG(("--- rejecting FP len=%ld other=%ld\n", length, otherLen));
err = kNuErrPreSizeOverflow;
goto bail;
}
err = Nu_DataSourceNew(ppDataSource);
BailErrorQuiet(err);
(*ppDataSource)->common.sourceType = kNuDataSourceFromFP;
(*ppDataSource)->common.threadFormat = threadFormat;
(*ppDataSource)->common.rawCrc = 0;
(*ppDataSource)->common.dataLen = length;
(*ppDataSource)->common.otherLen = otherLen;
(*ppDataSource)->common.refCount = 1;
(*ppDataSource)->fromFP.fp = fp;
(*ppDataSource)->fromFP.offset = offset;
(*ppDataSource)->fromFP.fcloseFunc = fcloseFunc;
bail:
return err;
}
/*
* Create a data source for a buffer.
*
* We allow "buffer" to be nil so long as "offset" and "length" are also
* nil. This is useful for creating empty pre-sized buffers, such as
* blank comment fields.
*/
NuError
Nu_DataSourceBuffer_New(NuThreadFormat threadFormat, ulong otherLen,
const uchar* buffer, long offset, long length, NuCallback freeFunc,
NuDataSource** ppDataSource)
{
NuError err;
if (offset < 0 || length < 0 || ppDataSource == nil)
return kNuErrInvalidArg;
if (buffer == nil && (offset != 0 || length != 0))
return kNuErrInvalidArg;
if (buffer == nil) {
DBUG(("+++ zeroing freeFunc for empty-buffer DataSource\n"));
freeFunc = nil;
}
if (otherLen && otherLen < (ulong)length) {
DBUG(("--- rejecting buffer len=%ld other=%ld\n", length, otherLen));
err = kNuErrPreSizeOverflow;
goto bail;
}
err = Nu_DataSourceNew(ppDataSource);
BailErrorQuiet(err);
(*ppDataSource)->common.sourceType = kNuDataSourceFromBuffer;
(*ppDataSource)->common.threadFormat = threadFormat;
(*ppDataSource)->common.rawCrc = 0;
(*ppDataSource)->common.dataLen = length;
(*ppDataSource)->common.otherLen = otherLen;
(*ppDataSource)->common.refCount = 1;
(*ppDataSource)->fromBuffer.buffer = buffer;
(*ppDataSource)->fromBuffer.offset = offset;
(*ppDataSource)->fromBuffer.curOffset = offset;
(*ppDataSource)->fromBuffer.curDataLen = length;
(*ppDataSource)->fromBuffer.freeFunc = freeFunc;
bail:
return err;
}
/*
* Get the type of a NuDataSource.
*/
NuDataSourceType
Nu_DataSourceGetType(const NuDataSource* pDataSource)
{
Assert(pDataSource != nil);
return pDataSource->sourceType;
}
/*
* Get the threadFormat for a data source.
*/
NuThreadFormat
Nu_DataSourceGetThreadFormat(const NuDataSource* pDataSource)
{
Assert(pDataSource != nil);
return pDataSource->common.threadFormat;
}
/*
* Get "dataLen" from a dataSource.
*/
ulong
Nu_DataSourceGetDataLen(const NuDataSource* pDataSource)
{
Assert(pDataSource != nil);
if (pDataSource->sourceType == kNuDataSourceFromFile) {
/* dataLen can only be valid if file has been opened */
Assert(pDataSource->fromFile.fp != nil);
}
return pDataSource->common.dataLen;
}
/*
* Get "otherLen" from a dataSource.
*/
ulong
Nu_DataSourceGetOtherLen(const NuDataSource* pDataSource)
{
Assert(pDataSource != nil);
return pDataSource->common.otherLen;
}
/*
* Change the "otherLen" value.
*/
void
Nu_DataSourceSetOtherLen(NuDataSource* pDataSource, long otherLen)
{
Assert(pDataSource != nil && otherLen > 0);
pDataSource->common.otherLen = otherLen;
}
/*
* Get the "raw CRC" value.
*/
ushort
Nu_DataSourceGetRawCrc(const NuDataSource* pDataSource)
{
Assert(pDataSource != nil);
return pDataSource->common.rawCrc;
}
/*
* Set the "raw CRC" value. You would want to do this if the input was
* already-compressed data, and you wanted to propagate the thread CRC.
*/
void
Nu_DataSourceSetRawCrc(NuDataSource* pDataSource, ushort crc)
{
Assert(pDataSource != nil);
pDataSource->common.rawCrc = crc;
}
/*
* Prepare a data source for action.
*/
NuError
Nu_DataSourcePrepareInput(NuArchive* pArchive, NuDataSource* pDataSource)
{
NuError err = kNuErrNone;
FILE* fileFp = nil;
/*
* Doesn't apply to buffer sources.
*/
if (Nu_DataSourceGetType(pDataSource) == kNuDataSourceFromBuffer)
goto bail;
/*
* FP sources can be used several times, so we need to seek them
* to the correct offset before we begin.
*/
if (Nu_DataSourceGetType(pDataSource) == kNuDataSourceFromFP) {
err = Nu_FSeek(pDataSource->fromFP.fp, pDataSource->fromFP.offset,
SEEK_SET);
goto bail; /* return this err */
}
/*
* We're adding from a file on disk. Open it.
*/
err = Nu_OpenInputFile(pArchive,
pDataSource->fromFile.pathname, pDataSource->fromFile.fromRsrcFork,
&fileFp);
BailError(err);
Assert(fileFp != nil);
pDataSource->fromFile.fp = fileFp;
err = Nu_GetFileLength(pArchive, fileFp,
(long*)&pDataSource->common.dataLen);
BailError(err);
if (pDataSource->common.otherLen &&
pDataSource->common.otherLen < pDataSource->common.dataLen)
{
DBUG(("--- Uh oh, looks like file len is too small for presized\n"));
}
bail:
return err;
}
/*
* Un-prepare a data source. This really only affects "file" sources, and
* is only here so we don't end up with 200+ FILE* structures hanging around.
* If we don't do this, the first resource we're likely to run out of is
* file descriptors.
*
* It's not necessary to do this in all error cases -- the DataSource "Free"
* call will take care of this eventually -- but for normal operation on
* a large number of files, it's vital.
*/
void
Nu_DataSourceUnPrepareInput(NuArchive* pArchive, NuDataSource* pDataSource)
{
if (Nu_DataSourceGetType(pDataSource) != kNuDataSourceFromFile)
return;
if (pDataSource->fromFile.fp != nil) {
fclose(pDataSource->fromFile.fp);
pDataSource->fromFile.fp = nil;
pDataSource->common.dataLen = 0;
}
}
/*
* Get the pathname from a "from-file" dataSource.
*/
const char*
Nu_DataSourceFile_GetPathname(NuDataSource* pDataSource)
{
Assert(pDataSource != nil);
Assert(pDataSource->sourceType == kNuDataSourceFromFile);
Assert(pDataSource->fromFile.pathname != nil);
return pDataSource->fromFile.pathname;
}
/*
* Read a block of data from a dataSource.
*/
NuError
Nu_DataSourceGetBlock(NuDataSource* pDataSource, uchar* buf, ulong len)
{
NuError err;
Assert(pDataSource != nil);
Assert(buf != nil);
Assert(len > 0);
switch (pDataSource->sourceType) {
case kNuDataSourceFromFile:
Assert(pDataSource->fromFile.fp != nil);
err = Nu_FRead(pDataSource->fromFile.fp, buf, len);
if (feof(pDataSource->fromFile.fp))
Nu_ReportError(NU_NILBLOB, err, "EOF hit unexpectedly");
return err;
case kNuDataSourceFromFP:
err = Nu_FRead(pDataSource->fromFP.fp, buf, len);
if (feof(pDataSource->fromFP.fp))
Nu_ReportError(NU_NILBLOB, err, "EOF hit unexpectedly");
return err;
case kNuDataSourceFromBuffer:
if ((long)len > pDataSource->fromBuffer.curDataLen) {
/* buffer underrun */
return kNuErrBufferUnderrun;
}
memcpy(buf,
pDataSource->fromBuffer.buffer + pDataSource->fromBuffer.curOffset,
len);
pDataSource->fromBuffer.curOffset += len;
pDataSource->fromBuffer.curDataLen -= len;
return kNuErrNone;
default:
Assert(false);
return kNuErrInternal;
}
}
/*
* Rewind a data source to the start of its input.
*/
NuError
Nu_DataSourceRewind(NuDataSource* pDataSource)
{
NuError err;
Assert(pDataSource != nil);
switch (pDataSource->sourceType) {
case kNuDataSourceFromFile:
Assert(pDataSource->fromFile.fp != nil);
err = Nu_FSeek(pDataSource->fromFile.fp, 0, SEEK_SET);
break; /* fall through with error */
case kNuDataSourceFromFP:
err = Nu_FSeek(pDataSource->fromFP.fp, pDataSource->fromFP.offset,
SEEK_SET);
break; /* fall through with error */
case kNuDataSourceFromBuffer:
pDataSource->fromBuffer.curOffset = pDataSource->fromBuffer.offset;
pDataSource->fromBuffer.curDataLen = pDataSource->common.dataLen;
err = kNuErrNone;
break;
default:
Assert(false);
err = kNuErrInternal;
}
return err;
}
/*
* ===========================================================================
* NuDataSink
* ===========================================================================
*/
/*
* Allocate a new DataSink structure.
*/
static NuError
Nu_DataSinkNew(NuDataSink** ppDataSink)
{
Assert(ppDataSink != nil);
*ppDataSink = Nu_Malloc(nil, sizeof(**ppDataSink));
if (*ppDataSink == nil)
return kNuErrMalloc;
(*ppDataSink)->sinkType = kNuDataSinkUnknown;
return kNuErrNone;
}
/*
* Free a data sink structure, and any type-specific elements.
*/
NuError
Nu_DataSinkFree(NuDataSink* pDataSink)
{
if (pDataSink == nil)
return kNuErrNone;
switch (pDataSink->sinkType) {
case kNuDataSinkToFile:
Nu_DataSinkFile_Close(pDataSink);
Nu_Free(nil, pDataSink->toFile.pathname);
break;
case kNuDataSinkToFP:
break;
case kNuDataSinkToBuffer:
break;
case kNuDataSinkToVoid:
break;
case kNuDataSinkUnknown:
break;
default:
Assert(0);
return kNuErrInternal;
}
Nu_Free(nil, pDataSink);
return kNuErrNone;
}
/*
* Create a data sink for an unopened file.
*/
NuError
Nu_DataSinkFile_New(Boolean doExpand, NuValue convertEOL, const char* pathname,
char fssep, NuDataSink** ppDataSink)
{
NuError err;
if ((doExpand != true && doExpand != false) ||
(convertEOL != kNuConvertOff && convertEOL != kNuConvertOn &&
convertEOL != kNuConvertAuto) ||
pathname == nil ||
fssep == 0 ||
ppDataSink == nil)
{
return kNuErrInvalidArg;
}
err = Nu_DataSinkNew(ppDataSink);
BailErrorQuiet(err);
(*ppDataSink)->common.sinkType = kNuDataSinkToFile;
(*ppDataSink)->common.doExpand = doExpand;
if (doExpand)
(*ppDataSink)->common.convertEOL = convertEOL;
else
(*ppDataSink)->common.convertEOL = kNuConvertOff;
(*ppDataSink)->common.outCount = 0;
(*ppDataSink)->toFile.pathname = strdup(pathname);
(*ppDataSink)->toFile.fssep = fssep;
(*ppDataSink)->toFile.fp = nil;
bail:
return err;
}
/*
* Create a data sink based on a file pointer.
*/
NuError
Nu_DataSinkFP_New(Boolean doExpand, NuValue convertEOL, FILE* fp,
NuDataSink** ppDataSink)
{
NuError err;
if ((doExpand != true && doExpand != false) ||
(convertEOL != kNuConvertOff && convertEOL != kNuConvertOn &&
convertEOL != kNuConvertAuto) ||
fp == nil ||
ppDataSink == nil)
{
return kNuErrInvalidArg;
}
err = Nu_DataSinkNew(ppDataSink);
BailErrorQuiet(err);
(*ppDataSink)->common.sinkType = kNuDataSinkToFP;
(*ppDataSink)->common.doExpand = doExpand;
if (doExpand)
(*ppDataSink)->common.convertEOL = convertEOL;
else
(*ppDataSink)->common.convertEOL = kNuConvertOff;
(*ppDataSink)->common.outCount = 0;
(*ppDataSink)->toFP.fp = fp;
bail:
return err;
}
/*
* Create a data sink for a buffer in memory.
*/
NuError
Nu_DataSinkBuffer_New(Boolean doExpand, NuValue convertEOL, uchar* buffer,
ulong bufLen, NuDataSink** ppDataSink)
{
NuError err;
if ((doExpand != true && doExpand != false) ||
(convertEOL != kNuConvertOff && convertEOL != kNuConvertOn &&
convertEOL != kNuConvertAuto) ||
buffer == nil ||
bufLen == 0 ||
ppDataSink == nil)
{
return kNuErrInvalidArg;
}
err = Nu_DataSinkNew(ppDataSink);
BailErrorQuiet(err);
(*ppDataSink)->common.sinkType = kNuDataSinkToBuffer;
(*ppDataSink)->common.doExpand = doExpand;
if (doExpand)
(*ppDataSink)->common.convertEOL = convertEOL;
else
(*ppDataSink)->common.convertEOL = kNuConvertOff;
(*ppDataSink)->common.convertEOL = convertEOL;
(*ppDataSink)->common.outCount = 0;
(*ppDataSink)->toBuffer.buffer = buffer;
(*ppDataSink)->toBuffer.bufLen = bufLen;
(*ppDataSink)->toBuffer.stickyErr = kNuErrNone;
bail:
return err;
}
/*
* Create a data sink that goes nowhere.
*/
NuError
Nu_DataSinkVoid_New(Boolean doExpand, NuValue convertEOL,
NuDataSink** ppDataSink)
{
NuError err;
Assert(doExpand == true || doExpand == false);
Assert(ppDataSink != nil);
err = Nu_DataSinkNew(ppDataSink);
BailErrorQuiet(err);
(*ppDataSink)->common.sinkType = kNuDataSinkToVoid;
(*ppDataSink)->common.doExpand = doExpand;
(*ppDataSink)->common.convertEOL = convertEOL;
(*ppDataSink)->common.outCount = 0;
bail:
return err;
}
/*
* Get the type of a NuDataSink.
*/
NuDataSinkType
Nu_DataSinkGetType(const NuDataSink* pDataSink)
{
Assert(pDataSink != nil);
return pDataSink->sinkType;
}
/*
* Return the "doExpand" parameter from any kind of sink.
*/
Boolean
Nu_DataSinkGetDoExpand(const NuDataSink* pDataSink)
{
return pDataSink->common.doExpand;
}
/*
* Return the "convertEOL" parameter from any kind of sink.
*/
NuValue
Nu_DataSinkGetConvertEOL(const NuDataSink* pDataSink)
{
return pDataSink->common.convertEOL;
}
/*
* Return the #of bytes written to the sink.
*/
ulong
Nu_DataSinkGetOutCount(const NuDataSink* pDataSink)
{
return pDataSink->common.outCount;
}
/*
* Get "pathname" from a to-file sink.
*/
const char*
Nu_DataSinkFile_GetPathname(const NuDataSink* pDataSink)
{
Assert(pDataSink != nil);
Assert(pDataSink->sinkType == kNuDataSinkToFile);
return pDataSink->toFile.pathname;
}
/*
* Get "fssep" from a to-file sink.
*/
char
Nu_DataSinkFile_GetFssep(const NuDataSink* pDataSink)
{
Assert(pDataSink != nil);
Assert(pDataSink->sinkType == kNuDataSinkToFile);
return pDataSink->toFile.fssep;
}
/*
* Get the "fp" for a file sink.
*/
FILE*
Nu_DataSinkFile_GetFP(const NuDataSink* pDataSink)
{
Assert(pDataSink != nil);
Assert(pDataSink->sinkType == kNuDataSinkToFile);
return pDataSink->toFile.fp;
}
/*
* Set the "fp" for a file sink.
*/
void
Nu_DataSinkFile_SetFP(NuDataSink* pDataSink, FILE* fp)
{
Assert(pDataSink != nil);
Assert(pDataSink->sinkType == kNuDataSinkToFile);
pDataSink->toFile.fp = fp;
}
/*
* Close a to-file sink.
*/
void
Nu_DataSinkFile_Close(NuDataSink* pDataSink)
{
Assert(pDataSink != nil);
if (pDataSink->toFile.fp != nil) {
fclose(pDataSink->toFile.fp);
pDataSink->toFile.fp = nil;
}
}
/*
* Write a block of data to a DataSink.
*/
NuError
Nu_DataSinkPutBlock(NuDataSink* pDataSink, const uchar* buf, ulong len)
{
NuError err;
Assert(pDataSink != nil);
Assert(buf != nil);
Assert(len > 0);
switch (pDataSink->sinkType) {
case kNuDataSinkToFile:
Assert(pDataSink->toFile.fp != nil);
err = Nu_FWrite(pDataSink->toFile.fp, buf, len);
if (err != kNuErrNone)
return err;
break;
case kNuDataSinkToFP:
Assert(pDataSink->toFP.fp != nil);
err = Nu_FWrite(pDataSink->toFP.fp, buf, len);
if (err != kNuErrNone)
return err;
break;
case kNuDataSinkToBuffer:
if (len > pDataSink->toBuffer.bufLen) {
/* buffer overrun; set a "sticky" error, like FILE* does */
err = kNuErrBufferOverrun;
pDataSink->toBuffer.stickyErr = err;
return err;
}
memcpy(pDataSink->toBuffer.buffer, buf, len);
pDataSink->toBuffer.buffer += len;
pDataSink->toBuffer.bufLen -= len;
break;
case kNuDataSinkToVoid:
/* do nothing */
break;
default:
Assert(false);
return kNuErrInternal;
}
pDataSink->common.outCount += len;
return kNuErrNone;
}
/*
* Figure out if one of our earlier writes has failed.
*/
NuError
Nu_DataSinkGetError(NuDataSink* pDataSink)
{
NuError err = kNuErrNone;
Assert(pDataSink != nil);
switch (pDataSink->sinkType) {
case kNuDataSinkToFile:
if (ferror(pDataSink->toFile.fp))
err = kNuErrFileWrite;
break;
case kNuDataSinkToFP:
if (ferror(pDataSink->toFP.fp))
err = kNuErrFileWrite;
break;
case kNuDataSinkToBuffer:
err = pDataSink->toBuffer.stickyErr;
break;
case kNuDataSinkToVoid:
/* do nothing */
break;
default:
Assert(false);
err = kNuErrInternal;
break;
}
return err;
}