nulib2/nulib2/Binary2.c
Andy McFadden 132a8338b9 Fix Mac OS X behavior
Some fixes to the Mac OS X build:

- Replace the Carbon calls that were used to set the creator
and file type with xattr calls.  The Carbon stuff still worked
but caused deprecation warnings.  Stop linking against Carbon.

- Correct the way resource forks are accessed (from "/rsrc" to
"/..namedfork/rsrc").  The native resource fork support is
incomplete and doesn't work quite right, so it's now disabled.
(Which means the corrections to the file name don't actually do
anything, but you can at least play with it.)

- Correct the file/aux type conversion, which appeared to do
useful things but actually didn't in some circumstances (e.g. when
adding files, the code for acquiring the file types needs to be in
NuLib2, not NufxLib).

- Set creator and file type to 'pdos' values when extracting from
a Binary ][ archive.

Also, drop some old purify/quantify stuff.
2015-01-03 15:59:37 -08:00

1416 lines
41 KiB
C

/*
* NuLib2
* 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.
*
* Binary II support.
*
* The BNY format is significantly different from the NuFX format, so
* support wasn't included in NufxLib. Since there's no reason to
* create BNY archives when SHK is available, I've only implemented
* data extraction features.
*
* (Technically, BNY isn't an archive format. It was a way to encapsulate
* file attributes and file data for transmission over a modem, and was
* meant to be added and stripped on the fly.)
*
* TO DO: add "junk skipping" like NufxLib has.
*/
#include "NuLib2.h"
#ifdef HAVE_FCNTL_H
# include <fcntl.h>
#endif
#ifndef O_BINARY
# define O_BINARY 0
#endif
/* how to open a file */
#ifdef FOPEN_WANTS_B
# define kFileOpenReadOnly "rb"
#else
# define kFileOpenReadOnly "r"
#endif
/*
* ===========================================================================
* Utility functions
* ===========================================================================
*/
/*
* Open a file read-only. We abstract this so we get "r" vs "rb" right.
* (Moves this to SysUtils.c if anybody else needs it.)
*/
NuError OpenFileReadOnly(const char* filename, FILE** pFp)
{
NuError err = kNuErrNone;
*pFp = fopen(filename, kFileOpenReadOnly);
if (*pFp == NULL)
err = errno ? errno : kNuErrFileOpen;
return err;
}
/*
* Determine whether the current file we're examining is described by
* the file specification given on the command line.
*
* If no filespec was provided, then all records are "specified".
*/
Boolean NameIsSpecified(NulibState* pState, const char* filename)
{
char* const* pSpec;
int i;
if (!NState_GetFilespecCount(pState))
return true;
pSpec = NState_GetFilespecPointer(pState);
for (i = NState_GetFilespecCount(pState); i > 0; i--, pSpec++) {
#ifdef NU_CASE_SENSITIVE
if (NState_GetModRecurse(pState)) {
if (strncmp(*pSpec, filename, strlen(*pSpec)) == 0)
return true;
} else {
if (strcmp(*pSpec, filename) == 0)
return true;
}
#else
if (NState_GetModRecurse(pState)) {
if (strncasecmp(*pSpec, filename, strlen(*pSpec)) == 0)
return true;
} else {
if (strcasecmp(*pSpec, filename) == 0)
return true;
}
#endif
}
return false;
}
/*
* ===========================================================================
* Binary II goodies
* ===========================================================================
*/
/*
* State for the currently open Binary II archive.
*/
typedef struct BNYArchive {
NulibState* pState;
FILE* fp;
Boolean first;
} BNYArchive;
#define kBNYBlockSize 128 /* BNY files are broken into blocks */
#define kBNYMaxFileName 64
#define kBNYMaxNativeName 48
#define kBNYFlagCompressed (1<<7)
#define kBNYFlagEncrypted (1<<6)
#define kBNYFlagSparse (1)
/*
* An entry in a Binary II archive. Each archive is essentially a stream
* of files; only the "filesToFollow" value gives any indication that
* something else follows this entry.
*/
typedef struct BNYEntry {
uint16_t access;
uint16_t fileType;
uint32_t auxType;
uint8_t storageType;
uint32_t fileSize; /* in 512-byte blocks */
uint16_t prodosModDate;
uint16_t prodosModTime;
NuDateTime modWhen; /* computed from previous two fields */
uint16_t prodosCreateDate;
uint16_t prodosCreateTime;
NuDateTime createWhen; /* computed from previous two fields */
uint32_t eof;
uint32_t realEOF; /* eof is bogus for directories */
char fileName[kBNYMaxFileName+1]; /* ASCII only */
char nativeName[kBNYMaxNativeName+1];
uint32_t diskSpace; /* in 512-byte blocks */
uint8_t osType; /* not exactly same as NuFileSysID */
uint16_t nativeFileType;
uint8_t phantomFlag;
uint8_t dataFlags; /* advisory flags */
uint8_t version;
uint8_t filesToFollow; /* #of files after this one */
uint8_t blockBuf[kBNYBlockSize];
} BNYEntry;
/*
* Test for the magic number on a file in SQueezed format.
*/
static inline Boolean IsSqueezed(uint8_t one, uint8_t two)
{
return (one == 0x76 && two == 0xff);
}
/*
* Test if this entry is a directory.
*/
static inline Boolean IsDir(BNYEntry* pEntry)
{
/*
* NuLib and "unblu.c" compared against file type 15 (DIR), so I'm
* going to do that too, but it would probably be better to compare
* against storageType 0x0d.
*/
return (pEntry->fileType == 15);
}
/*
* Initialize a BNYArchive structure.
*/
static BNYArchive* BNYInit(NulibState* pState)
{
BNYArchive* pBny;
pBny = Malloc(sizeof(*pBny));
memset(pBny, 0, sizeof(*pBny));
pBny->pState = pState;
return pBny;
}
/*
* Free up a BNYArchive, disposing of anything inside it.
*/
static void BNYFree(BNYArchive* pBny)
{
/* don't need to do this on stdin, but won't really hurt */
if (pBny->fp != NULL)
fclose(pBny->fp);
Free(pBny);
}
/*
* Open a Binary II archive read-only. Might be a file on disk or a
* stream on stdin.
*/
static NuError BNYOpenReadOnly(BNYArchive* pBny)
{
NuError err = kNuErrNone;
NulibState* pState;
Assert(pBny != NULL);
Assert(pBny->pState != NULL);
Assert(pBny->fp == NULL);
pState = pBny->pState;
if (IsFilenameStdin(NState_GetArchiveFilename(pState))) {
pBny->fp = stdin;
/*
* Since the archive is on stdin, we can't ask the user questions.
* On a UNIX system we could open /dev/tty, but that's not portable,
* and I don't think archives on stdin are going to be popular
* enough to make this worth doing.
*/
NState_SetInputUnavailable(pState, true);
} else {
err = OpenFileReadOnly(NState_GetArchiveFilename(pState), &pBny->fp);
if (err != kNuErrNone) {
ReportError(err, "unable to open '%s'",
NState_GetArchiveFilename(pState));
goto bail;
}
}
bail:
return err;
}
/*
* Wrapper for fread(). Note the arguments resemble read(2) rather
* than fread(3S).
*/
static NuError BNYRead(BNYArchive* pBny, void* buf, size_t nbyte)
{
size_t result;
Assert(pBny != NULL);
Assert(buf != NULL);
Assert(nbyte > 0);
Assert(pBny->fp != NULL);
errno = 0;
result = fread(buf, 1, nbyte, pBny->fp);
if (result != nbyte)
return errno ? errno : kNuErrFileRead;
return kNuErrNone;
}
/*
* Seek within an archive. Because we need to handle streaming archives,
* and don't need to special-case anything, we only allow relative
* forward seeks.
*/
static NuError BNYSeek(BNYArchive* pBny, long offset)
{
Assert(pBny != NULL);
Assert(pBny->fp != NULL);
Assert(pBny->pState != NULL);
Assert(offset > 0);
/*DBUG(("--- seeking forward %ld bytes\n", offset));*/
if (IsFilenameStdin(NState_GetArchiveFilename(pBny->pState))) {
/* OPT: might be faster to fread a chunk at a time */
while (offset--)
(void) getc(pBny->fp);
if (ferror(pBny->fp) || feof(pBny->fp))
return kNuErrFileSeek;
} else {
if (fseek(pBny->fp, offset, SEEK_CUR) < 0)
return kNuErrFileSeek;
}
return kNuErrNone;
}
/*
* Convert from ProDOS compact date format to the expanded DateTime format.
*/
static void BNYConvertDateTime(uint16_t prodosDate, uint16_t prodosTime,
NuDateTime* pWhen)
{
pWhen->second = 0;
pWhen->minute = prodosTime & 0x3f;
pWhen->hour = (prodosTime >> 8) & 0x1f;
pWhen->day = (prodosDate & 0x1f) -1;
pWhen->month = ((prodosDate >> 5) & 0x0f) -1;
pWhen->year = (prodosDate >> 9) & 0x7f;
if (pWhen->year < 40)
pWhen->year += 100; /* P8 uses 0-39 for 2000-2039 */
pWhen->extra = 0;
pWhen->weekDay = 0;
}
/*
* Decode a Binary II header.
*
* See the File Type Note for $e0/8000 to decipher the buffer offsets
* and meanings.
*/
static NuError BNYDecodeHeader(BNYArchive* pBny, BNYEntry* pEntry)
{
NuError err = kNuErrNone;
uint8_t* raw;
int len;
Assert(pBny != NULL);
Assert(pEntry != NULL);
raw = pEntry->blockBuf;
if (raw[0] != 0x0a || raw[1] != 0x47 || raw[2] != 0x4c || raw[18] != 0x02) {
err = kNuErrBadData;
ReportError(err, "this doesn't look like a Binary II header");
goto bail;
}
pEntry->access = raw[3] | raw[111] << 8;
pEntry->fileType = raw[4] | raw[112] << 8;
pEntry->auxType = raw[5] | raw[6] << 8 | raw[109] << 16 | raw[110] << 24;
pEntry->storageType = raw[7];
pEntry->fileSize = raw[8] | raw[9] << 8;
pEntry->prodosModDate = raw[10] | raw[11] << 8;
pEntry->prodosModTime = raw[12] | raw[13] << 8;
BNYConvertDateTime(pEntry->prodosModDate, pEntry->prodosModTime,
&pEntry->modWhen);
pEntry->prodosCreateDate = raw[14] | raw[15] << 8;
pEntry->prodosCreateTime = raw[16] | raw[17] << 8;
BNYConvertDateTime(pEntry->prodosCreateDate, pEntry->prodosCreateTime,
&pEntry->createWhen);
pEntry->eof = raw[20] | raw[21] << 8 | raw[22] << 16 | raw[116] << 24;
len = raw[23];
if (len > kBNYMaxFileName) {
err = kNuErrBadData;
ReportError(err, "invalid filename length %d", len);
goto bail;
}
memcpy(pEntry->fileName, &raw[24], len);
pEntry->fileName[len] = '\0';
pEntry->nativeName[0] = '\0';
if (len <= 15 && raw[39] != 0) {
len = raw[39];
if (len > kBNYMaxNativeName) {
err = kNuErrBadData;
ReportError(err, "invalid filename length %d", len);
goto bail;
}
memcpy(pEntry->nativeName, &raw[40], len);
pEntry->nativeName[len] = '\0';
}
pEntry->diskSpace = raw[117] | raw[118] << 8 | raw[119] << 16 |
raw[120] << 24;
pEntry->osType = raw[121];
pEntry->nativeFileType = raw[122] | raw[123] << 8;
pEntry->phantomFlag = raw[124];
pEntry->dataFlags = raw[125];
pEntry->version = raw[126];
pEntry->filesToFollow = raw[127];
/* directories are given an EOF but don't actually have any content */
if (IsDir(pEntry))
pEntry->realEOF = 0;
else
pEntry->realEOF = pEntry->eof;
bail:
return err;
}
/*
* Normalize the pathname by running it through the usual NuLib2
* function. The trick here is that the function usually takes a
* NuPathnameProposal, which we don't happen to have handy. Rather
* than generalize the NuLib2 code, we just create a fake proposal,
* which is a bit dicey but shouldn't break too easily.
*
* This takes care of -e, -ee, and -j.
*
* We return the new path, which is stored in NulibState's temporary
* filename buffer.
*/
const char* BNYNormalizePath(BNYArchive* pBny, BNYEntry* pEntry)
{
NuPathnameProposal pathProposal;
NuRecord fakeRecord;
NuThread fakeThread;
/* make uninitialized data obvious */
memset(&fakeRecord, 0xa1, sizeof(fakeRecord));
memset(&fakeThread, 0xa5, sizeof(fakeThread));
pathProposal.pathnameUNI = pEntry->fileName;
pathProposal.filenameSeparator = '/'; /* BNY always uses ProDOS conv */
pathProposal.pRecord = &fakeRecord;
pathProposal.pThread = &fakeThread;
pathProposal.newPathnameUNI = NULL;
pathProposal.newFilenameSeparator = '\0';
pathProposal.newDataSink = NULL;
/* need the filetype and auxtype for -e/-ee */
fakeRecord.recFileType = pEntry->fileType;
fakeRecord.recExtraType = pEntry->auxType;
/* need the components of a ThreadID */
fakeThread.thThreadClass = kNuThreadClassData;
fakeThread.thThreadKind = 0x0000; /* data fork */
return NormalizePath(pBny->pState, &pathProposal);
}
/*
* Copy all data from the Binary II file to "outfp", reading in 128-byte
* blocks. We need to read multiples of 128 bytes so that we're ready to
* read the next entry without additional seeking.
*
* Some programs (like ProTERM) apply a 128-byte header without padding
* out the file length, so we may come up short at the end of the file.
*
* Uses pEntry->blockBuf, which already has the first 128 bytes in it.
*/
static NuError BNYCopyBlocks(BNYArchive* pBny, BNYEntry* pEntry, FILE* outfp)
{
NuError err = kNuErrNone;
long bytesLeft;
Assert(pEntry->realEOF > 0);
bytesLeft = pEntry->realEOF;
while (bytesLeft > 0) {
long toWrite;
toWrite = bytesLeft;
if (toWrite > kBNYBlockSize)
toWrite = kBNYBlockSize;
if (outfp != NULL) {
if (fwrite(pEntry->blockBuf, toWrite, 1, outfp) != 1) {
err = errno ? errno : kNuErrFileWrite;
ReportError(err, "BNY write failed");
goto bail;
}
}
bytesLeft -= toWrite;
if (bytesLeft) {
size_t actual;
errno = 0;
actual = fread(pEntry->blockBuf, 1, kBNYBlockSize, pBny->fp);
if (actual != kBNYBlockSize) {
/* failure or partial read */
if (actual != bytesLeft) {
err = errno ? errno : kNuErrFileRead;
ReportError(err, "BNY read failed");
goto bail;
} else {
DBUG(("+++ partial read %d/%d\n", actual, kBNYBlockSize));
}
}
}
}
bail:
return err;
}
/*
* ===========================================================================
* Unsqueeze
* ===========================================================================
*/
/*
* This was ripped fairly directly from Squeeze.c in NufxLib. Because
* there's relatively little code, and providing direct access to the
* compression functions is a little unwieldy, I've cut & pasted the
* necessary pieces here.
*/
#define FULL_SQ_HEADER
#define kSqBufferSize 8192 /* must be enough to hold full SQ header */
#define kNuSQMagic 0xff76 /* magic value for file header */
#define kNuSQRLEDelim 0x90 /* RLE delimiter */
#define kNuSQEOFToken 256 /* distinguished stop symbol */
#define kNuSQNumVals 257 /* 256 symbols + stop */
/*
* State during uncompression.
*/
typedef struct USQState {
uint32_t dataInBuffer;
uint8_t* dataPtr;
int bitPosn;
int bits;
/*
* Decoding tree; first "nodeCount" values are populated. Positive
* values are indicies to another node in the tree, negative values
* are literals (+1 because "negative zero" doesn't work well).
*/
int nodeCount;
struct {
short child[2]; /* left/right kids, must be signed 16-bit */
} decTree[kNuSQNumVals-1];
} USQState;
/*
* Decode the next symbol from the Huffman stream.
*/
static NuError USQDecodeHuffSymbol(USQState* pUsqState, int* pVal)
{
short val = 0;
int bits, bitPosn;
bits = pUsqState->bits; /* local copy */
bitPosn = pUsqState->bitPosn;
do {
if (++bitPosn > 7) {
/* grab the next byte and use that */
bits = *pUsqState->dataPtr++;
bitPosn = 0;
if (!pUsqState->dataInBuffer--)
return kNuErrBufferUnderrun;
val = pUsqState->decTree[val].child[1 & bits];
} else {
/* still got bits; shift right and use it */
val = pUsqState->decTree[val].child[1 & (bits >>= 1)];
}
} while (val >= 0);
/* val is negative literal; add one to make it zero-based then negate it */
*pVal = -(val + 1);
pUsqState->bits = bits;
pUsqState->bitPosn = bitPosn;
return kNuErrNone;
}
/*
* Read two bytes of signed data out of the buffer.
*/
static inline NuError USQReadShort(USQState* pUsqState, short* pShort)
{
if (pUsqState->dataInBuffer < 2)
return kNuErrBufferUnderrun;
*pShort = *pUsqState->dataPtr++;
*pShort |= (*pUsqState->dataPtr++) << 8;
pUsqState->dataInBuffer -= 2;
return kNuErrNone;
}
/*
* Expand "SQ" format.
*
* Because we have a stop symbol, knowing the uncompressed length of
* the file is not essential.
*/
static NuError BNYUnSqueeze(BNYArchive* pBny, BNYEntry* pEntry, FILE* outfp)
{
NuError err = kNuErrNone;
USQState usqState;
uint32_t compRemaining, getSize;
#ifdef FULL_SQ_HEADER
uint16_t magic, fileChecksum, checksum;
#endif
short nodeCount;
int i, inrep;
uint8_t* tmpBuf = NULL;
uint8_t lastc = 0;
tmpBuf = Malloc(kSqBufferSize);
if (tmpBuf == NULL) {
err = kNuErrMalloc;
goto bail;
}
usqState.dataInBuffer = 0;
usqState.dataPtr = tmpBuf;
usqState.bits = 0;
compRemaining = pEntry->realEOF;
#ifdef FULL_SQ_HEADER
if (compRemaining < 8)
#else
if (compRemaining < 3)
#endif
{
err = kNuErrBadData;
ReportError(err, "too short to be valid SQ data");
goto bail;
}
/*
* Round up to the nearest 128-byte boundary. We need to read
* everything out of the file in case this is a streaming archive.
* Because the compressed data has an embedded stop symbol, it's okay
* to "overrun" the expansion code.
*/
compRemaining =
((compRemaining + kBNYBlockSize-1) / kBNYBlockSize) * kBNYBlockSize;
/* want to grab up to kSqBufferSize bytes */
if (compRemaining > kSqBufferSize)
getSize = kSqBufferSize;
else
getSize = compRemaining;
/* copy the <= 128 bytes we already have into the general buffer */
memcpy(usqState.dataPtr, pEntry->blockBuf, kBNYBlockSize);
if (getSize > kBNYBlockSize) {
getSize -= kBNYBlockSize;
compRemaining -= kBNYBlockSize;
usqState.dataInBuffer += kBNYBlockSize;
} else {
Assert(compRemaining <= kBNYBlockSize);
usqState.dataInBuffer = getSize;
getSize = 0;
compRemaining = 0;
}
/* temporarily advance dataPtr past the block we copied in */
usqState.dataPtr += kBNYBlockSize;
/*
* Grab a big chunk. "compRemaining" is the amount of compressed
* data left in the file, usqState.dataInBuffer is the amount of
* compressed data left in the buffer.
*
* We always want to read 128-byte blocks.
*/
if (getSize) {
Assert(getSize <= kSqBufferSize);
err = BNYRead(pBny, usqState.dataPtr, getSize);
if (err != kNuErrNone) {
ReportError(err,
"failed reading compressed data (%u bytes)", getSize);
goto bail;
}
usqState.dataInBuffer += getSize;
if (getSize > compRemaining)
compRemaining = 0;
else
compRemaining -= getSize;
}
/* reset dataPtr */
usqState.dataPtr = tmpBuf;
/*
* Read the header. We assume that the header will fit in the
* compression buffer ( sq allowed 300+ for the filename, plus
* 257*2 for the tree, plus misc).
*/
Assert(kSqBufferSize > 1200);
#ifdef FULL_SQ_HEADER
err = USQReadShort(&usqState, (short*) &magic);
if (err != kNuErrNone)
goto bail;
if (magic != kNuSQMagic) {
err = kNuErrBadData;
ReportError(err, "bad magic number in SQ block");
goto bail;
}
err = USQReadShort(&usqState, (short*) &fileChecksum);
if (err != kNuErrNone)
goto bail;
checksum = 0;
/* skip over the filename */
while (*usqState.dataPtr++ != '\0')
usqState.dataInBuffer--;
usqState.dataInBuffer--;
#endif
err = USQReadShort(&usqState, &nodeCount);
if (err != kNuErrNone)
goto bail;
if (nodeCount < 0 || nodeCount >= kNuSQNumVals) {
err = kNuErrBadData;
ReportError(err, "invalid decode tree in SQ (%d nodes)", nodeCount);
goto bail;
}
usqState.nodeCount = nodeCount;
/* initialize for possibly empty tree (only happens on an empty file) */
usqState.decTree[0].child[0] = -(kNuSQEOFToken+1);
usqState.decTree[0].child[1] = -(kNuSQEOFToken+1);
/* read the nodes, ignoring "read errors" until we're done */
for (i = 0; i < nodeCount; i++) {
err = USQReadShort(&usqState, &usqState.decTree[i].child[0]);
err = USQReadShort(&usqState, &usqState.decTree[i].child[1]);
}
if (err != kNuErrNone) {
err = kNuErrBadData;
ReportError(err, "SQ data looks truncated at tree");
goto bail;
}
usqState.bitPosn = 99; /* force an immediate read */
/*
* Start pulling data out of the file. We have to Huffman-decode
* the input, and then feed that into an RLE expander.
*
* A completely lopsided (and broken) Huffman tree could require
* 256 tree descents, so we want to try to ensure we have at least 256
* bits in the buffer. Otherwise, we could get a false buffer underrun
* indication back from DecodeHuffSymbol.
*
* The SQ sources actually guarantee that a code will fit entirely
* in 16 bits, but there's no reason not to use the larger value.
*/
inrep = false;
while (1) {
int val;
if (usqState.dataInBuffer < 65 && compRemaining) {
/*
* Less than 256 bits, but there's more in the file.
*
* First thing we do is slide the old data to the start of
* the buffer.
*/
if (usqState.dataInBuffer) {
Assert(tmpBuf != usqState.dataPtr);
memmove(tmpBuf, usqState.dataPtr, usqState.dataInBuffer);
}
usqState.dataPtr = tmpBuf;
/*
* Next we read as much as we can.
*/
if (kSqBufferSize - usqState.dataInBuffer < compRemaining)
getSize = kSqBufferSize - usqState.dataInBuffer;
else
getSize = compRemaining;
Assert(getSize <= kSqBufferSize);
err = BNYRead(pBny, usqState.dataPtr + usqState.dataInBuffer,
getSize);
if (err != kNuErrNone) {
ReportError(err,
"failed reading compressed data (%u bytes)", getSize);
goto bail;
}
usqState.dataInBuffer += getSize;
if (getSize > compRemaining)
compRemaining = 0;
else
compRemaining -= getSize;
Assert(compRemaining < 32767*65536);
Assert(usqState.dataInBuffer <= kSqBufferSize);
}
err = USQDecodeHuffSymbol(&usqState, &val);
if (err != kNuErrNone) {
ReportError(err, "failed decoding huff symbol");
goto bail;
}
if (val == kNuSQEOFToken)
break;
/*
* Feed the symbol into the RLE decoder.
*/
if (inrep) {
/*
* Last char was RLE delim, handle this specially. We use
* --val instead of val-- because we already emitted the
* first occurrence of the char (right before the RLE delim).
*/
if (val == 0) {
/* special case -- just an escaped RLE delim */
lastc = kNuSQRLEDelim;
val = 2;
}
while (--val) {
/*if (pCrc != NULL)
*pCrc = Nu_CalcCRC16(*pCrc, &lastc, 1);*/
if (outfp != NULL)
putc(lastc, outfp);
#ifdef FULL_SQ_HEADER
checksum += lastc;
#endif
}
inrep = false;
} else {
/* last char was ordinary */
if (val == kNuSQRLEDelim) {
/* set a flag and catch the count the next time around */
inrep = true;
} else {
lastc = val;
/*if (pCrc != NULL)
*pCrc = Nu_CalcCRC16(*pCrc, &lastc, 1);*/
if (outfp != NULL)
putc(lastc, outfp);
#ifdef FULL_SQ_HEADER
checksum += lastc;
#endif
}
}
}
if (inrep) {
err = kNuErrBadData;
ReportError(err, "got stop symbol when run length expected");
goto bail;
}
#ifdef FULL_SQ_HEADER
/* verify the checksum stored in the SQ file */
if (checksum != fileChecksum) {
err = kNuErrBadDataCRC;
ReportError(err, "expected 0x%04x, got 0x%04x (SQ)",
fileChecksum, checksum);
goto bail;
} else {
DBUG(("--- SQ checksums match (0x%04x)\n", checksum));
}
#endif
/*
* Gobble up any unused bytes in the last 128-byte block. There
* shouldn't be more than that left over.
*/
if (compRemaining > kSqBufferSize) {
err = kNuErrBadData;
ReportError(err, "wow: found %u bytes left over", compRemaining);
goto bail;
}
if (compRemaining) {
DBUG(("+++ slurping up last %ld bytes\n", compRemaining));
err = BNYRead(pBny, tmpBuf, compRemaining);
if (err != kNuErrNone) {
ReportError(err, "failed reading leftovers");
goto bail;
}
}
bail:
if (outfp != NULL)
fflush(outfp);
Free(tmpBuf);
return err;
}
/*
* ===========================================================================
* Iterators
* ===========================================================================
*/
typedef NuError (*BNYIteratorFunc)(BNYArchive* pBny, BNYEntry* pEntry,
Boolean* consumedFlag);
/*
* Iterate through a Binary II archive, calling "func" to perform
* operations on the file.
*/
static NuError BNYIterate(NulibState* pState, BNYIteratorFunc func)
{
NuError err = kNuErrNone;
BNYArchive* pBny = NULL;
BNYEntry entry;
Boolean consumed;
int first = true;
int toFollow;
Assert(pState != NULL);
Assert(func != NULL);
NState_SetMatchCount(pState, 0);
pBny = BNYInit(pState);
if (pBny == NULL) {
err = kNuErrMalloc;
goto bail;
}
err = BNYOpenReadOnly(pBny);
if (err != kNuErrNone)
goto bail;
toFollow = 1; /* assume 1 file in archive */
pBny->first = true;
while (toFollow) {
err = BNYRead(pBny, entry.blockBuf, sizeof(entry.blockBuf));
if (err != kNuErrNone) {
ReportError(err, "failed while reading header in '%s'",
NState_GetArchiveFilename(pState));
goto bail;
}
err = BNYDecodeHeader(pBny, &entry);
if (err != kNuErrNone) {
if (first)
ReportError(err, "not a Binary II archive?");
goto bail;
}
/*
* If the file has one or more blocks, read the first block now.
* This will allow the various functions to evaluate the file
* contents for SQueeze compression.
*/
if (entry.realEOF != 0) {
err = BNYRead(pBny, entry.blockBuf, sizeof(entry.blockBuf));
if (err != kNuErrNone) {
ReportError(err, "failed while reading '%s'",
NState_GetArchiveFilename(pState));
goto bail;
}
}
/*
* Invoke the appropriate function if this file was requested.
*/
consumed = false;
if (NameIsSpecified(pBny->pState, entry.fileName)) {
NState_IncMatchCount(pState);
err = (*func)(pBny, &entry, &consumed);
if (err != kNuErrNone)
goto bail;
pBny->first = false;
}
/*
* If they didn't "consume" the entire BNY entry, we need to
* do it for them. We've already read the first block (if it
* existed), so we don't need to eat that one again.
*/
if (!consumed) {
int nblocks = (entry.realEOF + kBNYBlockSize-1) / kBNYBlockSize;
if (nblocks > 1) {
err = BNYSeek(pBny, (nblocks-1) * kBNYBlockSize);
if (err != kNuErrNone) {
ReportError(err, "failed while seeking forward");
goto bail;
}
}
}
if (!first) {
if (entry.filesToFollow != toFollow -1) {
ReportError(kNuErrNone,
"WARNING: filesToFollow %d, expected %d\n",
entry.filesToFollow, toFollow -1);
}
}
toFollow = entry.filesToFollow;
first = false;
}
if (!NState_GetMatchCount(pState))
printf("%s: no records match\n", gProgName);
bail:
if (pBny != NULL)
BNYFree(pBny);
if (err != kNuErrNone) {
DBUG(("--- Iterator returning failure %d\n", err));
}
return err;
}
/*
* Get a quick table of contents.
*/
static NuError BNYListShort(BNYArchive* pBny, BNYEntry* pEntry,
Boolean* pConsumedFlag)
{
NuError err = kNuErrNone;
printf("%s\n", pEntry->fileName);
return err;
}
/*
* Get a verbose listing of contents.
*/
static NuError BNYListVerbose(BNYArchive* pBny, BNYEntry* pEntry,
Boolean* pConsumedFlag)
{
NuError err = kNuErrNone;
Boolean isSqueezed, isReadOnly;
NulibState* pState;
char date1[kDateOutputLen];
int len;
pState = pBny->pState;
if (pBny->first) {
const char* ccp;
if (IsFilenameStdin(NState_GetArchiveFilename(pState)))
ccp = "<stdin>";
else
ccp = FilenameOnly(pState, NState_GetArchiveFilename(pState));
printf("%-59.59s Files:%5u\n\n",
ccp, pEntry->filesToFollow+1);
printf(" Name Type Auxtyp Modified"
" Fmat Length\n");
printf("-------------------------------------------------"
"----------------------\n");
}
isSqueezed = false;
if (pEntry->realEOF && IsSqueezed(pEntry->blockBuf[0], pEntry->blockBuf[1]))
isSqueezed = true;
len = strlen(pEntry->fileName);
isReadOnly = pEntry->access == 0x21L || pEntry->access == 0x01L;
if (len <= 27) {
printf("%c%-27.27s ",
isReadOnly ? '+' : ' ', pEntry->fileName);
} else {
printf("%c..%-25.25s ",
isReadOnly ? '+' : ' ', pEntry->fileName + len - 25);
}
printf("%s $%04X ",
GetFileTypeString(pEntry->fileType), pEntry->auxType);
printf("%s ", FormatDateShort(&pEntry->modWhen, date1));
if (isSqueezed)
printf("squ ");
else
printf("unc ");
printf("%8u", pEntry->realEOF);
printf("\n");
if (!pEntry->filesToFollow) {
/* last entry, print footer */
printf("-------------------------------------------------"
"----------------------\n");
}
return err;
}
/*
* Get a verbose table of contents.
*/
static NuError BNYListDebug(BNYArchive* pBny, BNYEntry* pEntry,
Boolean* pConsumedFlag)
{
NuError err = kNuErrNone;
printf("File name: '%s' Native name: '%s' BNY Version %d\n",
pEntry->fileName, pEntry->nativeName,
pEntry->version);
printf(" Phantom: %s DataFlags: 0x%02x (%s%s%s)\n",
pEntry->phantomFlag ? "YES" : "no",
pEntry->dataFlags,
pEntry->dataFlags & kBNYFlagCompressed ? "compr" : "",
pEntry->dataFlags & kBNYFlagEncrypted ? "encry" : "",
pEntry->dataFlags & kBNYFlagSparse ? "sparse" : "");
printf(" Modified %d/%02d/%02d %02d:%02d Created %d/%02d/%02d %02d:%02d\n",
pEntry->modWhen.year+1900, pEntry->modWhen.month,
pEntry->modWhen.day, pEntry->modWhen.hour,
pEntry->modWhen.minute,
pEntry->createWhen.year+1900, pEntry->createWhen.month,
pEntry->createWhen.day, pEntry->createWhen.hour,
pEntry->createWhen.minute);
printf(" FileType: 0x%04x AuxType: 0x%08x StorageType: 0x%02x\n",
pEntry->fileType, pEntry->auxType, pEntry->storageType);
printf(" EOF: %u FileSize: %u blocks DiskSpace: %u blocks\n",
pEntry->eof, pEntry->fileSize, pEntry->diskSpace);
printf(" Access: 0x%04x OSType: %d NativeFileType: 0x%04x\n",
pEntry->access, pEntry->osType, pEntry->nativeFileType);
if (pEntry->realEOF) {
printf(" *File begins 0x%02x 0x%02x%s\n",
pEntry->blockBuf[0], pEntry->blockBuf[1],
IsSqueezed(pEntry->blockBuf[0], pEntry->blockBuf[1]) ?
" (probably SQueezed)" : "");
}
printf(" FilesToFollow: %d\n", pEntry->filesToFollow);
return err;
}
/* quick enum to simplify our lives a bit */
typedef enum { kBNYExtNormal, kBNYExtPipe, kBNYExtTest } ExtMode;
/*
* Handle "extraction" of a directory.
*/
static NuError BNYExtractDirectory(BNYArchive* pBny, BNYEntry* pEntry,
ExtMode extMode)
{
NuError err = kNuErrNone;
const char* newName;
Boolean isDir;
const char* actionStr = "HOSED";
if (extMode == kBNYExtTest) {
actionStr = "skipping ";
} else if (NState_GetModJunkPaths(pBny->pState)) {
actionStr = "skipping ";
} else {
/*
* Using the normalized name of a directory is a problem
* when "-e" is set. Since Binary II officially only
* allows ProDOS names, and everything under the sun that
* supports "long" filenames can handle 15 chars of
* [A-Z][a-z][0-9][.], I'm going to skip the normalization
* step.
*
* The "right" way to handle this is to ignore the directory
* entries and just create any directories for the fully
* normalized pathname generated below.
*/
/*newName = BNYNormalizePath(pBny, pEntry);*/
newName = pEntry->fileName;
if (newName == NULL)
goto bail;
err = TestFileExistence(newName, &isDir);
if (err == kNuErrNone) {
if (isDir) {
actionStr = "skipping ";
} else {
err = kNuErrFileExists;
ReportError(err,
"unable to create directory '%s'", newName);
goto bail;
}
} else if (err == kNuErrFileNotFound) {
err = Mkdir(newName);
if (err == kNuErrNone) {
actionStr = "creating ";
} else {
ReportError(err, "failed creating directory '%s'", newName);
}
}
}
if (!NState_GetSuppressOutput(pBny->pState)) {
printf("\rDONE %s %s (directory)\n", actionStr, pEntry->fileName);
}
bail:
return err;
}
/*
* Handle "extract", "extract to pipe", and "test".
*/
static NuError BNYExtract(BNYArchive* pBny, BNYEntry* pEntry,
Boolean* pConsumedFlag)
{
NuError err = kNuErrNone;
NulibState* pState;
ExtMode extMode;
const char* actionStr = "HOSED";
FILE* outfp = NULL;
Boolean eolConv;
pState = pBny->pState;
switch (NState_GetCommand(pState)) {
case kCommandExtract: extMode = kBNYExtNormal; break;
case kCommandExtractToPipe: extMode = kBNYExtPipe; break;
case kCommandTest: extMode = kBNYExtTest; break;
default:
err = kNuErrInternal;
ReportError(err, "unexpected command %d in BNYExtract",
NState_GetCommand(pState));
Assert(0);
goto bail;
}
/*eolConv = NState_GetModConvertAll(pState);*/
eolConv = false; /* maybe someday */
/*
* Binary II requires that all directories be listed explicitly.
* If we see one, create it.
*/
if (IsDir(pEntry)) {
err = BNYExtractDirectory(pBny, pEntry, extMode);
goto bail;
}
/*
* Open the file, taking various command line flags into account.
* If we're writing to a pipe, just use that. If we're in test
* mode, the output goes nowhere.
*/
if (extMode == kBNYExtPipe) {
outfp = stdout;
} else if (extMode == kBNYExtNormal) {
/*
* Normalize the filename. If the file is squeezed, and it ends
* with ".QQ", strip off the .QQ part. (The SQ format actually
* includes a filename within, but it could be confusing to use
* that instead of the name in the BNY archive, so I've decided to
* ignore it.)
*/
const char* newName;
Boolean isDir;
char* ext;
/* if we find .qq on a squeezed file, just stomp the copy in pEntry */
if (strlen(pEntry->fileName) > 3) {
ext = pEntry->fileName + strlen(pEntry->fileName) -3;
if (IsSqueezed(pEntry->blockBuf[0], pEntry->blockBuf[1]) &&
strcasecmp(ext, ".qq") == 0)
{
*ext = '\0';
}
}
newName = BNYNormalizePath(pBny, pEntry);
if (newName == NULL)
goto bail;
err = TestFileExistence(newName, &isDir);
if (err == kNuErrNone) {
/* file exists, what to do? */
if (isDir) {
err = kNuErrFileExists;
ReportError(err, "unable to replace directory '%s'", newName);
goto bail;
}
if (!NState_GetModOverwriteExisting(pState)) {
err = kNuErrFileExists;
ReportError(err, "unable to overwrite '%s' (try '-s'?)",
newName);
goto bail;
}
} else if (err != kNuErrFileNotFound) {
ReportError(err, "stat failed on '%s'", newName);
goto bail;
} else {
Assert(err == kNuErrFileNotFound);
err = kNuErrNone;
}
/* open it, overwriting anything present */
outfp = fopen(newName, "w");
if (outfp == NULL) {
err = kNuErrFileOpen;
goto bail;
}
} else {
/* outfp == NULL means we're in test mode */
Assert(outfp == NULL);
}
/*
* Show initial progress update message ("extracting" or "expanding",
* depending on whether or not the file was squeezed).
*/
if (IsSqueezed(pEntry->blockBuf[0], pEntry->blockBuf[1]))
actionStr = "expanding ";
else
actionStr = "extracting";
if (extMode == kBNYExtTest)
actionStr = "verifying";
if (!NState_GetSuppressOutput(pState)) {
printf("\r 0%% %s%c %s", actionStr, eolConv ? '+' : ' ',
pEntry->fileName);
}
/*
* Extract the file. Send the output, perhaps with EOL conversion,
* to the output file.
*
* Thought for the day: assuming a file is Squeezed based only on
* the magic number is bogus. If we get certain classes of failures,
* and this isn't a streaming archive, we should back up and just
* extract it as a plain file.
*
* If the file is empty, don't do anything.
*/
if (pEntry->realEOF) {
if (IsSqueezed(pEntry->blockBuf[0], pEntry->blockBuf[1]))
err = BNYUnSqueeze(pBny, pEntry, outfp);
else
err = BNYCopyBlocks(pBny, pEntry, outfp);
if (err != kNuErrNone)
goto bail;
}
/*
* Show final progress update.
*/
if (!NState_GetSuppressOutput(pState)) {
printf("\rDONE\n");
}
*pConsumedFlag = true;
bail:
if (outfp != NULL && outfp != stdout) {
#if defined(MAC_LIKE)
if (SetFinderInfo(fileno(outfp), pEntry->fileType,
pEntry->auxType) != kNuErrNone)
{
fprintf(stderr, "WARNING: unable to set finder info\n");
}
#endif
fclose(outfp);
}
return err;
}
/*
* ===========================================================================
* Entry points from NuLib2
* ===========================================================================
*/
NuError BNYDoExtract(NulibState* pState)
{
if (NState_GetModConvertText(pState) ||
NState_GetModConvertAll(pState))
{
fprintf(stderr,
"%s: Binary II extraction doesn't support '-l' or '-ll'\n",
gProgName);
return kNuErrSyntax;
}
if (NState_GetModUpdate(pState) ||
NState_GetModFreshen(pState))
{
fprintf(stderr,
"%s: Binary II extraction doesn't support '-f' or '-u'\n",
gProgName);
return kNuErrSyntax;
}
if (NState_GetModComments(pState))
{
fprintf(stderr,
"%s: Binary II extraction doesn't support '-c'\n",
gProgName);
return kNuErrSyntax;
}
if (NState_GetCommand(pState) == kCommandExtractToPipe)
NState_SetSuppressOutput(pState, true);
return BNYIterate(pState, BNYExtract);
}
NuError BNYDoTest(NulibState* pState)
{
return BNYIterate(pState, BNYExtract);
}
NuError BNYDoListShort(NulibState* pState)
{
return BNYIterate(pState, BNYListShort);
}
NuError BNYDoListVerbose(NulibState* pState)
{
return BNYIterate(pState, BNYListVerbose);
}
NuError BNYDoListDebug(NulibState* pState)
{
return BNYIterate(pState, BNYListDebug);
}