mirror of
https://github.com/fadden/nulib2.git
synced 2024-11-20 06:34:46 +00:00
1169554de3
instead of a "doClose" argument. NufxLib should no longer try to free anything allocated by the application (or vice-versa). The DataSource "copy" function now does refcounting instead of copying. This was done as part of cleaning up some memory leaks in the DataSource code. The samples were all updated with the changes to the API (and the occasional minor valgrind-inspired bug fix).
888 lines
22 KiB
C
888 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:
|
|
err = Nu_FWrite(pDataSink->toFile.fp, buf, len);
|
|
if (err != kNuErrNone)
|
|
return err;
|
|
break;
|
|
case kNuDataSinkToFP:
|
|
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;
|
|
}
|
|
|