Added test-twirl to samples.

This commit is contained in:
Andy McFadden 2003-02-23 23:46:47 +00:00
parent 47e930c7dd
commit 16234a46d1
6 changed files with 715 additions and 4 deletions

View File

@ -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().

View File

@ -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__

View File

@ -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

View File

@ -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

View File

@ -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.

View File

@ -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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#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);
}