ciderpress/nufxlib/samples/TestTwirl.c
2015-12-26 16:50:42 -08:00

710 lines
19 KiB
C

/*
* NuFX archive manipulation library
* Copyright (C) 2000-2007 by Andy McFadden, All Rights Reserved.
* This is free software; you can redistribute it and/or modify it under the
* terms of the BSD 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;
uint16_t* 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 == NULL)
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, NULL on failure.
*/
CRCList* GatherCRCs(NuArchive* pArchive)
{
NuError err = kNuErrNone;
const NuMasterHeader* pMasterHeader;
CRCList* pCRCList = NULL;
uint16_t* pEntries = NULL;
long recCount, maxCRCs;
long recIdx, crcIdx;
int i;
pCRCList = malloc(sizeof(*pCRCList));
if (pCRCList == NULL) {
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 == NULL) {
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 %u\n", recordIdx);
goto bail;
}
if (NuRecordGetNumThreads(pRecord) == 0) {
fprintf(stderr, "ERROR: not expecting empty record (%u)!\n",
recordIdx);
err = kNuErrGeneric;
goto bail;
}
int rsrcCrcIdx = -1;
for (i = 0; i < (int)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;
}
/*
* Ensure that the data fork CRC comes first. Otherwise
* we can fail if it gets rearranged. This is only a
* problem for GSHK-created archives that don't have
* threads for every fork, so "mask dataless" is create
* fake entries.
*
* The correct way to do this is to store a tuple
* { thread-kind, crc }, but that's more work.
*/
if (pThread->thThreadKind == kNuThreadKindRsrcFork) {
rsrcCrcIdx = crcIdx;
}
if (pThread->thThreadKind == kNuThreadKindDataFork &&
rsrcCrcIdx != -1)
{
/* this is the data fork, we've already seen the
resource fork; swap entries */
pEntries[crcIdx++] = pEntries[rsrcCrcIdx];
pEntries[rsrcCrcIdx] = pThread->thThreadCRC;
} else {
pEntries[crcIdx++] = pThread->thThreadCRC;
}
}
}
}
pCRCList->numEntries = crcIdx;
DumpCRCs(pCRCList);
bail:
if (err != kNuErrNone) {
FreeCRCs(pCRCList);
pCRCList = NULL;
}
return pCRCList;
}
/*
* Compare the current set of CRCs against our saved list. If any of
* the records or threads were deleted or rearranged, this will fail.
* I happen to think this is a *good* thing: if something is the least
* bit screwy, I want to know about it.
*
* Unfortunately, if we *deliberately* delete records, this can't
* help us with the survivors.
*
* Returns 0 on success, nonzero on failure.
*/
int CompareCRCs(NuArchive* pArchive, const CRCList* pOldCRCList)
{
CRCList* pNewCRCList = NULL;
int result = -1;
int badCrc = 0;
int i;
pNewCRCList = GatherCRCs(pArchive);
if (pNewCRCList == NULL) {
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->entries[i], pNewCRCList->entries[i]);
badCrc = 1;
}
}
if (!badCrc) {
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 = NULL;
NuDataSink* pDataSink = NULL;
uint8_t* buf = NULL;
if (pThread->actualThreadEOF == 0) {
buf = malloc(1);
if (buf == NULL) {
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 == NULL) {
fprintf(stderr, "ERROR: failed allocating %u 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 %u in '%s': %s\n",
pThread->threadIdx, pRecord->filenameMOR, NuStrError(err));
goto bail;
}
}
/*
* Delete the existing thread.
*/
err = NuDeleteThread(pArchive, pThread->threadIdx);
if (err != kNuErrNone) {
fprintf(stderr, "ERROR: unable to delete thread %u\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 = NULL;
/*
* Create replacement thread.
*/
err = NuAddThread(pArchive, pRecord->recordIdx, NuGetThreadID(pThread),
pDataSource, NULL);
if (err != kNuErrNone) {
fprintf(stderr, "ERROR: unable to add new thread ID=0x%08x (err=%d)\n",
NuGetThreadID(pThread), err);
goto bail;
}
pDataSource = NULL; /* 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 %u\n", recordIdx);
*pLen = 0;
err = NuGetRecord(pArchive, recordIdx, &pRecord);
if (err != kNuErrNone) {
fprintf(stderr, "ERROR: unable to get record %u (err=%d)\n",
recordIdx, err);
goto bail;
}
for (i = 0; i < (int)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 %u "
" in record %u (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 = NULL;
NuAttr countAttr;
long heldLen;
long idx;
err = NuSetValue(pArchive, kNuValueDataCompression, compression);
if (err != kNuErrNone) {
fprintf(stderr, "ERROR: unable to set compression to %u (err=%d)\n",
compression, err);
goto bail;
}
printf("Recompressing threads with compression type %u\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 == NULL) {
fprintf(stderr, "ERROR: malloc on %u indices failed\n", countAttr);
err = kNuErrGeneric;
goto bail;
}
for (idx = 0; idx < (int)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 < (int)countAttr; idx++) {
long recHeldLen;
err = RecompressRecord(pArchive, pIndices[idx], &recHeldLen);
if (err != kNuErrNone) {
fprintf(stderr, "ERROR: failed recompressing record %u (err=%d)\n",
pIndices[idx], err);
goto bail;
}
heldLen += recHeldLen;
if (heldLen > kMaxHeldLen) {
uint32_t 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 = NULL;
CRCList* pCRCList = NULL;
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 == NULL) {
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++)
{
uint32_t 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--)
{
uint32_t 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 != NULL) {
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 NULL on error.
*
* (Note "CopyFile()" exists under Win32.)
*/
FILE* MyCopyFile(const char* outFileName, FILE* srcfp)
{
char buf[24576];
FILE* outfp;
size_t count;
outfp = fopen(outFileName, kNuFileOpenWriteTrunc);
if (outfp == NULL) {
fprintf(stderr, "ERROR: unable to open '%s' (err=%d)\n", outFileName,
errno);
return NULL;
}
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 NULL;
}
}
if (ferror(srcfp)) {
fprintf(stderr, "ERROR: failed reading srcfp (err=%d)\n", errno);
fclose(outfp);
return NULL;
}
return outfp;
}
/*
* Let's get started.
*/
int main(int argc, char** argv)
{
int32_t major, minor, bug;
const char* pBuildDate;
FILE* srcfp = NULL;
FILE* infp = NULL;
int cc;
/* don't buffer output */
setvbuf(stdout, NULL, _IONBF, 0);
setvbuf(stderr, NULL, _IONBF, 0);
(void) NuGetVersion(&major, &minor, &bug, &pBuildDate, NULL);
printf("Using NuFX lib %d.%d.%d built on or after %s\n\n",
major, minor, bug, pBuildDate);
if (argc == 2) {
srcfp = fopen(argv[1], kNuFileOpenReadOnly);
if (srcfp == NULL) {
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 = MyCopyFile(kWorkFileName, srcfp);
if (infp == NULL) {
fprintf(stderr, "Copy failed, bailing.\n");
exit(1);
}
fclose(srcfp);
fclose(infp);
cc = TwirlArchive(kWorkFileName);
exit(cc != 0);
}