From a9ad3e7a3e442c377d7fc8bfbe10343bf5f95632 Mon Sep 17 00:00:00 2001 From: Andy McFadden Date: Wed, 9 Oct 2002 00:15:24 +0000 Subject: [PATCH] Added Binary II support. It's not quite seamless, but it's a lot better than what the original NuLib offered. Wrote first cut at verbose "help" output. --- nulib2/Add.c | 10 +- nulib2/ArcUtils.c | 76 +-- nulib2/Binary2.c | 1361 ++++++++++++++++++++++++++++++++++++++++++ nulib2/ChangeLog.txt | 3 + nulib2/Extract.c | 22 +- nulib2/List.c | 62 +- nulib2/Main.c | 178 +++++- nulib2/Makefile.in | 8 +- nulib2/Nulib2.h | 12 + nulib2/State.c | 23 +- nulib2/State.h | 6 +- nulib2/SysUtils.c | 149 +++-- 12 files changed, 1772 insertions(+), 138 deletions(-) create mode 100644 nulib2/Binary2.c diff --git a/nulib2/Add.c b/nulib2/Add.c index c027199..edc98d0 100644 --- a/nulib2/Add.c +++ b/nulib2/Add.c @@ -21,14 +21,14 @@ DoAdd(NulibState* pState) NuArchive* pArchive = nil; long flushStatus; - assert(pState != nil); + Assert(pState != nil); err = OpenArchiveReadWrite(pState); if (err != kNuErrNone) goto bail; pArchive = NState_GetNuArchive(pState); - assert(pArchive != nil); + Assert(pArchive != nil); NState_SetMatchCount(pState, 0); @@ -66,7 +66,7 @@ bail: } err2 = NuClose(pArchive); - assert(err2 == kNuErrNone); + Assert(err2 == kNuErrNone); } return err; } @@ -86,8 +86,8 @@ AddToArchive(NulibState* pState, NuArchive* pArchive) ulong fileCount; int i; - assert(pState != nil); - assert(pArchive != nil); + Assert(pState != nil); + Assert(pArchive != nil); if (!NState_GetFilespecCount(pState)) { err = kNuErrSyntax; diff --git a/nulib2/ArcUtils.c b/nulib2/ArcUtils.c index f90fae3..fd7411b 100644 --- a/nulib2/ArcUtils.c +++ b/nulib2/ArcUtils.c @@ -34,9 +34,9 @@ OutputPathnameFilter(NuArchive* pArchive, void* vproposal) char* renameToStr; char* resultBuf; - assert(pArchive != nil); + Assert(pArchive != nil); (void) NuGetExtraData(pArchive, (void**) &pState); - assert(pState != nil); + Assert(pState != nil); /* handle extract-to-pipe */ if (NState_GetCommand(pState) == kCommandExtractToPipe) { @@ -58,7 +58,7 @@ OutputPathnameFilter(NuArchive* pArchive, void* vproposal) /* right source file, proceed with the rename */ NState_SetTempPathnameLen(pState, strlen(renameToStr) +1); resultBuf = NState_GetTempPathnameBuf(pState); - assert(resultBuf != nil); + Assert(resultBuf != nil); strcpy(resultBuf, renameToStr); pathProposal->newPathname = resultBuf; @@ -210,6 +210,10 @@ SpecMatchesRecord(NulibState* pState, const char* spec, const NuRecord* pRecord) * the file specification given on the command line. * * If no filespec was provided, then all records are "specified". + * + * We pass the entire NuRecord in because we may want to allow + * extraction by criteria other than name, e.g. all text files or all + * files archived before a certain date. */ Boolean IsSpecified(NulibState* pState, const NuRecord* pRecord) @@ -240,9 +244,9 @@ SelectionFilter(NuArchive* pArchive, void* vproposal) const NuSelectionProposal* selProposal = vproposal; NulibState* pState; - assert(pArchive != nil); + Assert(pArchive != nil); (void) NuGetExtraData(pArchive, (void**) &pState); - assert(pState != nil); + Assert(pState != nil); if (IsSpecified(pState, selProposal->pRecord)) { NState_IncMatchCount(pState); @@ -298,9 +302,9 @@ ProgressUpdater(NuArchive* pArchive, void* vProgress) char nameBuf[kMaxDisplayLen+1]; Boolean showName, eolConv; - assert(pArchive != nil); + Assert(pArchive != nil); (void) NuGetExtraData(pArchive, (void**) &pState); - assert(pState != nil); + Assert(pState != nil); if (NState_GetSuppressOutput(pState)) return kNuOK; @@ -367,7 +371,7 @@ ProgressUpdater(NuArchive* pArchive, void* vProgress) } break; default: - assert(0); + Assert(0); actionStr = "????"; } @@ -413,12 +417,12 @@ ProgressUpdater(NuArchive* pArchive, void* vProgress) PrintPercentage(pProgress->uncompressedLength, pProgress->uncompressedProgress); if (showName) - printf(" %s%s %s", actionStr, eolConv ? "+" : " ", nameBuf); + printf(" %s%c %s", actionStr, eolConv ? '+' : ' ', nameBuf); else - printf(" %s%s", actionStr, eolConv ? "+" : " "); + printf(" %s%c", actionStr, eolConv ? '+' : ' '); } } else { - assert(0); + Assert(0); printf("????\n"); } @@ -442,13 +446,13 @@ HandleReplaceExisting(NulibState* pState, NuArchive* pArchive, char* renameName; char reply; - assert(pState != nil); - assert(pErrorStatus != nil); - assert(pErrorStatus->pathname != nil); + Assert(pState != nil); + Assert(pErrorStatus != nil); + Assert(pErrorStatus->pathname != nil); - assert(pErrorStatus->canOverwrite); - assert(pErrorStatus->canSkip); - assert(pErrorStatus->canAbort); + Assert(pErrorStatus->canOverwrite); + Assert(pErrorStatus->canSkip); + Assert(pErrorStatus->canAbort); if (NState_GetInputUnavailable(pState)) { putc('\n', stdout); @@ -460,7 +464,7 @@ HandleReplaceExisting(NulibState* pState, NuArchive* pArchive, while (1) { printf("\n Replace %s? [y]es, [n]o, [A]ll, [N]one", pErrorStatus->pathname); - if (pErrorStatus->canRename) /* renaming records not allowed */ + if (pErrorStatus->canRename) /* renaming record adds not allowed */ printf(", [r]ename: "); else printf(": "); @@ -529,8 +533,8 @@ HandleBadCRC(NulibState* pState, NuArchive* pArchive, NuResult result = kNuOK; char reply; - assert(pState != nil); - assert(pErrorStatus != nil); + Assert(pState != nil); + Assert(pErrorStatus != nil); if (NState_GetInputUnavailable(pState)) { putc('\n', stderr); @@ -581,9 +585,9 @@ HandleAddNotFound(NulibState* pState, NuArchive* pArchive, NuResult result = kNuOK; char reply; - assert(pState != nil); - assert(pErrorStatus != nil); - assert(pErrorStatus->pathname != nil); + Assert(pState != nil); + Assert(pErrorStatus != nil); + Assert(pErrorStatus->pathname != nil); if (NState_GetInputUnavailable(pState)) { putc('\n', stdout); @@ -629,9 +633,9 @@ ErrorHandler(NuArchive* pArchive, void* vErrorStatus) NulibState* pState; NuResult result; - assert(pArchive != nil); + Assert(pArchive != nil); (void) NuGetExtraData(pArchive, (void**) &pState); - assert(pState != nil); + Assert(pState != nil); /* default action is to abort the current operation */ result = kNuAbort; @@ -682,7 +686,7 @@ ErrorHandler(NuArchive* pArchive, void* vErrorStatus) * you have to "sabotage" AddFile, or remove a file from disk * while NuFlush is running.) */ - assert(0); + Assert(0); /*result = HandleAddNotFound(pState, pArchive, pErrorStatus);*/ } } else if (pErrorStatus->operation == kNuOpTest) { @@ -752,7 +756,7 @@ IsRecordReadOnly(const NuRecord* pRecord) Boolean IsFilenameStdin(const char* archiveName) { - assert(archiveName != nil); + Assert(archiveName != nil); return (strcmp(archiveName, kStdinArchive) == 0); } @@ -769,12 +773,14 @@ OpenArchiveReadOnly(NulibState* pState) NuError err; NuArchive* pArchive = nil; - assert(pState != nil); + Assert(pState != nil); if (IsFilenameStdin(NState_GetArchiveFilename(pState))) { err = NuStreamOpenRO(stdin, &pArchive); if (err != kNuErrNone) { ReportError(err, "unable to open stdin archive"); + if (err == kNuErrIsBinary2) + err = kNuErrNotNuFX; /* we can't seek back, so forget BNY */ goto bail; } /* @@ -787,8 +793,10 @@ OpenArchiveReadOnly(NulibState* pState) } else { err = NuOpenRO(NState_GetArchiveFilename(pState), &pArchive); if (err != kNuErrNone) { - ReportError(err, "unable to open '%s'", - NState_GetArchiveFilename(pState)); + if (err != kNuErrIsBinary2) { + ReportError(err, "unable to open '%s'", + NState_GetArchiveFilename(pState)); + } goto bail; } } @@ -839,7 +847,7 @@ OpenArchiveReadOnly(NulibState* pState) else if (strcmp(SYSTEM_DEFAULT_EOL, "\r\n") == 0) err = NuSetValue(pArchive, kNuValueEOL, kNuEOLCRLF); else { - assert(0); + Assert(0); err = kNuErrInternal; ReportError(err, "Unknown SYSTEM_DEFAULT_EOL '%s'", SYSTEM_DEFAULT_EOL); goto bail; @@ -869,8 +877,8 @@ OpenArchiveReadWrite(NulibState* pState) NuArchive* pArchive = nil; char* tempName = nil; - assert(pState != nil); - assert(IsFilenameStdin(NState_GetArchiveFilename(pState)) == false); + Assert(pState != nil); + Assert(IsFilenameStdin(NState_GetArchiveFilename(pState)) == false); tempName = MakeTempArchiveName(pState); if (tempName == nil) @@ -932,7 +940,7 @@ OpenArchiveReadWrite(NulibState* pState) else if (strcmp(SYSTEM_DEFAULT_EOL, "\r\n") == 0) err = NuSetValue(pArchive, kNuValueEOL, kNuEOLCRLF); else { - assert(0); + Assert(0); err = kNuErrInternal; ReportError(err, "Unknown SYSTEM_DEFAULT_EOL '%s'", SYSTEM_DEFAULT_EOL); goto bail; diff --git a/nulib2/Binary2.c b/nulib2/Binary2.c new file mode 100644 index 0000000..01ffd1e --- /dev/null +++ b/nulib2/Binary2.c @@ -0,0 +1,1361 @@ +/* + * Nulib2 + * Copyright (C) 2000 by Andy McFadden, All Rights Reserved. + * This is free software; you can redistribute it and/or modify it under the + * terms of the GNU General Public 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.) + */ +#include "Nulib2.h" + +#ifdef HAVE_FCNTL_H +# include +#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 == nil) + 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++) { + if (NState_GetModRecurse(pState)) { + if (strncmp(*pSpec, filename, strlen(*pSpec)) == 0) + return true; + } else { + if (strcmp(*pSpec, filename) == 0) + return true; + } + } + + 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 { + ushort access; + ushort fileType; + ulong auxType; + uchar storageType; + ulong fileSize; /* in 512-byte blocks */ + ushort prodosModDate; + ushort prodosModTime; + NuDateTime modWhen; /* computed from previous two fields */ + ushort prodosCreateDate; + ushort prodosCreateTime; + NuDateTime createWhen; /* computed from previous two fields */ + ulong eof; + ulong realEOF; /* eof is bogus for directories */ + char fileName[kBNYMaxFileName+1]; + char nativeName[kBNYMaxNativeName+1]; + ulong diskSpace; /* in 512-byte blocks */ + uchar osType; /* not exactly same as NuFileSysID */ + ushort nativeFileType; + uchar phantomFlag; + uchar dataFlags; /* advisory flags */ + uchar version; + uchar filesToFollow; /* #of files after this one */ + + uchar blockBuf[kBNYBlockSize]; +} BNYEntry; + +/* + * Test for the magic number on a file in SQueezed format. + */ +static inline Boolean +IsSqueezed(uchar one, uchar 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 != nil) + 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 != nil); + Assert(pBny->pState != nil); + Assert(pBny->fp == nil); + + 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 != nil); + Assert(buf != nil); + Assert(nbyte > 0); + Assert(pBny->fp != nil); + + 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 != nil); + Assert(pBny->fp != nil); + Assert(pBny->pState != nil); + 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(ushort prodosDate, ushort prodosTime, NuDateTime* pWhen) +{ + pWhen->second = 0; + pWhen->minute = prodosTime & 0x3f; + pWhen->hour = (prodosTime >> 8) & 0x1f; + pWhen->day = prodosDate & 0x1f; + pWhen->month = (prodosDate >> 5) & 0x0f; + 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; + uchar* raw; + int len; + + Assert(pBny != nil); + Assert(pEntry != nil); + + 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.pathname = pEntry->fileName; + pathProposal.filenameSeparator = '/'; /* BNY always uses ProDOS conv */ + pathProposal.pRecord = &fakeRecord; + pathProposal.pThread = &fakeThread; + + pathProposal.newPathname = nil; + pathProposal.newFilenameSeparator = '\0'; + pathProposal.newDataSink = nil; + + /* 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. + * + * 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 != nil) { + if (fwrite(pEntry->blockBuf, toWrite, 1, outfp) != 1) { + err = errno ? errno : kNuErrFileWrite; + ReportError(err, "BNY write failed"); + goto bail; + } + } + + bytesLeft -= toWrite; + + if (bytesLeft) { + err = BNYRead(pBny, pEntry->blockBuf, kBNYBlockSize); + if (err != kNuErrNone) { + ReportError(err, "BNY read failed"); + goto bail; + } + } + } + +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 { + ulong dataInBuffer; + uchar* 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; + ulong compRemaining, getSize; +#ifdef FULL_SQ_HEADER + ushort magic, fileChecksum, checksum; +#endif + short nodeCount; + int i, inrep; + uchar* tmpBuf = nil; + uchar lastc = 0; + + tmpBuf = Malloc(kSqBufferSize); + if (tmpBuf == nil) { + err = kNuErrMalloc; + goto bail; + } + + usqState.dataInBuffer = 0; + usqState.dataPtr = tmpBuf; + + 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 (%ld 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, &magic); + if (err != kNuErrNone) + goto bail; + if (magic != kNuSQMagic) { + err = kNuErrBadData; + ReportError(err, "bad magic number in SQ block"); + goto bail; + } + + err = USQReadShort(&usqState, &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 (%ld 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 != nil) + *pCrc = Nu_CalcCRC16(*pCrc, &lastc, 1);*/ + if (outfp != nil) + 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 != nil) + *pCrc = Nu_CalcCRC16(*pCrc, &lastc, 1);*/ + if (outfp != nil) + 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 %ld 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 != nil) + 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 = nil; + BNYEntry entry; + Boolean consumed; + int first = true; + int toFollow; + + Assert(pState != nil); + Assert(func != nil); + + NState_SetMatchCount(pState, 0); + + pBny = BNYInit(pState); + if (pBny == nil) { + 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 != nil) + 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 = ""; + 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 $%04lX ", + GetFileTypeString(pEntry->fileType), pEntry->auxType); + + printf("%s ", FormatDateShort(&pEntry->modWhen, date1)); + if (isSqueezed) + printf("squ "); + else + printf("unc "); + + printf("%8ld", 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%08lx StorageType: 0x%02x\n", + pEntry->fileType, pEntry->auxType, pEntry->storageType); + printf(" EOF: %ld FileSize: %ld blocks DiskSpace: %ld 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; +} + +/* + * Handle "extract", "extract to pipe", and "test". + */ +static NuError +BNYExtract(BNYArchive* pBny, BNYEntry* pEntry, Boolean* pConsumedFlag) +{ + NuError err = kNuErrNone; + NulibState* pState; + enum { kBNYExtNormal, kBNYExtPipe, kBNYExtTest } extMode; + const char* actionStr = "HOSED"; + FILE* outfp = nil; + 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; + + /* + * Binary II requires that all directories be listed explicitly. + * If we see one, create it. + */ + if (IsDir(pEntry)) { + const char* newName; + Boolean isDir; + + if (extMode == kBNYExtTest) { + 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 == nil) + 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(pState)) { + printf("\rDONE %s %s (directory)\n", actionStr, pEntry->fileName); + } + 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; + + newName = BNYNormalizePath(pBny, pEntry); + if (newName == nil) + 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; + } + + /* open it, overwriting anything present */ + outfp = fopen(newName, "w"); + if (outfp == nil) { + err = kNuErrFileOpen; + goto bail; + } + } else { + /* outfp == nil means we're in test mode */ + Assert(outfp == nil); + } + + /* + * 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 just based 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 != nil && outfp != stdout) + 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_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); +} + diff --git a/nulib2/ChangeLog.txt b/nulib2/ChangeLog.txt index 5fe695e..6133f66 100644 --- a/nulib2/ChangeLog.txt +++ b/nulib2/ChangeLog.txt @@ -1,3 +1,6 @@ +2002/10/08 fadden + - added Binary II support + 2002/09/30 fadden - added "-z" flag to specify zlib's "deflate" compression (the "secret" debug dump command is now -g) diff --git a/nulib2/Extract.c b/nulib2/Extract.c index d14103f..30e16fb 100644 --- a/nulib2/Extract.c +++ b/nulib2/Extract.c @@ -28,7 +28,7 @@ ExtractAllRecords(NulibState* pState, NuArchive* pArchive) int idx, threadIdx; DBUG(("--- doing manual extract\n")); - assert(NState_GetCommand(pState) == kCommandExtract); /* no "-p" here */ + Assert(NState_GetCommand(pState) == kCommandExtract); /* no "-p" here */ err = NuGetAttr(pArchive, kNuAttrNumRecords, &numRecords); for (idx = 0; idx < (int) numRecords; idx++) { @@ -57,7 +57,7 @@ ExtractAllRecords(NulibState* pState, NuArchive* pArchive) threadIdx++) { pThread = NuGetThread(pRecord, threadIdx); - assert(pThread != nil); + Assert(pThread != nil); if (NuGetThreadID(pThread) == kNuThreadIDComment && pThread->actualThreadEOF > 0) @@ -93,13 +93,18 @@ DoExtract(NulibState* pState) NuError err; NuArchive* pArchive = nil; - assert(pState != nil); + Assert(pState != nil); + + if (NState_GetModBinaryII(pState)) + return BNYDoExtract(pState); err = OpenArchiveReadOnly(pState); + if (err == kNuErrIsBinary2) + return BNYDoExtract(pState); if (err != kNuErrNone) goto bail; pArchive = NState_GetNuArchive(pState); - assert(pArchive != nil); + Assert(pArchive != nil); NState_SetMatchCount(pState, 0); @@ -147,13 +152,18 @@ DoTest(NulibState* pState) NuError err; NuArchive* pArchive = nil; - assert(pState != nil); + Assert(pState != nil); + + if (NState_GetModBinaryII(pState)) + return BNYDoTest(pState); err = OpenArchiveReadOnly(pState); + if (err == kNuErrIsBinary2) + return BNYDoTest(pState); if (err != kNuErrNone) goto bail; pArchive = NState_GetNuArchive(pState); - assert(pArchive != nil); + Assert(pArchive != nil); NState_SetMatchCount(pState, 0); diff --git a/nulib2/List.c b/nulib2/List.c index c38b4bd..384a853 100644 --- a/nulib2/List.c +++ b/nulib2/List.c @@ -35,7 +35,6 @@ static const char* gMonths[] = { "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; -#define kNuDateOutputLen 64 /* @@ -66,11 +65,11 @@ ComputePercent(ulong totalSize, ulong size) * Convert a NuDateTime structure into something printable. This uses an * abbreviated format, with date and time but not weekday or seconds. * - * The buffer passed in must hold at least kNuDateOutputLen bytes. + * The buffer passed in must hold at least kDateOutputLen bytes. * * Returns "buffer" for the benefit of printf() calls. */ -static char* +char* FormatDateShort(const NuDateTime* pDateTime, char* buffer) { /* is it valid? */ @@ -108,9 +107,9 @@ ShowContentsShort(NuArchive* pArchive, void* vpRecord) const NuRecord* pRecord = (NuRecord*) vpRecord; NulibState* pState; - assert(pArchive != nil); + Assert(pArchive != nil); (void) NuGetExtraData(pArchive, (void**) &pState); - assert(pState != nil); + Assert(pState != nil); if (!IsSpecified(pState, pRecord)) goto bail; @@ -152,7 +151,7 @@ AnalyzeRecord(const NuRecord* pRecord, enum RecordKind* pRecordKind, for (idx = 0; idx < pRecord->recTotalThreads; idx++) { pThread = NuGetThread(pRecord, idx); - assert(pThread != nil); + Assert(pThread != nil); if (pThread->thThreadClass == kNuThreadClassData) { /* replace what's there if this might be more interesting */ @@ -193,13 +192,13 @@ ShowContentsVerbose(NuArchive* pArchive, void* vpRecord) ulong totalLen, totalCompLen; ushort format; NulibState* pState; - char date1[kNuDateOutputLen]; + char date1[kDateOutputLen]; char tmpbuf[16]; int len; - assert(pArchive != nil); + Assert(pArchive != nil); (void) NuGetExtraData(pArchive, (void**) &pState); - assert(pState != nil); + Assert(pState != nil); if (!IsSpecified(pState, pRecord)) goto bail; @@ -235,7 +234,7 @@ ShowContentsVerbose(NuArchive* pArchive, void* vpRecord) pRecord->recExtraType); break; default: - assert(0); + Assert(0); printf("ERROR "); } @@ -281,13 +280,18 @@ DoListShort(NulibState* pState) NuError err; NuArchive* pArchive = nil; - assert(pState != nil); + Assert(pState != nil); + + if (NState_GetModBinaryII(pState)) + return BNYDoListShort(pState); err = OpenArchiveReadOnly(pState); + if (err == kNuErrIsBinary2) + return BNYDoListShort(pState); if (err != kNuErrNone) goto bail; pArchive = NState_GetNuArchive(pState); - assert(pArchive != nil); + Assert(pArchive != nil); err = NuContents(pArchive, ShowContentsShort); /* fall through with err */ @@ -308,18 +312,23 @@ DoListVerbose(NulibState* pState) NuError err; NuArchive* pArchive = nil; const NuMasterHeader* pHeader; - char date1[kNuDateOutputLen]; - char date2[kNuDateOutputLen]; + char date1[kDateOutputLen]; + char date2[kDateOutputLen]; long totalLen, totalCompLen; const char* cp; - assert(pState != nil); + Assert(pState != nil); + + if (NState_GetModBinaryII(pState)) + return BNYDoListVerbose(pState); err = OpenArchiveReadOnly(pState); + if (err == kNuErrIsBinary2) + return BNYDoListVerbose(pState); if (err != kNuErrNone) goto bail; pArchive = NState_GetNuArchive(pState); - assert(pArchive != nil); + Assert(pArchive != nil); /* * Try to get just the filename. @@ -359,10 +368,12 @@ DoListVerbose(NulibState* pState) totalLen, totalCompLen, totalLen == 0 ? 0 : ComputePercent(totalCompLen, totalLen)); #ifdef DEBUG_VERBOSE - printf(" Overhead: %ld (%d%%)\n", - pHeader->mhMasterEOF - totalCompLen, - ComputePercent(pHeader->mhMasterEOF - totalCompLen, - pHeader->mhMasterEOF)); + if (!NState_GetFilespecCount(pState)) { + printf(" Overhead: %ld (%d%%)\n", + pHeader->mhMasterEOF - totalCompLen, + ComputePercent(pHeader->mhMasterEOF - totalCompLen, + pHeader->mhMasterEOF)); + } #endif /*(void) NuDebugDumpArchive(pArchive);*/ @@ -386,7 +397,7 @@ NullCallback(NuArchive* pArchive, void* vpRecord) /* * Print very detailed output, suitable for debugging (requires that - * debugging be enabled in nufxlib). + * debug messages be enabled in nufxlib). */ NuError DoListDebug(NulibState* pState) @@ -394,13 +405,18 @@ DoListDebug(NulibState* pState) NuError err; NuArchive* pArchive = nil; - assert(pState != nil); + Assert(pState != nil); + + if (NState_GetModBinaryII(pState)) + return BNYDoListDebug(pState); err = OpenArchiveReadOnly(pState); + if (err == kNuErrIsBinary2) + return BNYDoListDebug(pState); if (err != kNuErrNone) goto bail; pArchive = NState_GetNuArchive(pState); - assert(pArchive != nil); + Assert(pArchive != nil); /* have to do something to force the library to scan the archive */ err = NuContents(pArchive, NullCallback); diff --git a/nulib2/Main.c b/nulib2/Main.c index e2ea584..28410e5 100644 --- a/nulib2/Main.c +++ b/nulib2/Main.c @@ -27,14 +27,15 @@ typedef struct ValidCombo { } ValidCombo; static const ValidCombo gValidCombos[] = { - { kCommandAdd, false, true, "ufrj0zcke" }, + { kCommandAdd, false, true, "ekcz0jrfu" }, { kCommandDelete, false, true, "r" }, - { kCommandExtract, true, false, "ufrjclse" }, - { kCommandExtractToPipe, true, false, "rl" }, - { kCommandListShort, true, false, "" }, - { kCommandListVerbose, true, false, "" }, - { kCommandListDebug, true, false, "" }, - { kCommandTest, true, false, "r" }, + { kCommandExtract, true, false, "beslcjrfu" }, + { kCommandExtractToPipe, true, false, "blr" }, + { kCommandListShort, true, false, "br" }, + { kCommandListVerbose, true, false, "br" }, + { kCommandListDebug, true, false, "b" }, + { kCommandTest, true, false, "br" }, + { kCommandHelp, false, false, "" }, }; @@ -156,24 +157,119 @@ Usage(const NulibState* pState) " -a add files, create arc if needed -x extract files\n" " -t list files (short) -v list files (verbose)\n" " -p extract files to pipe, no msgs -i test archive integrity\n" - " -d delete files from archive\n" + " -d delete files from archive -h extended help message\n" "\n" " modifiers:\n" " -u update files (add + keep newest) -f freshen (update, no add)\n" " -r recurse into subdirs -j junk (don't record) directory names\n" + " -0 don't use compression -c add one-line comments\n" #ifdef HAVE_LIBZ - " -0 don't use compression -z compress with gzip-style 'deflate'\n" + " -z use gzip 'deflate' compression " #else - " -0 don't use compression -z use zlib [not included]\n" + " -z use zlib [not included] " #endif - " -l auto-convert text files -ll auto-convert ALL files\n" + #ifdef HAVE_LIBBZ2 + "-zz use bzip2 'BWT' compression\n" + #else + "-zz use BWT [not included]\n" + #endif + " -l auto-convert text files -ll convert CR/LF on ALL files\n" " -s stomp existing files w/o asking -k store files as disk images\n" " -e preserve ProDOS file types -ee preserve types and extend names\n" - " -c add one-line comments\n" + " -b force Binary II mode\n" ); } +/* + * Handle the "-h" command. + */ +NuError +DoHelp(const NulibState* pState) +{ + static const struct { + Command cmd; + char letter; + const char* shortDescr; + const char* longDescr; + } help[] = { + { kCommandListVerbose, 'v', "verbose listing of archive contents", +" List files in the archive, blah blah blah\n" + }, + { kCommandListShort, 't', "quick dump of table of contents", +" shortList files in the archive, blah blah blah\n" + }, + { kCommandAdd, 'a', "add files, creating the archive if necessary", +" Add files to the archive, blah blah blah\n" + }, + { kCommandDelete, 'd', "delete files from archive", +" Delete files from the archive, blah blah blah\n" + }, + { kCommandExtract, 'x', "extract files from an archive", +" Extracts files, blah blah blah\n" + }, + { kCommandExtractToPipe, 'p', "extract files to pipe", +" Extracts files to stdout, blah blah blah\n" + }, + { kCommandTest, 'i', "test archive integrity", +" Tests files, blah blah blah\n" + }, + { kCommandHelp, 'h', "show extended help", +" This is the extended help text\n" +" A full manual is available from http://www.nulib.com/.\n" + }, + }; + + int i; + + + printf("%s", +"\n" +"NuLib2 is free software, distributed under terms of the GNU General\n" +"Public License. NuLib2 uses NufxLib, a complete library of functions\n" +"for accessing NuFX (ShrinkIt) archives. NufxLib is also free software,\n" +"distributed under terms of the GNU Library General Public License (LGPL).\n" +"Source code for both is available from http://www.nulib.com/, and copies\n" +"of the licenses are included.\n" +"\n" +"This program is distributed in the hope that it will be useful,\n" +"but WITHOUT ANY WARRANTY; without even the implied warranty of\n" +"MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n" +"README file for more details.\n" + ); + + for (i = 0; i < NELEM(help); i++) { + const ValidCombo* pvc; + int j; + + pvc = FindValidComboEntry(help[i].cmd); + if (pvc == nil) { + fprintf(stderr, "%s: internal error: couldn't find vc for %d\n", + gProgName, help[i].cmd); + continue; + } + + printf("\nCommand \"-%c\": %s\n", help[i].letter, help[i].shortDescr); + printf(" Valid modifiers:"); + for (j = strlen(pvc->modifiers) -1; j >= 0; j--) { + char ch = pvc->modifiers[j]; + /* print flags, special-casing options that can be doubled */ + if (ch == 'l' || ch == 'e' || ch == 'z') + printf(" -%c -%c%c", ch, ch, ch); + else + printf(" -%c", ch); + } + putchar('\n'); + + printf("\n%s", help[i].longDescr); + } + + putchar('\n'); + + return kNuErrNone; +} + + /* * Process the command-line options. The results are placed into "pState". */ @@ -184,8 +280,16 @@ ProcessOptions(NulibState* pState, int argc, char* const* argv) int idx; /* - * Must have at least a command letter and an archive filename. + * Must have at least a command letter and an archive filename, unless + * the command letter is 'h'. Special-case a solitary "-h" here. */ + if (argc == 2 && (tolower(argv[1][0]) == 'h' || + (argv[1][0] == '-' && tolower(argv[1][1] == 'h')) ) ) + { + DoHelp(nil); + return -1; + } + if (argc < 3) { Usage(pState); return -1; @@ -228,6 +332,7 @@ ProcessOptions(NulibState* pState, int argc, char* const* argv) case 'g': NState_SetCommand(pState, kCommandListDebug); break; case 'i': NState_SetCommand(pState, kCommandTest); break; case 'd': NState_SetCommand(pState, kCommandDelete); break; + case 'h': NState_SetCommand(pState, kCommandHelp); break; default: fprintf(stderr, "%s: Unknown command '%c'\n", gProgName, *cp); goto fail; @@ -243,16 +348,29 @@ ProcessOptions(NulibState* pState, int argc, char* const* argv) case 'r': NState_SetModRecurse(pState, true); break; case 'j': NState_SetModJunkPaths(pState, true); break; case '0': NState_SetModNoCompression(pState, true); break; - case 'c': NState_SetModComments(pState, true); break; case 's': NState_SetModOverwriteExisting(pState, true); break; case 'k': NState_SetModAddAsDisk(pState, true); break; + case 'c': NState_SetModComments(pState, true); break; + case 'b': NState_SetModBinaryII(pState, true); break; case 'z': - #ifdef HAVE_LIBZ - NState_SetModCompressDeflate(pState, true); - #else - fprintf(stderr, "%s: WARNING: zlib support not compiled in\n", - gProgName); - #endif + if (*(cp+1) == 'z') { + #ifdef HAVE_LIBBZ2 + NState_SetModCompressBWT(pState, true); + #else + fprintf(stderr, + "%s: WARNING: libbzip2 support not compiled in\n", + gProgName); + #endif + cp++; + } else { + #ifdef HAVE_LIBZ + NState_SetModCompressDeflate(pState, true); + #else + fprintf(stderr, + "%s: WARNING: zlib support not compiled in\n", + gProgName); + #endif + } break; case 'e': if (*(cp-1) == 'e') /* should never point at invalid */ @@ -283,10 +401,21 @@ ProcessOptions(NulibState* pState, int argc, char* const* argv) } } + /* + * Can't have tea and no tea at the same time. + */ + if (NState_GetModNoCompression(pState) && + NState_GetModCompressDeflate(pState)) + { + fprintf(stderr, "%s: Can't specify both -0 and -z\n", + gProgName); + goto fail; + } + /* * See if we have an archive name. If it's "-", see if we allow that. */ - assert(idx < argc); + Assert(idx < argc); NState_SetArchiveFilename(pState, argv[idx]); if (IsFilenameStdin(argv[idx])) { if (!IsValidOnPipe(NState_GetCommand(pState))) { @@ -307,7 +436,7 @@ ProcessOptions(NulibState* pState, int argc, char* const* argv) NState_SetFilespecPointer(pState, &argv[idx]); NState_SetFilespecCount(pState, argc - idx); } else { - assert(idx == argc); + Assert(idx == argc); if (IsFilespecRequired(NState_GetCommand(pState))) { fprintf(stderr, "%s: This command requires a list of files\n", gProgName); @@ -368,11 +497,14 @@ DoWork(NulibState* pState) case kCommandDelete: err = DoDelete(pState); break; + case kCommandHelp: + err = DoHelp(pState); + break; default: fprintf(stderr, "ERROR: unexpected command %d\n", NState_GetCommand(pState)); err = kNuErrInternal; - assert(0); + Assert(0); break; } diff --git a/nulib2/Makefile.in b/nulib2/Makefile.in index 0604589..c5ea2cf 100644 --- a/nulib2/Makefile.in +++ b/nulib2/Makefile.in @@ -49,10 +49,10 @@ CFLAGS = @BUILD_FLAGS@ -I. -I$(NUFXSRCDIR) -I$(includedir) @DEFS@ # CFLAGS += -DQUANTIFY #endif -SRCS = Add.c ArcUtils.c Delete.c Extract.c Filename.c List.c Main.c \ - MiscStuff.c MiscUtils.c State.c SysUtils.c -OBJS = Add.o ArcUtils.o Delete.o Extract.o Filename.o List.o Main.o \ - MiscStuff.o MiscUtils.o State.o SysUtils.o +SRCS = Add.c ArcUtils.c Binary2.c Delete.c Extract.c Filename.c \ + List.c Main.c MiscStuff.c MiscUtils.c State.c SysUtils.c +OBJS = Add.o ArcUtils.o Binary2.o Delete.o Extract.o Filename.o \ + List.o Main.o MiscStuff.o MiscUtils.o State.o SysUtils.o PRODUCT = nulib2 diff --git a/nulib2/Nulib2.h b/nulib2/Nulib2.h index 6271496..c8a955d 100644 --- a/nulib2/Nulib2.h +++ b/nulib2/Nulib2.h @@ -23,6 +23,8 @@ /* make our one-line comments this big */ #define kDefaultCommentLen 200 +/* for use with FormatDateShort() */ +#define kDateOutputLen 64 /* * Function prototypes. @@ -40,6 +42,13 @@ NuError OpenArchiveReadWrite(NulibState* pState); const NuThread* GetThread(const NuRecord* pRecord, ulong idx); Boolean IsRecordReadOnly(const NuRecord* pRecord); +/* Binary2.c */ +NuError BNYDoExtract(NulibState* pState); +NuError BNYDoTest(NulibState* pState); +NuError BNYDoListShort(NulibState* pState); +NuError BNYDoListVerbose(NulibState* pState); +NuError BNYDoListDebug(NulibState* pState); + /* Delete.c */ NuError DoDelete(NulibState* pState); @@ -63,6 +72,7 @@ const char* FindExtension(NulibState* pState, const char* pathname); NuError DoListShort(NulibState* pState); NuError DoListVerbose(NulibState* pState); NuError DoListDebug(NulibState* pState); +char* FormatDateShort(const NuDateTime* pDateTime, char* buffer); /* Main.c */ extern const char* gProgName; @@ -93,5 +103,7 @@ NuError NormalizeDirectoryName(NulibState* pState, const char* srcp, char* MakeTempArchiveName(NulibState* pState); NuError AddFile(NulibState* pState, NuArchive* pArchive, const char* pathname); +NuError Mkdir(const char* dir); +NuError TestFileExistence(const char* fileName, Boolean* pIsDir); #endif /*__Nulib2__*/ diff --git a/nulib2/State.c b/nulib2/State.c index 9ff140a..aefff30 100644 --- a/nulib2/State.c +++ b/nulib2/State.c @@ -18,7 +18,7 @@ static const char* gProgramVersion = "1.1.0d1"; NuError NState_Init(NulibState** ppState) { - assert(ppState != nil); + Assert(ppState != nil); *ppState = Calloc(sizeof(**ppState)); if (*ppState == nil) @@ -89,7 +89,7 @@ NState_Free(NulibState* pState) void NState_DebugDump(const NulibState* pState) { - /* this will break when the code changes, but it's just for debugging */ + /* this table will break if the code changes, but it's just for debugging */ static const char* kCommandNames[] = { "", "add", @@ -100,9 +100,10 @@ NState_DebugDump(const NulibState* pState) "listVerbose", "listDebug", "test", + "help", }; - assert(pState != nil); + Assert(pState != nil); printf("NState:\n"); printf(" programVersion: '%s'\n", pState->programVersion); @@ -127,6 +128,8 @@ NState_DebugDump(const NulibState* pState) printf(" compressDeflate\n"); if (pState->modComments) printf(" comments\n"); + if (pState->modBinaryII) + printf(" binaryII\n"); if (pState->modConvertText) printf(" convertText\n"); if (pState->modConvertAll) @@ -286,7 +289,7 @@ NState_SetTempPathnameLen(NulibState* pState, long len) newBuf = Malloc(len); else newBuf = Realloc(pState->tempPathnameBuf, len); - assert(newBuf != nil); + Assert(newBuf != nil); if (newBuf == nil) { Free(pState->tempPathnameBuf); pState->tempPathnameBuf = nil; @@ -439,6 +442,18 @@ NState_SetModComments(NulibState* pState, Boolean val) pState->modComments = val; } +Boolean +NState_GetModBinaryII(const NulibState* pState) +{ + return pState->modBinaryII; +} + +void +NState_SetModBinaryII(NulibState* pState, Boolean val) +{ + pState->modBinaryII = val; +} + Boolean NState_GetModConvertText(const NulibState* pState) { diff --git a/nulib2/State.h b/nulib2/State.h index 2bf6816..bd241e2 100644 --- a/nulib2/State.h +++ b/nulib2/State.h @@ -24,7 +24,8 @@ typedef enum Command { kCommandListShort, kCommandListVerbose, kCommandListDebug, - kCommandTest + kCommandTest, + kCommandHelp } Command; @@ -65,6 +66,7 @@ typedef struct NulibState { Boolean modNoCompression; Boolean modCompressDeflate; Boolean modComments; + Boolean modBinaryII; Boolean modConvertText; Boolean modConvertAll; Boolean modOverwriteExisting; @@ -132,6 +134,8 @@ Boolean NState_GetModCompressDeflate(const NulibState* pState); void NState_SetModCompressDeflate(NulibState* pState, Boolean val); Boolean NState_GetModComments(const NulibState* pState); void NState_SetModComments(NulibState* pState, Boolean val); +Boolean NState_GetModBinaryII(const NulibState* pState); +void NState_SetModBinaryII(NulibState* pState, Boolean val); Boolean NState_GetModConvertText(const NulibState* pState); void NState_SetModConvertText(NulibState* pState, Boolean val); Boolean NState_GetModConvertAll(const NulibState* pState); diff --git a/nulib2/SysUtils.c b/nulib2/SysUtils.c index bcae9fb..157aa85 100644 --- a/nulib2/SysUtils.c +++ b/nulib2/SysUtils.c @@ -78,7 +78,7 @@ UNIXNormalizeFileName(NulibState* pState, const char* srcp, long srcLen, char* dstp = *pDstp; while (srcLen--) { /* don't go until null found! */ - assert(*srcp != '\0'); + Assert(*srcp != '\0'); if (*srcp == '%') { /* change '%' to "%%" */ @@ -101,7 +101,7 @@ UNIXNormalizeFileName(NulibState* pState, const char* srcp, long srcLen, } *dstp = '\0'; /* end the string, but don't advance past the null */ - assert(*pDstp - dstp <= dstLen); /* make sure we didn't overflow */ + Assert(*pDstp - dstp <= dstLen); /* make sure we didn't overflow */ *pDstp = dstp; return kNuErrNone; @@ -157,7 +157,7 @@ Win32NormalizeFileName(NulibState* pState, const char* srcp, long srcLen, while (srcLen--) { /* don't go until null found! */ - assert(*srcp != '\0'); + Assert(*srcp != '\0'); if (*srcp == '%') { /* change '%' to "%%" */ @@ -180,7 +180,7 @@ Win32NormalizeFileName(NulibState* pState, const char* srcp, long srcLen, } *dstp = '\0'; /* end the string, but don't advance past the null */ - assert(*pDstp - dstp <= dstLen); /* make sure we didn't overflow */ + Assert(*pDstp - dstp <= dstLen); /* make sure we didn't overflow */ *pDstp = dstp; return kNuErrNone; @@ -203,12 +203,12 @@ NormalizeFileName(NulibState* pState, const char* srcp, long srcLen, { NuError err; - assert(srcp != nil); - assert(srcLen > 0); - assert(dstLen > srcLen); - assert(pDstp != nil); - assert(*pDstp != nil); - assert(fssep > ' ' && fssep < 0x7f); + Assert(srcp != nil); + Assert(srcLen > 0); + Assert(dstLen > srcLen); + Assert(pDstp != nil); + Assert(*pDstp != nil); + Assert(fssep > ' ' && fssep < 0x7f); #if defined(UNIX_LIKE) err = UNIXNormalizeFileName(pState, srcp, srcLen, fssep, pDstp, dstLen); @@ -255,9 +255,9 @@ MakeTempArchiveName(NulibState* pState) long len; archivePathname = NState_GetArchiveFilename(pState); - assert(archivePathname != nil); + Assert(archivePathname != nil); fssep = NState_GetSystemPathSeparator(pState); - assert(fssep != 0); + Assert(fssep != 0); /* we'll get confused if the archive pathname looks like "/foo/bar/" */ len = strlen(archivePathname); @@ -338,10 +338,10 @@ CheckFileStatus(const char* pathname, struct stat* psb, Boolean* pExists, NuError err = kNuErrNone; int cc; - assert(pathname != nil); - assert(pExists != nil); - assert(pIsReadable != nil); - assert(pIsDir != nil); + Assert(pathname != nil); + Assert(pExists != nil); + Assert(pIsReadable != nil); + Assert(pIsDir != nil); *pExists = true; *pIsReadable = true; @@ -411,14 +411,14 @@ SetFileDetails(NulibState* pState, const char* pathname, struct stat* psb, char slashDotDotSlash[5] = "_.._"; time_t now; - assert(pState != nil); - assert(pathname != nil); - assert(pDetails != nil); + Assert(pState != nil); + Assert(pathname != nil); + Assert(pDetails != nil); /* set up the pathname buffer; note pDetails->storageName is const */ NState_SetTempPathnameLen(pState, strlen(pathname) +1); livePathStr = NState_GetTempPathnameBuf(pState); - assert(livePathStr != nil); + Assert(livePathStr != nil); strcpy(livePathStr, pathname); /* init to defaults */ @@ -485,7 +485,7 @@ SetFileDetails(NulibState* pState, const char* pathname, struct stat* psb, /* * Check for other unpleasantness, such as a leading fssep. */ - assert(NState_GetSystemPathSeparator(pState) != '\0'); + Assert(NState_GetSystemPathSeparator(pState) != '\0'); while (livePathStr[0] == NState_GetSystemPathSeparator(pState)) { /* slide it down, len is strlen +1 (for null) -1 (dropping first char)*/ memmove(livePathStr, livePathStr+1, strlen(livePathStr)); @@ -524,7 +524,7 @@ SetFileDetails(NulibState* pState, const char* pathname, struct stat* psb, char* lastFssep; lastFssep = strrchr(livePathStr, NState_GetSystemPathSeparator(pState)); if (lastFssep != nil) { - assert(*(lastFssep+1) != '\0'); /* should already have been caught*/ + Assert(*(lastFssep+1) != '\0'); /* should already have been caught*/ memmove(livePathStr, lastFssep+1, strlen(lastFssep+1)+1); } } @@ -581,7 +581,7 @@ DoAddFile(NulibState* pState, NuArchive* pArchive, const char* pathname, char* comment; DBUG(("Preparing comment for recordIdx=%ld\n", recordIdx)); - assert(recordIdx != 0); + Assert(recordIdx != 0); comment = GetSimpleComment(pState, pathname, kDefaultCommentLen); if (comment != nil) { NuDataSource* pDataSource; @@ -616,7 +616,7 @@ bail_quiet: } -#if defined(UNIX_LIKE) +#if defined(UNIX_LIKE) /* ---- UNIX --------------------------------------- */ static NuError UNIXAddFile(NulibState* pState, NuArchive* pArchive, const char* pathname); @@ -635,9 +635,9 @@ UNIXAddDirectory(NulibState* pState, NuArchive* pArchive, const char* dirName) char fssep; int len; - assert(pState != nil); - assert(pArchive != nil); - assert(dirName != nil); + Assert(pState != nil); + Assert(pArchive != nil); + Assert(dirName != nil); DBUG(("+++ DESCEND: '%s'\n", dirName)); @@ -701,9 +701,9 @@ UNIXAddFile(NulibState* pState, NuArchive* pArchive, const char* pathname) NuFileDetails details; struct stat sb; - assert(pState != nil); - assert(pArchive != nil); - assert(pathname != nil); + Assert(pState != nil); + Assert(pArchive != nil); + Assert(pathname != nil); err = CheckFileStatus(pathname, &sb, &exists, &isReadable, &isDir); if (err != kNuErrNone) { @@ -749,7 +749,7 @@ bail_quiet: return err; } -#elif defined(WINDOWS_LIKE) +#elif defined(WINDOWS_LIKE) /* ---- Windows -------------------------------- */ /* * Directory structure and functions, based on zDIR in Info-Zip sources. @@ -867,9 +867,9 @@ Win32AddDirectory(NulibState* pState, NuArchive* pArchive, const char* dirName) char fssep; int len; - assert(pState != nil); - assert(pArchive != nil); - assert(dirName != nil); + Assert(pState != nil); + Assert(pArchive != nil); + Assert(dirName != nil); DBUG(("+++ DESCEND: '%s'\n", dirName)); @@ -932,9 +932,9 @@ Win32AddFile(NulibState* pState, NuArchive* pArchive, const char* pathname) NuFileDetails details; struct stat sb; - assert(pState != nil); - assert(pArchive != nil); - assert(pathname != nil); + Assert(pState != nil); + Assert(pArchive != nil); + Assert(pathname != nil); err = CheckFileStatus(pathname, &sb, &exists, &isReadable, &isDir); if (err != kNuErrNone) { @@ -980,7 +980,7 @@ bail_quiet: return err; } -#else +#else /* ---- unknown ----------------------------------------------------- */ # error "Port this (AddFile/AddDirectory)" #endif @@ -1007,3 +1007,76 @@ AddFile(NulibState* pState, NuArchive* pArchive, const char* pathname) #endif } + +/* + * Invoke the system-dependent directory creation function. + * + * Currently only used by Binary2.c. + */ +NuError +Mkdir(const char* dir) +{ + NuError err = kNuErrNone; + + Assert(dir != nil); + +#if defined(UNIX_LIKE) + if (mkdir(dir, S_IRWXU | S_IRGRP|S_IXGRP | S_IROTH|S_IXOTH) < 0) { + err = errno ? errno : kNuErrDirCreate; + goto bail; + } + +#elif defined(WINDOWS_LIKE) + if (mkdir(dir) < 0) { + err = errno ? errno : kNuErrDirCreate; + goto bail; + } + +#else + #error "Port this" +#endif + +bail: + return err; +} + +/* + * Test for the existence of a file. + * + * Currently only used by Binary2.c. + */ +NuError +TestFileExistence(const char* fileName, Boolean* pIsDir) +{ + NuError err = kNuErrNone; + Assert(fileName != nil); + Assert(pIsDir != nil); + +#if defined(UNIX_LIKE) || defined(WINDOWS_LIKE) + { + struct stat sbuf; + int cc; + + cc = stat(fileName, &sbuf); + if (cc) { + if (errno == ENOENT) + err = kNuErrFileNotFound; + else + err = kNuErrFileStat; + goto bail; + } + + if (S_ISDIR(sbuf.st_mode)) + *pIsDir = true; + else + *pIsDir = false; + } +#else + #error "Port this" +#endif + +bail: + return err; +} + +