nulib2/nufxlib/Bzip2.c
Andy McFadden e2088e64d3 Distinguish Unicode and Mac OS Roman strings
NufxLib has historically made no effort to distinguish between
the character set used for filenames on the local disk, and for
filenames stored within the archive.  Now all Unicode filename
strings use the UNICHAR type and have "UNI" in the name, and all
Mac OS Roman strings have "MOR" in the name.  (The naming
convention makes it obvious when you're assigning the wrong thing;
on Linux both formats are char*, so the compiler won't tell you
if you get it wrong.)

The distinction is necessary because filesystems generally support
Unicode these days, but on Windows you need to use a separate
set of wide-character file I/O functions.  (On Linux it all works
with "narrow" strings, and the UTF-8 encoding is interpreted by
applications.)  The character set used for NuFX archive filenames
is MOR, matching what GS/OS + HFS supported, and we want to be able
to convert back and forth between MOR and a Unicode representation.

This change updates the various character types and string names,
adds conversion functions, and updates NuLib2 for proper execution
on Linux.  It does not include the (probably extensive) changes
required for Windows UTF-16 support.  Instead, the conversion
functions are no-ops, which should result in NuLib2 for Windows
continuing to behave in the same slightly broken way.

This adds "test-names", which exercises Unicode filenames a bit.
It will not pass on Win32.

Also, tweaked the Linux makefiles to have explicit dependencies,
rather than empty space and an expectation that "makedepend" exists.

Also, minor source code cleanups.

While this probably doesn't affect binary compatibility -- it's
mainly a matter of naming and string interpretation -- there's
enough going on that it should be considered an API revision, so
this updates the version to 3.0.0.
2015-01-02 17:14:34 -08:00

297 lines
8.8 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.
*
* Support for the "bzip2" (BTW+Huffman) algorithm, via "libbz2".
*
* This compression format is totally unsupported on the Apple II. This
* is provided primarily for the benefit of Apple II emulators that want
* a better storage format for disk images than SHK+LZW or a ZIP file.
*
* This code was developed and tested with libz2 version 1.0.2. Visit
* http://sources.redhat.com/bzip2/ for more information.
*/
#include "NufxLibPriv.h"
#ifdef ENABLE_BZIP2
#include "bzlib.h"
#define kBZBlockSize 8 /* use 800K blocks */
#define kBZVerbosity 1 /* library verbosity level (0-4) */
/*
* Alloc and free functions provided to libbz2.
*/
static void* Nu_bzalloc(void* opaque, int items, int size)
{
return Nu_Malloc(opaque, items * size);
}
static void Nu_bzfree(void* opaque, void* address)
{
Nu_Free(opaque, address);
}
/*
* ===========================================================================
* Compression
* ===========================================================================
*/
/*
* Compress "srcLen" bytes from "pStraw" to "fp".
*/
NuError Nu_CompressBzip2(NuArchive* pArchive, NuStraw* pStraw, FILE* fp,
uint32_t srcLen, uint32_t* pDstLen, uint16_t* pCrc)
{
NuError err = kNuErrNone;
bz_stream bzstream;
int bzerr;
uint8_t* outbuf = NULL;
Assert(pArchive != NULL);
Assert(pStraw != NULL);
Assert(fp != NULL);
Assert(srcLen > 0);
Assert(pDstLen != NULL);
Assert(pCrc != NULL);
err = Nu_AllocCompressionBufferIFN(pArchive);
if (err != kNuErrNone)
return err;
/* allocate a similarly-sized buffer for the output */
outbuf = Nu_Malloc(pArchive, kNuGenCompBufSize);
BailAlloc(outbuf);
/*
* Initialize the bz2lib stream.
*/
bzstream.bzalloc = Nu_bzalloc;
bzstream.bzfree = Nu_bzfree;
bzstream.opaque = pArchive;
bzstream.next_in = NULL;
bzstream.avail_in = 0;
bzstream.next_out = outbuf;
bzstream.avail_out = kNuGenCompBufSize;
/* fourth arg is "workFactor"; set to zero for default (30) */
bzerr = BZ2_bzCompressInit(&bzstream, kBZBlockSize, kBZVerbosity, 0);
if (bzerr != BZ_OK) {
err = kNuErrInternal;
if (bzerr == BZ_CONFIG_ERROR) {
Nu_ReportError(NU_BLOB, err, "error configuring bz2lib");
} else {
Nu_ReportError(NU_BLOB, err,
"call to BZ2_bzCompressInit failed (bzerr=%d)", bzerr);
}
goto bail;
}
/*
* Loop while we have data.
*/
do {
uint32_t getSize;
int action;
/* should be able to read a full buffer every time */
if (bzstream.avail_in == 0 && srcLen) {
getSize = (srcLen > kNuGenCompBufSize) ? kNuGenCompBufSize : srcLen;
DBUG(("+++ reading %ld bytes\n", getSize));
err = Nu_StrawRead(pArchive, pStraw, pArchive->compBuf, getSize);
if (err != kNuErrNone) {
Nu_ReportError(NU_BLOB, err, "bzip2 read failed");
goto bz_bail;
}
srcLen -= getSize;
*pCrc = Nu_CalcCRC16(*pCrc, pArchive->compBuf, getSize);
bzstream.next_in = pArchive->compBuf;
bzstream.avail_in = getSize;
}
if (srcLen == 0)
action = BZ_FINISH; /* tell libbz2 that we're done */
else
action = BZ_RUN; /* more to come! */
bzerr = BZ2_bzCompress(&bzstream, action);
if (bzerr != BZ_RUN_OK && bzerr != BZ_FINISH_OK && bzerr != BZ_STREAM_END)
{
err = kNuErrInternal;
Nu_ReportError(NU_BLOB, err,
"libbz2 compress call failed (bzerr=%d)", bzerr);
goto bz_bail;
}
/* write when we're full or when we're done */
if (bzstream.avail_out == 0 ||
(bzerr == BZ_STREAM_END && bzstream.avail_out != kNuGenCompBufSize))
{
DBUG(("+++ writing %d bytes\n",
(uint8_t*)bzstream.next_out - outbuf));
err = Nu_FWrite(fp, outbuf, (uint8_t*)bzstream.next_out - outbuf);
if (err != kNuErrNone) {
Nu_ReportError(NU_BLOB, err, "fwrite failed in bzip2");
goto bz_bail;
}
bzstream.next_out = outbuf;
bzstream.avail_out = kNuGenCompBufSize;
}
} while (bzerr != BZ_STREAM_END);
*pDstLen = bzstream.total_out_lo32;
Assert(bzstream.total_out_hi32 == 0); /* no huge files for us */
bz_bail:
BZ2_bzCompressEnd(&bzstream); /* free up any allocated structures */
bail:
if (outbuf != NULL)
Nu_Free(NULL, outbuf);
return err;
}
/*
* ===========================================================================
* Expansion
* ===========================================================================
*/
/*
* Expand from "infp" to "pFunnel".
*/
NuError Nu_ExpandBzip2(NuArchive* pArchive, const NuRecord* pRecord,
const NuThread* pThread, FILE* infp, NuFunnel* pFunnel, uint16_t* pCrc)
{
NuError err = kNuErrNone;
bz_stream bzstream;
int bzerr;
uint32_t compRemaining;
uint8_t* outbuf;
Assert(pArchive != NULL);
Assert(pThread != NULL);
Assert(infp != NULL);
Assert(pFunnel != NULL);
err = Nu_AllocCompressionBufferIFN(pArchive);
if (err != kNuErrNone)
return err;
/* allocate a similarly-sized buffer for the output */
outbuf = Nu_Malloc(pArchive, kNuGenCompBufSize);
BailAlloc(outbuf);
compRemaining = pThread->thCompThreadEOF;
/*
* Initialize the libbz2 stream.
*/
bzstream.bzalloc = Nu_bzalloc;
bzstream.bzfree = Nu_bzfree;
bzstream.opaque = pArchive;
bzstream.next_in = NULL;
bzstream.avail_in = 0;
bzstream.next_out = outbuf;
bzstream.avail_out = kNuGenCompBufSize;
/* third arg is "small" (set nonzero to reduce mem) */
bzerr = BZ2_bzDecompressInit(&bzstream, kBZVerbosity, 0);
if (bzerr != BZ_OK) {
err = kNuErrInternal;
if (bzerr == BZ_CONFIG_ERROR) {
Nu_ReportError(NU_BLOB, err, "error configuring libbz2");
} else {
Nu_ReportError(NU_BLOB, err,
"call to BZ2_bzDecompressInit failed (bzerr=%d)", bzerr);
}
goto bail;
}
/*
* Loop while we have data.
*/
do {
uint32_t getSize;
/* read as much as we can */
if (bzstream.avail_in == 0) {
getSize = (compRemaining > kNuGenCompBufSize) ?
kNuGenCompBufSize : compRemaining;
DBUG(("+++ reading %ld bytes (%ld left)\n", getSize,
compRemaining));
err = Nu_FRead(infp, pArchive->compBuf, getSize);
if (err != kNuErrNone) {
Nu_ReportError(NU_BLOB, err, "bzip2 read failed");
goto bz_bail;
}
compRemaining -= getSize;
bzstream.next_in = pArchive->compBuf;
bzstream.avail_in = getSize;
}
/* uncompress the data */
bzerr = BZ2_bzDecompress(&bzstream);
if (bzerr != BZ_OK && bzerr != BZ_STREAM_END) {
err = kNuErrInternal;
Nu_ReportError(NU_BLOB, err,
"libbz2 decompress call failed (bzerr=%d)", bzerr);
goto bz_bail;
}
/* write every time there's anything (buffer will usually be full) */
if (bzstream.avail_out != kNuGenCompBufSize) {
DBUG(("+++ writing %d bytes\n",
(uint8_t*) bzstream.next_out - outbuf));
err = Nu_FunnelWrite(pArchive, pFunnel, outbuf,
(uint8_t*)bzstream.next_out - outbuf);
if (err != kNuErrNone) {
Nu_ReportError(NU_BLOB, err, "write failed in bzip2");
goto bz_bail;
}
if (pCrc != NULL)
*pCrc = Nu_CalcCRC16(*pCrc, outbuf,
(uint8_t*) bzstream.next_out - outbuf);
bzstream.next_out = outbuf;
bzstream.avail_out = kNuGenCompBufSize;
}
} while (bzerr == BZ_OK);
Assert(bzerr == BZ_STREAM_END); /* other errors should've been caught */
Assert(bzstream.total_out_hi32 == 0); /* no huge files for us */
if (bzstream.total_out_lo32 != pThread->actualThreadEOF) {
err = kNuErrBadData;
Nu_ReportError(NU_BLOB, err,
"size mismatch on expanded bzip2 file (%d vs %ld)",
bzstream.total_out_lo32, pThread->actualThreadEOF);
goto bz_bail;
}
bz_bail:
BZ2_bzDecompressEnd(&bzstream); /* free up any allocated structures */
bail:
if (outbuf != NULL)
Nu_Free(NULL, outbuf);
return err;
}
#endif /*ENABLE_BZIP2*/