From 16234a46d1c8b5be15cd58f86afa06771bb2e23e Mon Sep 17 00:00:00 2001 From: Andy McFadden Date: Sun, 23 Feb 2003 23:46:47 +0000 Subject: [PATCH] Added test-twirl to samples. --- nufxlib-0/ChangeLog.txt | 3 + nufxlib-0/samples/Common.h | 2 +- nufxlib-0/samples/Makefile.in | 10 +- nufxlib-0/samples/Makefile.msc | 5 + nufxlib-0/samples/README-S.txt | 12 + nufxlib-0/samples/TestTwirl.c | 687 +++++++++++++++++++++++++++++++++ 6 files changed, 715 insertions(+), 4 deletions(-) create mode 100644 nufxlib-0/samples/TestTwirl.c diff --git a/nufxlib-0/ChangeLog.txt b/nufxlib-0/ChangeLog.txt index 761ed1a..38f5585 100644 --- a/nufxlib-0/ChangeLog.txt +++ b/nufxlib-0/ChangeLog.txt @@ -1,3 +1,6 @@ +2003/02/23 fadden + - Added test-twirl to samples. + 2003/02/22 fadden - Turn off EOL conversion when extracting disk images. - Added NuTestRecord(). diff --git a/nufxlib-0/samples/Common.h b/nufxlib-0/samples/Common.h index 29f633d..c5c180e 100644 --- a/nufxlib-0/samples/Common.h +++ b/nufxlib-0/samples/Common.h @@ -4,7 +4,7 @@ * 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. * - * Common functions for NuLib tests + * Common functions for NuLib tests. */ #ifndef __Common__ #define __Common__ diff --git a/nufxlib-0/samples/Makefile.in b/nufxlib-0/samples/Makefile.in index 06472f4..a6d92cd 100644 --- a/nufxlib-0/samples/Makefile.in +++ b/nufxlib-0/samples/Makefile.in @@ -19,12 +19,13 @@ GCC_FLAGS = -Wall -Wwrite-strings -Wstrict-prototypes -Wpointer-arith -Wshadow CFLAGS = @BUILD_FLAGS@ -I. -I.. @DEFS@ #ALL_SRCS = $(wildcard *.c *.cpp) -ALL_SRCS = Exerciser.c Launder.c ImgConv.c TestBasic.c TestExtract.c \ - TestSimple.c +ALL_SRCS = Exerciser.c ImgConv.c Launder.c TestBasic.c \ + TestExtract.c TestSimple.c TestTwirl.c NUFXLIB = -L.. -lnufx -PRODUCTS = exerciser imgconv launder test-basic test-extract test-simple +PRODUCTS = exerciser imgconv launder test-basic test-extract test-simple \ + test-twirl #ifdef PURIFY_BUILD # PURIFY = purify @@ -64,6 +65,9 @@ test-simple: TestSimple.o $(LIB_PRODUCT) test-extract: TestExtract.o $(LIB_PRODUCT) $(PURIFY) $(QUANTIFY) $(CC) -o $@ TestExtract.o $(NUFXLIB) @LIBS@ +test-twirl: TestTwirl.o $(LIB_PRODUCT) + $(PURIFY) $(QUANTIFY) $(CC) -o $@ TestTwirl.o $(NUFXLIB) @LIBS@ + tags:: ctags --totals -R ../* @#ctags *.cpp ../*.c *.h ../*.h diff --git a/nufxlib-0/samples/Makefile.msc b/nufxlib-0/samples/Makefile.msc index f24d416..469e932 100644 --- a/nufxlib-0/samples/Makefile.msc +++ b/nufxlib-0/samples/Makefile.msc @@ -83,6 +83,9 @@ test-simple.exe: TestSimple.obj $(LIB_PRODUCT) test-extract.exe: TestExtract.obj $(LIB_PRODUCT) $(link) $(ldebug) TestExtract.obj -out:$@ $(NUFXSRCDIR)\nufxlib.lib $(LIB_FLAGS) +test-twirl.exe: TestTwirl.obj $(LIB_PRODUCT) + $(link) $(ldebug) TestTwirl.obj -out:$@ $(NUFXSRCDIR)\nufxlib.lib $(LIB_FLAGS) + clean: -del *.obj -del *.pdb @@ -94,6 +97,7 @@ clean: -del test-basic.exe -del test-simple.exe -del test-extract.exe + -del test-twirl.exe Exerciser.obj: Exerciser.c Common.h $(NUFXSRCDIR)\NufxLib.h $(NUFXSRCDIR)\SysDefs.h ImgConv.obj: ImgConv.c Common.h $(NUFXSRCDIR)\NufxLib.h $(NUFXSRCDIR)\SysDefs.h @@ -101,4 +105,5 @@ Launder.obj: Launder.c Common.h $(NUFXSRCDIR)\NufxLib.h $(NUFXSRCDIR)\SysDefs.h TestBasic.obj: TestBasic.c Common.h $(NUFXSRCDIR)\NufxLib.h $(NUFXSRCDIR)\SysDefs.h TestSimple.obj: TestSimple.c Common.h $(NUFXSRCDIR)\NufxLib.h $(NUFXSRCDIR)\SysDefs.h TestExtract.obj: TestExtract.c Common.h $(NUFXSRCDIR)\NufxLib.h $(NUFXSRCDIR)\SysDefs.h +TestTwirl.obj: TestTwirl.c Common.h $(NUFXSRCDIR)\NufxLib.h $(NUFXSRCDIR)\SysDefs.h diff --git a/nufxlib-0/samples/README-S.txt b/nufxlib-0/samples/README-S.txt index 10f5004..f18822a 100644 --- a/nufxlib-0/samples/README-S.txt +++ b/nufxlib-0/samples/README-S.txt @@ -96,3 +96,15 @@ Simple test program. Give it the name of an archive, and it will write all filename threads into "out.buf", "out.fp", and "out.file" using three different kinds of NuDataSinks. + +test-twirl +========== + +Like "launder", but not meant to be useful. This recompresses the file "in +place", deleting and adding threads within existing records several times. +The changes are periodically flushed, but the archive is never closed. +The goal is to test repeated updates on an open archive. + +This will leave a file called "TwirlCopy678" in the current directory, and +overwrite "TwirlTmp789" during processing. + diff --git a/nufxlib-0/samples/TestTwirl.c b/nufxlib-0/samples/TestTwirl.c new file mode 100644 index 0000000..c74ed0a --- /dev/null +++ b/nufxlib-0/samples/TestTwirl.c @@ -0,0 +1,687 @@ +/* + * 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. + * + * Recompress records in place, several times, possibly deleting records + * or threads as we go. The goal is to perform a large number of operations + * that modify the archive without closing and reopening it. + * + * Depending on which #defines are enabled, this can be very destructive, + * so a copy of the archive is made before processing begins. + */ +#include +#include +#include +#include +#include "NufxLib.h" +#include "Common.h" + +/* copy the archive to this file before starting */ +static const char* kWorkFileName = "TwirlCopy678"; +static const char* kTempFileName = "TwirlTmp789"; + +/* after loading this much stuff into memory, flush changes */ +const int kMaxHeldLen = 1024 * 1024; + +/* + * A list of CRCs. + */ +typedef struct CRCList { + int numEntries; + unsigned short* entries; +} CRCList; + + +/* + * Returns true if the compression type is supported, false otherwise. + */ +int +CompressionSupported(NuValue compression) +{ + int result; + + switch (compression) { + case kNuCompressNone: + result = true; + break; + case kNuCompressSQ: + result = (NuTestFeature(kNuFeatureCompressSQ) == kNuErrNone); + break; + case kNuCompressLZW1: + case kNuCompressLZW2: + result = (NuTestFeature(kNuFeatureCompressLZW) == kNuErrNone); + break; + case kNuCompressLZC12: + case kNuCompressLZC16: + result = (NuTestFeature(kNuFeatureCompressLZC) == kNuErrNone); + break; + case kNuCompressDeflate: + result = (NuTestFeature(kNuFeatureCompressDeflate) == kNuErrNone); + break; + case kNuCompressBzip2: + result = (NuTestFeature(kNuFeatureCompressBzip2) == kNuErrNone); + break; + default: + assert(false); + result = false; + } + + /*printf("Returning %d for %ld\n", result, compression);*/ + + return result; +} + +/* + * This gets called when a buffer DataSource is no longer needed. + */ +NuResult +FreeCallback(NuArchive* pArchive, void* args) +{ + free(args); + return kNuOK; +} + + +/* + * Dump a CRC list. + */ +void +DumpCRCs(const CRCList* pCRCList) +{ + int i; + + printf(" NumEntries: %d\n", pCRCList->numEntries); + + for (i = 0; i < pCRCList->numEntries; i++) + printf(" %5d: 0x%04x\n", i, pCRCList->entries[i]); +} + +/* + * Free a CRC list. + */ +void +FreeCRCs(CRCList* pCRCList) +{ + if (pCRCList == nil) + return; + + free(pCRCList->entries); + free(pCRCList); +} + +/* + * Gather a list of CRCs from the archive. + * + * We assume there are at most two data threads (e.g. data fork and rsrc + * fork) in a record. + * + * Returns the list on success, nil on failure. + */ +CRCList* +GatherCRCs(NuArchive* pArchive) +{ + NuError err = kNuErrNone; + const NuMasterHeader* pMasterHeader; + CRCList* pCRCList = nil; + unsigned short* pEntries = nil; + long recCount, maxCRCs; + long recIdx, crcIdx; + int i; + + pCRCList = malloc(sizeof(*pCRCList)); + if (pCRCList == nil) { + fprintf(stderr, "ERROR: couldn't alloc CRC list\n"); + err = kNuErrGeneric; + goto bail; + } + memset(pCRCList, 0, sizeof(*pCRCList)); + + /* get record count out of master header, just for fun */ + err = NuGetMasterHeader(pArchive, &pMasterHeader); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: couldn't get master header (err=%d)\n", err); + goto bail; + } + recCount = pMasterHeader->mhTotalRecords; + maxCRCs = recCount * 2; + + pEntries = malloc(sizeof(*pEntries) * maxCRCs); + if (pEntries == nil) { + fprintf(stderr, "ERROR: unable to alloc CRC list (%ld entries)\n", + maxCRCs); + err = kNuErrGeneric; + goto bail; + } + pCRCList->entries = pEntries; + + for (i = 0; i < maxCRCs; i++) + pEntries[i] = 0xdead; + + /* + * Enumerate our way through the records. If something was disturbed + * we should end up in a different place and the CRCs will be off. + */ + crcIdx = 0; + for (recIdx = 0; recIdx < recCount; recIdx++) { + NuRecordIdx recordIdx; + const NuRecord* pRecord; + const NuThread* pThread; + + err = NuGetRecordIdxByPosition(pArchive, recIdx, &recordIdx); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: couldn't get record #%ld (err=%d)\n", + recIdx, err); + goto bail; + } + + err = NuGetRecord(pArchive, recordIdx, &pRecord); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: unable to get recordIdx %ld\n", recordIdx); + goto bail; + } + + if (NuRecordGetNumThreads(pRecord) == 0) { + fprintf(stderr, "ERROR: not expecting empty record (%ld)!\n", + recordIdx); + err = kNuErrGeneric; + goto bail; + } + + for (i = 0; i < NuRecordGetNumThreads(pRecord); i++) { + pThread = NuGetThread(pRecord, i); + if (pThread->thThreadClass == kNuThreadClassData) { + if (crcIdx >= maxCRCs) { + fprintf(stderr, "ERROR: CRC buffer exceeded\n"); + assert(false); + err = kNuErrGeneric; + goto bail; + } + + pEntries[crcIdx++] = pThread->thThreadCRC; + } + } + } + + pCRCList->numEntries = crcIdx; + + DumpCRCs(pCRCList); + +bail: + if (err != kNuErrNone) { + FreeCRCs(pCRCList); + pCRCList = nil; + } + return pCRCList; +} + + +/* + * Compare the current set of CRCs against our saved list. + * + * Returns 0 on success, nonzero on failure. + */ +int +CompareCRCs(NuArchive* pArchive, const CRCList* pOldCRCList) +{ + CRCList* pNewCRCList = nil; + int result = -1; + int i; + + pNewCRCList = GatherCRCs(pArchive); + if (pNewCRCList == nil) { + fprintf(stderr, "ERROR: unable to gather new list\n"); + goto bail; + } + + if (pOldCRCList->numEntries != pNewCRCList->numEntries) { + fprintf(stderr, "ERROR: numEntries mismatch: %d vs %d\n", + pOldCRCList->numEntries, pNewCRCList->numEntries); + goto bail; + } + + for (i = 0; i < pNewCRCList->numEntries; i++) { + if (pOldCRCList->entries[i] != pNewCRCList->entries[i]) { + fprintf(stderr, "ERROR: CRC mismatch: %5d old=0x%04x new=0x%04x\n", + i, pOldCRCList->numEntries, pNewCRCList->numEntries); + goto bail; + } + } + printf(" Matched %d CRCs\n", pOldCRCList->numEntries); + + result = 0; + +bail: + FreeCRCs(pNewCRCList); + return result; +} + + +/* + * Recompress a single thread. + * + * This entails (1) extracting the existing thread, (2) deleting the + * thread, and (3) adding the extracted data. + * + * All of this good stuff gets queued up until the next NuFlush call. + */ +NuError +RecompressThread(NuArchive* pArchive, const NuRecord* pRecord, + const NuThread* pThread) +{ + NuError err = kNuErrNone; + NuDataSource* pDataSource = nil; + NuDataSink* pDataSink = nil; + unsigned char* buf = nil; + + if (pThread->actualThreadEOF == 0) { + buf = malloc(1); + if (buf == nil) { + fprintf(stderr, "ERROR: failed allocating trivial buffer\n"); + err = kNuErrGeneric; + goto bail; + } + } else { + /* + * Create a buffer and data sink to hold the data. + */ + buf = malloc(pThread->actualThreadEOF); + if (buf == nil) { + fprintf(stderr, "ERROR: failed allocating %ld bytes\n", + pThread->actualThreadEOF); + err = kNuErrGeneric; + goto bail; + } + + err = NuCreateDataSinkForBuffer(true, kNuConvertOff, buf, + pThread->actualThreadEOF, &pDataSink); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: unable to create data sink (err=%d)\n",err); + goto bail; + } + + /* + * Extract the data. + */ + err = NuExtractThread(pArchive, pThread->threadIdx, pDataSink); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: failed extracting thread %ld in '%s': %s\n", + pThread->threadIdx, pRecord->filename, NuStrError(err)); + goto bail; + } + } + + /* + * Delete the existing thread. + */ + err = NuDeleteThread(pArchive, pThread->threadIdx); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: unable to delete thread %ld\n", + pThread->threadIdx); + goto bail; + } + + /* + * Create a data source for the new thread. Specify a callback to free + * the buffer when NufxLib is done with it. + */ + err = NuCreateDataSourceForBuffer(kNuThreadFormatUncompressed, + 0, buf, 0, pThread->actualThreadEOF, FreeCallback, &pDataSource); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: unable to create data source (err=%d)\n", err); + goto bail; + } + buf = nil; + + /* + * Create replacement thread. + */ + err = NuAddThread(pArchive, pRecord->recordIdx, NuGetThreadID(pThread), + pDataSource, nil); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: unable to add new thread ID=0x%08lx (err=%d)\n", + NuGetThreadID(pThread), err); + goto bail; + } + pDataSource = nil; /* now owned by NufxLib */ + +bail: + NuFreeDataSink(pDataSink); + NuFreeDataSource(pDataSource); + free(buf); + return err; +} + +/* + * Recompress a single record. + * + * The amount of data we're holding in memory as a result of the + * recompression is placed in "*pLen". + */ +NuError +RecompressRecord(NuArchive* pArchive, NuRecordIdx recordIdx, long* pLen) +{ + NuError err = kNuErrNone; + const NuRecord* pRecord; + const NuThread* pThread; + int i; + + printf(" Recompressing %ld\n", recordIdx); + + *pLen = 0; + + err = NuGetRecord(pArchive, recordIdx, &pRecord); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: unable to get record %ld (err=%d)\n", + recordIdx, err); + goto bail; + } + + for (i = 0; i < NuRecordGetNumThreads(pRecord); i++) { + pThread = NuGetThread(pRecord, i); + if (pThread->thThreadClass == kNuThreadClassData) { + /*printf(" Recompressing %d (threadID=0x%08lx)\n", i, + NuGetThreadID(pThread));*/ + err = RecompressThread(pArchive, pRecord, pThread); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: failed recompressing thread %ld " + " in record %ld (err=%d)\n", + pThread->threadIdx, pRecord->recordIdx, err); + goto bail; + } + *pLen += pThread->actualThreadEOF; + } else { + /*printf(" Skipping %d (threadID=0x%08lx)\n", i, + NuGetThreadID(pThread));*/ + } + } + +bail: + return err; +} + +/* + * Recompress every data thread in the archive. + */ +NuError +RecompressArchive(NuArchive* pArchive, NuValue compression) +{ + NuError err = kNuErrNone; + NuRecordIdx* pIndices = nil; + NuAttr countAttr; + long heldLen; + long idx; + + err = NuSetValue(pArchive, kNuValueDataCompression, compression); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: unable to set compression to %ld (err=%d)\n", + compression, err); + goto bail; + } + + printf("Recompressing threads with compression type %ld\n", compression); + + err = NuGetAttr(pArchive, kNuAttrNumRecords, &countAttr); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: unable to get numRecords (err=%d)\n", err); + goto bail; + } + + if (countAttr == 0) { + printf("No records found!\n"); + goto bail; + } + + /* + * Get all of the indices up front. This way, if something causes a + * record to "disappear" during processing, we will know about it. + */ + pIndices = malloc(countAttr * sizeof(*pIndices)); + if (pIndices == nil) { + fprintf(stderr, "ERROR: malloc on %ld indices failed\n", countAttr); + err = kNuErrGeneric; + goto bail; + } + + for (idx = 0; idx < countAttr; idx++) { + err = NuGetRecordIdxByPosition(pArchive, idx, &pIndices[idx]); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: couldn't get record #%ld (err=%d)\n", + idx, err); + goto bail; + } + } + + /* + * Walk through the index list, handling each record individually. + */ + heldLen = 0; + for (idx = 0; idx < countAttr; idx++) { + long recHeldLen; + + err = RecompressRecord(pArchive, pIndices[idx], &recHeldLen); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: failed recompressing record %ld (err=%d)\n", + pIndices[idx], err); + goto bail; + } + + heldLen += recHeldLen; + + if (heldLen > kMaxHeldLen) { + long statusFlags; + + printf(" (flush)\n"); + err = NuFlush(pArchive, &statusFlags); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: intra-recompress flush failed: %s\n", + NuStrError(err)); + goto bail; + } + + heldLen = 0; + } + } + +bail: + free(pIndices); + return err; +} + +/* + * Initiate the twirling. + */ +int +TwirlArchive(const char* filename) +{ + NuError err = kNuErrNone; + NuArchive* pArchive = nil; + CRCList* pCRCList = nil; + int compression; + int cc; + + /* + * Open the archive after removing any temp file remnants. + */ + cc = unlink(kTempFileName); + if (cc == 0) + printf("Removed stale temp file '%s'\n", kTempFileName); + + err = NuOpenRW(filename, kTempFileName, 0, &pArchive); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: unable to open archive '%s': %s\n", + filename, NuStrError(err)); + goto bail; + } + + /* + * Mask records with no data threads, so we don't have to + * special-case them. + */ + err = NuSetValue(pArchive, kNuValueMaskDataless, true); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: couldn't mask dataless (err=%d)\n", err); + goto bail; + } + + pCRCList = GatherCRCs(pArchive); + if (pCRCList == nil) { + fprintf(stderr, "ERROR: unable to get CRC list\n"); + goto bail; + } + + /* + * For each type of compression, recompress the entire archive. + */ + for (compression = kNuCompressNone; compression <= kNuCompressBzip2; + compression++) + { + long statusFlags; + + if (!CompressionSupported(compression)) + continue; + + err = RecompressArchive(pArchive, compression); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: recompress failed: %s\n", NuStrError(err)); + goto bail; + } + + err = NuFlush(pArchive, &statusFlags); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: post-recompress flush failed: %s\n", + NuStrError(err)); + goto bail; + } + } + + /* + * Same thing, reverse order. We want to start with the same one we + * ended on above, so we can practice skipping over things. + */ + for (compression = kNuCompressBzip2; compression >= kNuCompressNone; + compression--) + { + long statusFlags; + + if (!CompressionSupported(compression)) + continue; + + err = RecompressArchive(pArchive, compression); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: recompress2 failed: %s\n", NuStrError(err)); + goto bail; + } + + err = NuFlush(pArchive, &statusFlags); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: post-recompress flush2 failed: %s\n", + NuStrError(err)); + goto bail; + } + } + + if (CompareCRCs(pArchive, pCRCList) != 0) { + fprintf(stderr, "ERROR: CRCs didn't match\n"); + goto bail; + } + + printf("Done!\n"); + +bail: + FreeCRCs(pCRCList); + if (pArchive != nil) { + NuAbort(pArchive); + NuClose(pArchive); + } + + return (err != kNuErrNone); +} + + +/* + * Copy from the current offset in "srcfp" to a new file called + * "outFileName". Returns a writable file descriptor for the new file + * on success, or nil on error. + */ +FILE* +CopyFile(const char* outFileName, FILE* srcfp) +{ + char buf[24576]; + FILE* outfp; + size_t count; + + outfp = fopen(outFileName, kNuFileOpenWriteTrunc); + if (outfp == nil) { + fprintf(stderr, "ERROR: unable to open '%s' (err=%d)\n", outFileName, + errno); + return nil; + } + + while (!feof(srcfp)) { + count = fread(buf, 1, sizeof(buf), srcfp); + if (count == 0) + break; + if (fwrite(buf, 1, count, outfp) != count) { + fprintf(stderr, "ERROR: failed writing outfp (err=%d)\n", errno); + fclose(outfp); + return nil; + } + } + + if (ferror(srcfp)) { + fprintf(stderr, "ERROR: failed reading srcfp (err=%d)\n", errno); + fclose(outfp); + return nil; + } + + return outfp; +} + +/* + * Let's get started. + */ +int +main(int argc, char** argv) +{ + long major, minor, bug; + const char* pBuildDate; + FILE* srcfp = nil; + FILE* infp = nil; + int cc; + + /* don't buffer output */ + setvbuf(stdout, nil, _IONBF, 0); + setvbuf(stderr, nil, _IONBF, 0); + + (void) NuGetVersion(&major, &minor, &bug, &pBuildDate, nil); + printf("Using NuFX lib %ld.%ld.%ld built on or after %s\n\n", + major, minor, bug, pBuildDate); + + if (argc == 2) { + srcfp = fopen(argv[1], kNuFileOpenReadOnly); + if (srcfp == nil) { + perror("fopen failed"); + exit(1); + } + } else { + fprintf(stderr, "ERROR: you have to specify a filename\n"); + exit(2); + } + + printf("Copying '%s' to '%s'\n", argv[1], kWorkFileName); + + infp = CopyFile(kWorkFileName, srcfp); + if (infp == nil) { + fprintf(stderr, "Copy failed, bailing.\n"); + exit(1); + } + fclose(srcfp); + fclose(infp); + + cc = TwirlArchive(kWorkFileName); + + exit(cc != 0); +} +