From 5615fa90d4b0f57744777fe0216cb1ba210eff24 Mon Sep 17 00:00:00 2001 From: cvs <> Date: Tue, 23 May 2000 01:55:31 +0000 Subject: [PATCH] Imported sources. --- nufxlib-0/Archive.c | 1104 +++++++++++++ nufxlib-0/ArchiveIO.c | 420 +++++ nufxlib-0/COPYING-LIB | 482 ++++++ nufxlib-0/ChangeLog.txt | 154 ++ nufxlib-0/Compress.c | 363 ++++ nufxlib-0/Crc16.c | 109 ++ nufxlib-0/Debug.c | 385 +++++ nufxlib-0/Deferred.c | 2495 ++++++++++++++++++++++++++++ nufxlib-0/Entry.c | 794 +++++++++ nufxlib-0/Expand.c | 213 +++ nufxlib-0/FileIO.c | 1293 +++++++++++++++ nufxlib-0/Funnel.c | 804 +++++++++ nufxlib-0/INSTALL | 183 +++ nufxlib-0/Lzw.c | 1599 ++++++++++++++++++ nufxlib-0/Makefile.in | 110 ++ nufxlib-0/Makefile.msc | 75 + nufxlib-0/MiscStuff.c | 120 ++ nufxlib-0/MiscStuff.h | 108 ++ nufxlib-0/MiscUtils.c | 358 ++++ nufxlib-0/NOTES.txt | 206 +++ nufxlib-0/NufxLib.h | 733 +++++++++ nufxlib-0/NufxLibPriv.h | 827 ++++++++++ nufxlib-0/README.txt | 92 ++ nufxlib-0/Record.c | 2744 +++++++++++++++++++++++++++++++ nufxlib-0/SourceSink.c | 866 ++++++++++ nufxlib-0/SunOS4.h | 47 + nufxlib-0/SysDefs.h | 138 ++ nufxlib-0/Thread.c | 1278 ++++++++++++++ nufxlib-0/Value.c | 231 +++ nufxlib-0/Version.c.in | 42 + nufxlib-0/config.guess | 1088 ++++++++++++ nufxlib-0/config.h.in | 125 ++ nufxlib-0/config.sub | 1220 ++++++++++++++ nufxlib-0/configure | 2115 ++++++++++++++++++++++++ nufxlib-0/configure.in | 136 ++ nufxlib-0/install-sh | 251 +++ nufxlib-0/mkinstalldirs | 34 + nufxlib-0/samples/Common.h | 66 + nufxlib-0/samples/Exerciser.c | 1360 +++++++++++++++ nufxlib-0/samples/ImgConv.c | 624 +++++++ nufxlib-0/samples/Launder.c | 587 +++++++ nufxlib-0/samples/Makefile.in | 83 + nufxlib-0/samples/Makefile.msc | 71 + nufxlib-0/samples/README-S.txt | 91 + nufxlib-0/samples/TestBasic.c | 1158 +++++++++++++ nufxlib-0/samples/TestExtract.c | 494 ++++++ nufxlib-0/samples/TestSimple.c | 105 ++ nulib2/Add.c | 109 ++ nulib2/ArcUtils.c | 935 +++++++++++ nulib2/COPYING | 340 ++++ nulib2/ChangeLog.txt | 123 ++ nulib2/Delete.c | 46 + nulib2/Extract.c | 172 ++ nulib2/Filename.c | 660 ++++++++ nulib2/INSTALL | 183 +++ nulib2/List.c | 420 +++++ nulib2/Main.c | 409 +++++ nulib2/Makefile.in | 140 ++ nulib2/Makefile.msc | 61 + nulib2/MiscStuff.c | 120 ++ nulib2/MiscStuff.h | 108 ++ nulib2/MiscUtils.c | 110 ++ nulib2/Nulib2.h | 97 ++ nulib2/README.txt | 122 ++ nulib2/State.c | 499 ++++++ nulib2/State.h | 145 ++ nulib2/SunOS4.h | 47 + nulib2/SysDefs.h | 213 +++ nulib2/SysUtils.c | 1009 ++++++++++++ nulib2/config.guess | 1088 ++++++++++++ nulib2/config.h.in | 109 ++ nulib2/config.sub | 1220 ++++++++++++++ nulib2/configure | 2069 +++++++++++++++++++++++ nulib2/configure.in | 86 + nulib2/install-sh | 251 +++ nulib2/mkinstalldirs | 34 + nulib2/nulib2.1 | 178 ++ 77 files changed, 39084 insertions(+) create mode 100644 nufxlib-0/Archive.c create mode 100644 nufxlib-0/ArchiveIO.c create mode 100644 nufxlib-0/COPYING-LIB create mode 100644 nufxlib-0/ChangeLog.txt create mode 100644 nufxlib-0/Compress.c create mode 100644 nufxlib-0/Crc16.c create mode 100644 nufxlib-0/Debug.c create mode 100644 nufxlib-0/Deferred.c create mode 100644 nufxlib-0/Entry.c create mode 100644 nufxlib-0/Expand.c create mode 100644 nufxlib-0/FileIO.c create mode 100644 nufxlib-0/Funnel.c create mode 100644 nufxlib-0/INSTALL create mode 100644 nufxlib-0/Lzw.c create mode 100644 nufxlib-0/Makefile.in create mode 100644 nufxlib-0/Makefile.msc create mode 100644 nufxlib-0/MiscStuff.c create mode 100644 nufxlib-0/MiscStuff.h create mode 100644 nufxlib-0/MiscUtils.c create mode 100644 nufxlib-0/NOTES.txt create mode 100644 nufxlib-0/NufxLib.h create mode 100644 nufxlib-0/NufxLibPriv.h create mode 100644 nufxlib-0/README.txt create mode 100644 nufxlib-0/Record.c create mode 100644 nufxlib-0/SourceSink.c create mode 100644 nufxlib-0/SunOS4.h create mode 100644 nufxlib-0/SysDefs.h create mode 100644 nufxlib-0/Thread.c create mode 100644 nufxlib-0/Value.c create mode 100644 nufxlib-0/Version.c.in create mode 100644 nufxlib-0/config.guess create mode 100644 nufxlib-0/config.h.in create mode 100644 nufxlib-0/config.sub create mode 100755 nufxlib-0/configure create mode 100644 nufxlib-0/configure.in create mode 100755 nufxlib-0/install-sh create mode 100755 nufxlib-0/mkinstalldirs create mode 100644 nufxlib-0/samples/Common.h create mode 100644 nufxlib-0/samples/Exerciser.c create mode 100644 nufxlib-0/samples/ImgConv.c create mode 100644 nufxlib-0/samples/Launder.c create mode 100644 nufxlib-0/samples/Makefile.in create mode 100644 nufxlib-0/samples/Makefile.msc create mode 100644 nufxlib-0/samples/README-S.txt create mode 100644 nufxlib-0/samples/TestBasic.c create mode 100644 nufxlib-0/samples/TestExtract.c create mode 100644 nufxlib-0/samples/TestSimple.c create mode 100644 nulib2/Add.c create mode 100644 nulib2/ArcUtils.c create mode 100644 nulib2/COPYING create mode 100644 nulib2/ChangeLog.txt create mode 100644 nulib2/Delete.c create mode 100644 nulib2/Extract.c create mode 100644 nulib2/Filename.c create mode 100644 nulib2/INSTALL create mode 100644 nulib2/List.c create mode 100644 nulib2/Main.c create mode 100644 nulib2/Makefile.in create mode 100644 nulib2/Makefile.msc create mode 100644 nulib2/MiscStuff.c create mode 100644 nulib2/MiscStuff.h create mode 100644 nulib2/MiscUtils.c create mode 100644 nulib2/Nulib2.h create mode 100644 nulib2/README.txt create mode 100644 nulib2/State.c create mode 100644 nulib2/State.h create mode 100644 nulib2/SunOS4.h create mode 100644 nulib2/SysDefs.h create mode 100644 nulib2/SysUtils.c create mode 100644 nulib2/config.guess create mode 100644 nulib2/config.h.in create mode 100644 nulib2/config.sub create mode 100755 nulib2/configure create mode 100644 nulib2/configure.in create mode 100755 nulib2/install-sh create mode 100755 nulib2/mkinstalldirs create mode 100644 nulib2/nulib2.1 diff --git a/nufxlib-0/Archive.c b/nufxlib-0/Archive.c new file mode 100644 index 0000000..8772e39 --- /dev/null +++ b/nufxlib-0/Archive.c @@ -0,0 +1,1104 @@ +/* + * NuFX archive manipulation library + * 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 Library General Public License, see the file COPYING.LIB. + * + * Archive structure creation and manipulation. + */ +#include "NufxLibPriv.h" + +#ifdef HAVE_FCNTL_H +# include +#endif +#ifndef O_BINARY +# define O_BINARY 0 +#endif + +/* master header identification */ +static const uchar kNuMasterID[kNufileIDLen] = + { 0x4e, 0xf5, 0x46, 0xe9, 0x6c, 0xe5 }; + +/* other identification; can be no longer than kNufileIDLen */ +static const uchar kNuBinary2ID[] = + { 0x0a, 0x47, 0x4c }; +static const uchar kNuSHKSEAID[] = + { 0xa2, 0x2e, 0x00 }; + +/* + * Offsets to some interesting places in the wrappers. + */ +#define kNuBNYFileSizeLo 8 /* file size in 512-byte blocks (2B) */ +#define kNuBNYFileSizeHi 114 /* ... (2B) */ +#define kNuBNYEOFLo 20 /* file size in bytes (3B) */ +#define kNuBNYEOFHi 116 /* ... (1B) */ +#define kNuBNYDiskSpace 117 /* total space req'd; equiv FileSize (4B) */ +#define kNuBNYFilesToFollow 127 /* (1B) #of files in rest of BNY file */ +#define kNuSEAFunkySize 11938 /* length of archive + 68 (4B?) */ +#define kNuSEAFunkyAdjust 68 /* ... adjustment to "FunkySize" */ +#define kNuSEALength1 11946 /* length of archive (4B?) */ +#define kNuSEALength2 12001 /* length of archive (4B?) */ + + +/* + * =========================================================================== + * Archive and MasterHeader utility functions + * =========================================================================== + */ + +/* + * Allocate and initialize a new NuArchive structure. + */ +static NuError +Nu_NuArchiveNew(NuArchive** ppArchive) +{ + Assert(ppArchive != nil); + + /* validate some assumptions we make throughout the code */ + Assert(sizeof(int) >= 2); + Assert(sizeof(ushort) >= 2); + Assert(sizeof(ulong) >= 4); + Assert(sizeof(void*) >= sizeof(NuArchive*)); + + *ppArchive = Nu_Calloc(nil, sizeof(**ppArchive)); + if (*ppArchive == nil) + return kNuErrMalloc; + + (*ppArchive)->structMagic = kNuArchiveStructMagic; + + (*ppArchive)->recordIdxSeed = 1000; /* could be a random number */ + (*ppArchive)->nextRecordIdx = (*ppArchive)->recordIdxSeed; + + /* + * Initialize assorted values to defaults. We don't try to do any + * system-specific values here; it's up to the application to decide + * what is most appropriate for the current system. + */ + (*ppArchive)->valAllowDuplicates = false; + (*ppArchive)->valConvertExtractedEOL = kNuConvertOff; + (*ppArchive)->valDataCompression = kNuCompressLZW2; + (*ppArchive)->valDiscardWrapper = false; + (*ppArchive)->valEOL = kNuEOLLF; /* non-UNIX apps must override */ + (*ppArchive)->valHandleExisting = kNuMaybeOverwrite; + (*ppArchive)->valIgnoreCRC = false; + (*ppArchive)->valMimicSHK = false; + (*ppArchive)->valModifyOrig = false; + (*ppArchive)->valOnlyUpdateOlder = false; + + return kNuErrNone; +} + +/* + * Free up a NuArchive structure and its contents. + */ +static NuError +Nu_NuArchiveFree(NuArchive* pArchive) +{ + Assert(pArchive != nil); + Assert(pArchive->structMagic == kNuArchiveStructMagic); + + (void) Nu_RecordSet_FreeAllRecords(pArchive, &pArchive->origRecordSet); + pArchive->haveToc = false; + (void) Nu_RecordSet_FreeAllRecords(pArchive, &pArchive->copyRecordSet); + (void) Nu_RecordSet_FreeAllRecords(pArchive, &pArchive->newRecordSet); + + Nu_Free(nil, pArchive->archivePathname); + Nu_Free(nil, pArchive->tmpPathname); + Nu_Free(nil, pArchive->compBuf); + Nu_Free(nil, pArchive->lzwCompressState); + Nu_Free(nil, pArchive->lzwExpandState); + + /* mark it as deceased to prevent further use, then free it */ + pArchive->structMagic = kNuArchiveStructMagic ^ 0xffffffff; + Nu_Free(nil, pArchive); + + return kNuErrNone; +} + + +/* + * Copy a NuMasterHeader struct. + */ +void +Nu_MasterHeaderCopy(NuArchive* pArchive, NuMasterHeader* pDstHeader, + const NuMasterHeader* pSrcHeader) +{ + Assert(pArchive != nil); + Assert(pDstHeader != nil); + Assert(pSrcHeader != nil); + + *pDstHeader = *pSrcHeader; +} + +/* + * Get a pointer to the archive master header (this is an API call). + */ +NuError +Nu_GetMasterHeader(NuArchive* pArchive, const NuMasterHeader** ppMasterHeader) +{ + if (ppMasterHeader == nil) + return kNuErrInvalidArg; + + *ppMasterHeader = &pArchive->masterHeader; + + return kNuErrNone; +} + + +/* + * Allocate the general-purpose compression buffer, if needed. + */ +NuError +Nu_AllocCompressionBufferIFN(NuArchive* pArchive) +{ + Assert(pArchive != nil); + + if (pArchive->compBuf != nil) + return kNuErrNone; + + pArchive->compBuf = Nu_Malloc(pArchive, kNuGenCompBufSize); + if (pArchive->compBuf == nil) + return kNuErrMalloc; + + return kNuErrNone; +} + + +/* + * Return a unique value. + */ +NuRecordIdx +Nu_GetNextRecordIdx(NuArchive* pArchive) +{ + return pArchive->nextRecordIdx++; +} + +/* + * Return a unique value. + */ +NuThreadIdx +Nu_GetNextThreadIdx(NuArchive* pArchive) +{ + return pArchive->nextRecordIdx++; /* just use the record counter */ +} + + +/* + * =========================================================================== + * Wrapper (SEA, BXY, BSE) functions + * =========================================================================== + */ + +/* + * Copy the wrapper from the archive file to the temp file. + */ +NuError +Nu_CopyWrapperToTemp(NuArchive* pArchive) +{ + NuError err; + + Assert(pArchive->headerOffset); /* no wrapper to copy?? */ + + err = Nu_FSeek(pArchive->archiveFp, 0, SEEK_SET); + BailError(err); + err = Nu_FSeek(pArchive->tmpFp, 0, SEEK_SET); + BailError(err); + err = Nu_CopyFileSection(pArchive, pArchive->tmpFp, + pArchive->archiveFp, pArchive->headerOffset); + BailError(err); + +bail: + return err; +} + + +/* + * Fix up the wrapper. The SEA and BXY headers have some fields + * set according to file length and archive attributes. + * + * Pass in the file pointer that will be written to. Wrappers are + * assumed to start at offset 0. + * + * Wrappers must appear in this order: + * Binary II + * ShrinkIt SEA (Self-Extracting Archive) + * + * If they didn't, we wouldn't be this far. + * + * I have a Binary II specification, but don't have one for SEA, so I'm + * making educated guesses based on the differences between archives. I'd + * guess some of the SEA weirdness stems from some far-sighted support + * for multiple archives within a single SEA wrapper. + */ +NuError +Nu_UpdateWrapper(NuArchive* pArchive, FILE* fp) +{ + NuError err = kNuErrNone; + Boolean hasBinary2, hasSea; + uchar identBuf[kNufileIDLen]; + ulong archiveLen, archiveLen512; + + Assert(pArchive->newMasterHeader.isValid); /* need new crc and len */ + + hasBinary2 = hasSea = false; + + switch (pArchive->archiveType) { + case kNuArchiveNuFX: + goto bail; + case kNuArchiveNuFXInBNY: + hasBinary2 = true; + break; + case kNuArchiveNuFXSelfEx: + hasSea = true; + break; + case kNuArchiveNuFXSelfExInBNY: + hasBinary2 = hasSea = true; + break; + default: + if (pArchive->headerOffset) { + Nu_ReportError(NU_BLOB, kNuErrNone, "Can't fix the wrapper??"); + err = kNuErrInternal; + goto bail; + } else + goto bail; + } + + err = Nu_FSeek(fp, 0, SEEK_SET); + BailError(err); + + if (hasBinary2) { + /* sanity check - make sure it's Binary II */ + Nu_ReadBytes(pArchive, fp, identBuf, kNufileIDLen); + if ((err = Nu_HeaderIOFailed(pArchive, fp)) != kNuErrNone) { + Nu_ReportError(NU_BLOB, err, "Failed reading BNY wrapper"); + goto bail; + } + if (memcmp(identBuf, kNuBinary2ID, sizeof(kNuBinary2ID)) != 0) { + err = kNuErrInternal; + Nu_ReportError(NU_BLOB, kNuErrNone,"Didn't find Binary II wrapper"); + goto bail; + } + + /* archiveLen includes the SEA wrapper, if any */ + archiveLen = pArchive->newMasterHeader.mhMasterEOF + + (pArchive->headerOffset - kNuBinary2BlockSize); + archiveLen512 = (archiveLen + 511) / 512; + + err = Nu_FSeek(fp, kNuBNYFileSizeLo - kNufileIDLen, SEEK_CUR); + BailError(err); + Nu_WriteTwo(pArchive, fp, (ushort)(archiveLen512 & 0xffff)); + + err = Nu_FSeek(fp, kNuBNYFileSizeHi - (kNuBNYFileSizeLo+2), SEEK_CUR); + BailError(err); + Nu_WriteTwo(pArchive, fp, (ushort)(archiveLen512 >> 16)); + + err = Nu_FSeek(fp, kNuBNYEOFLo - (kNuBNYFileSizeHi+2), SEEK_CUR); + BailError(err); + Nu_WriteTwo(pArchive, fp, (ushort)(archiveLen & 0xffff)); + Nu_WriteOne(pArchive, fp, (uchar)((archiveLen >> 16) & 0xff)); + + err = Nu_FSeek(fp, kNuBNYEOFHi - (kNuBNYEOFLo+3), SEEK_CUR); + BailError(err); + Nu_WriteOne(pArchive, fp, (uchar)(archiveLen >> 24)); + + err = Nu_FSeek(fp, kNuBNYDiskSpace - (kNuBNYEOFHi+1), SEEK_CUR); + BailError(err); + Nu_WriteFour(pArchive, fp, archiveLen512); + + /* seek just past end of BNY wrapper */ + err = Nu_FSeek(fp, kNuBinary2BlockSize - (kNuBNYDiskSpace+4), SEEK_CUR); + BailError(err); + + if ((err = Nu_HeaderIOFailed(pArchive, fp)) != kNuErrNone) { + Nu_ReportError(NU_BLOB, err, "Failed updating Binary II wrapper"); + goto bail; + } + } + + if (hasSea) { + /* sanity check - make sure it's SEA */ + Nu_ReadBytes(pArchive, fp, identBuf, kNufileIDLen); + if ((err = Nu_HeaderIOFailed(pArchive, fp)) != kNuErrNone) { + Nu_ReportError(NU_BLOB, err, "Failed reading SEA wrapper"); + goto bail; + } + if (memcmp(identBuf, kNuSHKSEAID, sizeof(kNuSHKSEAID)) != 0) { + err = kNuErrInternal; + Nu_ReportError(NU_BLOB, kNuErrNone, "Didn't find SEA wrapper"); + goto bail; + } + + archiveLen = pArchive->newMasterHeader.mhMasterEOF; + + err = Nu_FSeek(fp, kNuSEAFunkySize - kNufileIDLen, SEEK_CUR); + BailError(err); + Nu_WriteFour(pArchive, fp, archiveLen + kNuSEAFunkyAdjust); + + err = Nu_FSeek(fp, kNuSEALength1 - (kNuSEAFunkySize+4), SEEK_CUR); + BailError(err); + Nu_WriteTwo(pArchive, fp, (ushort)archiveLen); + + err = Nu_FSeek(fp, kNuSEALength2 - (kNuSEALength1+2), SEEK_CUR); + BailError(err); + Nu_WriteTwo(pArchive, fp, (ushort)archiveLen); + + /* seek past end of SEA wrapper */ + err = Nu_FSeek(fp, kNuSEAOffset - (kNuSEALength2+2), SEEK_CUR); + BailError(err); + + if ((err = Nu_HeaderIOFailed(pArchive, fp)) != kNuErrNone) { + Nu_ReportError(NU_BLOB, err, "Failed updating SEA wrapper"); + goto bail; + } + } + +bail: + return kNuErrNone; +} + + +/* + * Adjust wrapper-induced padding on the archive. + * + * GS/ShrinkIt v1.1 does some peculiar things with SEA (Self-Extracting + * Archive) files. For no apparent reason, it always adds one extra 00 + * byte to the end. When you combine SEA and BXY to make BSE, it will + * leave that extra byte inside the BXY 128-byte padding area, UNLESS + * the archive itself happens to be exactly 128 bytes, in which case + * it throws the pad byte onto the end -- resulting in an archive that + * isn't an exact multiple of 128. + * + * I've chosen to emulate the 1-byte padding "feature" of GSHK, but I'm + * not going to try to emulate the quirky behavior described above. + * + * The SEA pad byte is added first, and then the 128-byte BXY padding + * is considered. In the odd case described above, the file would be + * 127 bytes larger with nufxlib than it is with GSHK. This shouldn't + * require additional disk space to be used, assuming a filesystem block + * size of at least 128 bytes. + */ +NuError +Nu_AdjustWrapperPadding(NuArchive* pArchive, FILE* fp) +{ + NuError err = kNuErrNone; + Boolean hasBinary2, hasSea; + + hasBinary2 = hasSea = false; + + switch (pArchive->archiveType) { + case kNuArchiveNuFX: + goto bail; + case kNuArchiveNuFXInBNY: + hasBinary2 = true; + break; + case kNuArchiveNuFXSelfEx: + hasSea = true; + break; + case kNuArchiveNuFXSelfExInBNY: + hasBinary2 = hasSea = true; + break; + default: + if (pArchive->headerOffset) { + Nu_ReportError(NU_BLOB, kNuErrNone, "Can't check the padding??"); + err = kNuErrInternal; + goto bail; + } else + goto bail; + } + + err = Nu_FSeek(fp, 0, SEEK_END); + BailError(err); + + if (hasSea && pArchive->valMimicSHK) { + /* throw on a single pad byte, for no apparent reason whatsoever */ + Nu_WriteOne(pArchive, fp, 0); + } + + if (hasBinary2) { + /* pad out to the next 128-byte boundary */ + long curOffset; + + err = Nu_FTell(fp, &curOffset); + BailError(err); + + if (curOffset & 0x7f) { + int i; + + for (i = kNuBinary2BlockSize - (curOffset & 0x7f); i > 0; i--) + Nu_WriteOne(pArchive, fp, 0); + } + } + + if ((err = Nu_HeaderIOFailed(pArchive, fp)) != kNuErrNone) { + Nu_ReportError(NU_BLOB, err, "Failed updating wrapper padding"); + goto bail; + } + +bail: + return err; +} + + +/* + * =========================================================================== + * Open an archive + * =========================================================================== + */ + +/* + * Read the master header from the archive file. + * + * This also handles skipping the first 128 bytes of a .BXY file and the + * front part of a self-extracting GSHK archive. + * + * On exit, the stream will be positioned just past the master header. + */ +static NuError +Nu_ReadMasterHeader(NuArchive* pArchive) +{ + NuError err; + ushort crc; + FILE* fp; + NuMasterHeader* pHeader; + Boolean isBinary2 = false; + Boolean isSea = false; + + Assert(pArchive != nil); + + fp = pArchive->archiveFp; /* saves typing */ + pHeader = &pArchive->masterHeader; + + pArchive->headerOffset = 0; + Nu_ReadBytes(pArchive, fp, pHeader->mhNufileID, kNufileIDLen); + + if (memcmp(pHeader->mhNufileID, kNuBinary2ID, sizeof(kNuBinary2ID)) == 0) + { + int count; + + /* looks like a Binary II archive, might be BXY or BSE; seek forward */ + err = Nu_SeekArchive(pArchive, fp, kNuBNYFilesToFollow - kNufileIDLen, + SEEK_CUR); + if (err != kNuErrNone) { + err = kNuErrNotNuFX; + Nu_ReportError(NU_BLOB, kNuErrNone, + "Might be Binary II, but it's not NuFX"); + goto bail; + } + + /* + * Check "files to follow", so we can be sure this isn't a BNY that + * just happened to have a .SHK as the first file. If it is, then + * any updates to the archive will trash the rest of the BNY files. + */ + count = Nu_ReadOne(pArchive, fp); + if (count != 0) { + err = kNuErrNotNuFX; + Nu_ReportError(NU_BLOB, kNuErrNone, + "This is a Binary II archive with %d files in it", count+1); + goto bail; + } + + /* that was last item in BNY header, no need to seek */ + Assert(kNuBNYFilesToFollow == kNuBinary2BlockSize -1); + + isBinary2 = true; + pArchive->headerOffset += kNuBinary2BlockSize; + Nu_ReadBytes(pArchive, fp, pHeader->mhNufileID, kNufileIDLen); + } + if (memcmp(pHeader->mhNufileID, kNuSHKSEAID, sizeof(kNuSHKSEAID)) == 0) + { + /* might be GSHK self-extracting; seek forward */ + err = Nu_SeekArchive(pArchive, fp, kNuSEAOffset - kNufileIDLen, + SEEK_CUR); + if (err != kNuErrNone) { + err = kNuErrNotNuFX; + Nu_ReportError(NU_BLOB, kNuErrNone, + "Looks like GS executable, not NuFX"); + goto bail; + } + + isSea = true; + pArchive->headerOffset += kNuSEAOffset; + Nu_ReadBytes(pArchive, fp, pHeader->mhNufileID, kNufileIDLen); + } + + if (memcmp(kNuMasterID, pHeader->mhNufileID, kNufileIDLen) != 0) { + err = kNuErrNotNuFX; + + if (isBinary2) + Nu_ReportError(NU_BLOB, kNuErrNone, + "Looks like Binary II, not NuFX"); + else if (isSea) + Nu_ReportError(NU_BLOB, kNuErrNone, + "Looks like GS executable, not NuFX"); + else if (Nu_HeaderIOFailed(pArchive, fp) != kNuErrNone) + Nu_ReportError(NU_BLOB, kNuErrNone, + "Couldn't read enough data, not NuFX?"); + else + Nu_ReportError(NU_BLOB, kNuErrNone, + "Not a NuFX archive? Got 0x%02x%02x%02x%02x%02x%02x...", + pHeader->mhNufileID[0], pHeader->mhNufileID[1], + pHeader->mhNufileID[2], pHeader->mhNufileID[3], + pHeader->mhNufileID[4], pHeader->mhNufileID[5]); + goto bail; + } + + crc = 0; + pHeader->mhMasterCRC = Nu_ReadTwo(pArchive, fp); + pHeader->mhTotalRecords = Nu_ReadFourC(pArchive, fp, &crc); + pHeader->mhArchiveCreateWhen = Nu_ReadDateTimeC(pArchive, fp, &crc); + pHeader->mhArchiveModWhen = Nu_ReadDateTimeC(pArchive, fp, &crc); + pHeader->mhMasterVersion = Nu_ReadTwoC(pArchive, fp, &crc); + Nu_ReadBytesC(pArchive, fp, pHeader->mhReserved1, + kNufileMasterReserved1Len, &crc); + pHeader->mhMasterEOF = Nu_ReadFourC(pArchive, fp, &crc); + Nu_ReadBytesC(pArchive, fp, pHeader->mhReserved2, + kNufileMasterReserved2Len, &crc); + + /* check for errors in any of the above reads */ + if ((err = Nu_HeaderIOFailed(pArchive, fp)) != kNuErrNone) { + Nu_ReportError(NU_BLOB, err, "Failed reading master header"); + goto bail; + } + if (pHeader->mhMasterVersion > kNuMaxMHVersion) { + err = kNuErrBadMHVersion; + Nu_ReportError(NU_BLOB, err, "Bad MH version %u", + pHeader->mhMasterVersion); + goto bail; + } + + /* compare the CRC */ + if (!pArchive->valIgnoreCRC && crc != pHeader->mhMasterCRC) { + if (!Nu_ShouldIgnoreBadCRC(pArchive, nil, kNuErrBadMHCRC)) { + err = kNuErrBadMHCRC; + Nu_ReportError(NU_BLOB, err, "Stored MH CRC=0x%04x, calc=0x%04x", + pHeader->mhMasterCRC, crc); + goto bail; + } + } + + /* + * Set up a few things in the archive structure on our way out. + */ + if (isBinary2) { + if (isSea) + pArchive->archiveType = kNuArchiveNuFXSelfExInBNY; + else + pArchive->archiveType = kNuArchiveNuFXInBNY; + } else { + if (isSea) + pArchive->archiveType = kNuArchiveNuFXSelfEx; + else + pArchive->archiveType = kNuArchiveNuFX; + } + + if (isSea || isBinary2) { + DBUG(("--- Archive isSea=%d isBinary2=%d type=%d\n", + isSea, isBinary2, pArchive->archiveType)); + } + + /*pArchive->origNumRecords = pHeader->mhTotalRecords;*/ + pArchive->currentOffset = pArchive->headerOffset + kNuMasterHeaderSize; + + /*DBUG(("--- GOT: records=%ld, vers=%d, EOF=%ld, type=%d, hdrOffset=%ld\n", + pHeader->mhTotalRecords, pHeader->mhMasterVersion, + pHeader->mhMasterEOF, pArchive->archiveType, pArchive->headerOffset));*/ + + pHeader->isValid = true; + +bail: + return err; +} + + +/* + * Prepare the NuArchive and NuMasterHeader structures for use with a + * newly-created archive. + */ +static void +Nu_InitNewArchive(NuArchive* pArchive) +{ + NuMasterHeader* pHeader; + + Assert(pArchive != nil); + + pHeader = &pArchive->masterHeader; + + memcpy(pHeader->mhNufileID, kNuMasterID, kNufileIDLen); + /*pHeader->mhMasterCRC*/ + pHeader->mhTotalRecords = 0; + Nu_SetCurrentDateTime(&pHeader->mhArchiveCreateWhen); + /*pHeader->mhArchiveModWhen*/ + pHeader->mhMasterVersion = kNuOurMHVersion; + /*pHeader->mhReserved1*/ + pHeader->mhMasterEOF = kNuMasterHeaderSize; + /*pHeader->mhReserved2*/ + + pHeader->isValid = true; + + /* no need to use a temp file for a newly-created archive */ + pArchive->valModifyOrig = true; +} + + +/* + * Open an archive in streaming read-only mode. + */ +NuError +Nu_StreamOpenRO(FILE* infp, NuArchive** ppArchive) +{ + NuError err; + NuArchive* pArchive = nil; + + Assert(infp != nil); + Assert(ppArchive != nil); + + err = Nu_NuArchiveNew(ppArchive); + if (err != kNuErrNone) + goto bail; + pArchive = *ppArchive; + + pArchive->openMode = kNuOpenStreamingRO; + pArchive->archiveFp = infp; + pArchive->archivePathname = strdup("(stream)"); + + err = Nu_ReadMasterHeader(pArchive); + BailError(err); + +bail: + if (err != kNuErrNone) { + if (pArchive != nil) + (void) Nu_NuArchiveFree(pArchive); + } + return err; +} + + +/* + * Open an archive in non-streaming read-only mode. + */ +NuError +Nu_OpenRO(const char* archivePathname, NuArchive** ppArchive) +{ + NuError err; + NuArchive* pArchive = nil; + FILE* fp = nil; + + if (archivePathname == nil || !strlen(archivePathname) || ppArchive == nil) + return kNuErrInvalidArg; + + *ppArchive = nil; + + fp = fopen(archivePathname, kNuFileOpenReadOnly); + if (fp == nil) { + Nu_ReportError(NU_BLOB, errno, "Unable to open '%s'", archivePathname); + err = kNuErrFileOpen; + goto bail; + } + + err = Nu_NuArchiveNew(ppArchive); + if (err != kNuErrNone) + goto bail; + pArchive = *ppArchive; + + pArchive->openMode = kNuOpenRO; + pArchive->archiveFp = fp; + pArchive->archivePathname = strdup(archivePathname); + + err = Nu_ReadMasterHeader(pArchive); + BailError(err); + +bail: + if (err != kNuErrNone) { + if (pArchive != nil) + (void) Nu_NuArchiveFree(pArchive); + if (fp != nil) + fclose(fp); + } + return err; +} + + +/* + * Open a temp file. If "fileName" contains six Xs ("XXXXXX"), it will + * be treated as a mktemp-style template, and modified before use. + */ +static NuError +Nu_OpenTempFile(char* fileName, FILE** pFp) +{ + NuArchive* pArchive = nil; /* dummy for NU_BLOB */ + NuError err = kNuErrNone; + int len; + + /* + * If this is a mktemp-style template, use mktemp or mkstemp to fill in + * the blanks. + * + * BUG: not all implementations of mktemp actually generate a unique + * name. We probably need to do probing here. Some BSD variants like + * to complain about mktemp, since it's generally a bad way to do + * things. + */ + len = strlen(fileName); + if (len > 6 && strcmp(fileName + len - 6, "XXXXXX") == 0) { +#if defined(HAVE_MKSTEMP) && defined(HAVE_FDOPEN) + int fd; + + DBUG(("+++ Using mkstemp\n")); + + /* this modifies the template *and* opens the file */ + fd = mkstemp(fileName); + if (fd < 0) { + err = errno ? errno : kNuErrFileOpen; + Nu_ReportError(NU_BLOB, kNuErrNone, "mkstemp failed on '%s'", + fileName); + goto bail; + } + + DBUG(("--- Fd-opening temp file '%s'\n", fileName)); + *pFp = fdopen(fd, kNuFileOpenReadWriteCreat); + if (*pFp == nil) { + close(fd); + err = errno ? errno : kNuErrFileOpen; + goto bail; + } + + /* file is open, we're done */ + goto bail; + +#else + char* result; + + DBUG(("+++ Using mktemp\n")); + result = mktemp(fileName); + if (result == nil) { + Nu_ReportError(NU_BLOB, kNuErrNone, "mktemp failed on '%s'", + fileName); + err = kNuErrInternal; + goto bail; + } + + /* now open the filename as usual */ +#endif + } + + DBUG(("--- Opening temp file '%s'\n", fileName)); + +#if defined(HAVE_FDOPEN) + { + int fd; + + fd = open(fileName, O_RDWR|O_CREAT|O_EXCL|O_BINARY, 0600); + + *pFp = fdopen(fd, kNuFileOpenReadWriteCreat); + if (*pFp == nil) { + close(fd); + err = errno ? errno : kNuErrFileOpen; + goto bail; + } + } +#else + /* (not sure how portable "access" is... I think it's POSIX) */ + if (access(fileName, F_OK) == 0) { + err = kNuErrFileExists; + goto bail; + } + + *pFp = fopen(fileName, kNuFileOpenReadWriteCreat); + if (*pFp == nil) { + err = errno ? errno : kNuErrFileOpen; + goto bail; + } +#endif + + +bail: + return err; +} + +/* + * Open an archive in read-write mode, optionally creating it if it doesn't + * exist. + */ +NuError +Nu_OpenRW(const char* archivePathname, const char* tmpPathname, ulong flags, + NuArchive** ppArchive) +{ + NuError err; + FILE* fp = nil; + FILE* tmpFp = nil; + NuArchive* pArchive = nil; + char* tmpPathDup = nil; + Boolean archiveExists; + Boolean newlyCreated; + + if (archivePathname == nil || !strlen(archivePathname) || + tmpPathname == nil || !strlen(tmpPathname) || ppArchive == nil || + (flags & ~(kNuOpenCreat|kNuOpenExcl)) != 0) + { + return kNuErrInvalidArg; + } + + archiveExists = (access(archivePathname, F_OK) == 0); + + /* + * Open or create archive file. + */ + if (archiveExists) { + if ((flags & kNuOpenCreat) && (flags & kNuOpenExcl)) { + err = kNuErrFileExists; + Nu_ReportError(NU_BLOB, err, "File '%s' exists", archivePathname); + goto bail; + } + fp = fopen(archivePathname, kNuFileOpenReadWrite); + newlyCreated = false; + } else { + if (!(flags & kNuOpenCreat)) { + err = kNuErrFileNotFound; + Nu_ReportError(NU_BLOB, err, "File '%s' not found",archivePathname); + goto bail; + } + fp = fopen(archivePathname, kNuFileOpenReadWriteCreat); + newlyCreated = true; + } + + if (fp == nil) { + Nu_ReportError(NU_BLOB, errno, "Unable to open '%s'", archivePathname); + err = kNuErrFileOpen; + goto bail; + } + + /* + * Treat zero-length files as newly-created archives. + */ + if (archiveExists && !newlyCreated) { + long length; + + err = Nu_GetFileLength(nil, fp, &length); + BailError(err); + + if (!length) { + DBUG(("--- treating zero-length file as newly created archive\n")); + newlyCreated = true; + } + } + + /* + * Create a temp file. We don't need one for a newly-created archive, + * at least not right away. It's possible the caller could add some + * files, flush the changes, and then want to delete them without + * closing and reopening the archive. + * + * So, create a temp file whether we think we need one or not. Won't + * do any harm, and might save us some troubles later. + */ + tmpPathDup = strdup(tmpPathname); + BailNil(tmpPathDup); + err = Nu_OpenTempFile(tmpPathDup, &tmpFp); + if (err != kNuErrNone) { + Nu_ReportError(NU_BLOB, err, "Failed opening temp file '%s'", + tmpPathname); + goto bail; + } + + err = Nu_NuArchiveNew(ppArchive); + if (err != kNuErrNone) + goto bail; + pArchive = *ppArchive; + + pArchive->openMode = kNuOpenRW; + pArchive->newlyCreated = newlyCreated; + pArchive->archiveFp = fp; + pArchive->archivePathname = strdup(archivePathname); + fp = nil; + pArchive->tmpFp = tmpFp; + tmpFp = nil; + pArchive->tmpPathname = tmpPathDup; + tmpPathDup = nil; + + if (archiveExists && !newlyCreated) { + err = Nu_ReadMasterHeader(pArchive); + BailError(err); + } else { + Nu_InitNewArchive(pArchive); + } + +bail: + if (err != kNuErrNone) { + if (pArchive != nil) { + (void) Nu_NuArchiveFree(pArchive); + *ppArchive = nil; + } + if (fp != nil) + fclose(fp); + if (tmpFp != nil) + fclose(tmpFp); + if (tmpPathDup != nil) + Nu_Free(pArchive, tmpPathDup); + } + return err; +} + + +/* + * =========================================================================== + * Update an archive + * =========================================================================== + */ + +/* + * Write the NuFX master header at the current offset. + */ +NuError +Nu_WriteMasterHeader(NuArchive* pArchive, FILE* fp, + NuMasterHeader* pHeader) +{ + NuError err; + long crcOffset; + ushort crc; + + Assert(pArchive != nil); + Assert(fp != nil); + Assert(pHeader != nil); + Assert(pHeader->isValid); + Assert(pHeader->mhMasterVersion == kNuOurMHVersion); + + crc = 0; + + Nu_WriteBytes(pArchive, fp, pHeader->mhNufileID, kNufileIDLen); + err = Nu_FTell(fp, &crcOffset); + BailError(err); + Nu_WriteTwo(pArchive, fp, 0); + Nu_WriteFourC(pArchive, fp, pHeader->mhTotalRecords, &crc); + Nu_WriteDateTimeC(pArchive, fp, pHeader->mhArchiveCreateWhen, &crc); + Nu_WriteDateTimeC(pArchive, fp, pHeader->mhArchiveModWhen, &crc); + Nu_WriteTwoC(pArchive, fp, pHeader->mhMasterVersion, &crc); + Nu_WriteBytesC(pArchive, fp, pHeader->mhReserved1, + kNufileMasterReserved1Len, &crc); + Nu_WriteFourC(pArchive, fp, pHeader->mhMasterEOF, &crc); + Nu_WriteBytesC(pArchive, fp, pHeader->mhReserved2, + kNufileMasterReserved2Len, &crc); + + /* go back and write the CRC (sadly, the seek will flush the stdio buf) */ + pHeader->mhMasterCRC = crc; + err = Nu_FSeek(fp, crcOffset, SEEK_SET); + BailError(err); + Nu_WriteTwo(pArchive, fp, pHeader->mhMasterCRC); + + /* check for errors in any of the above writes */ + if ((err = Nu_HeaderIOFailed(pArchive, fp)) != kNuErrNone) { + Nu_ReportError(NU_BLOB, err, "Failed writing master header"); + goto bail; + } + + DBUG(("--- Master header written successfully at %ld (crc=0x%04x)\n", + crcOffset - kNufileIDLen, crc)); + +bail: + return err; +} + + +/* + * =========================================================================== + * Close an archive + * =========================================================================== + */ + +/* + * Close all open files, and free the memory associated with the structure. + * + * If it's a brand-new archive, and we didn't add anything to it, then we + * want to remove the stub archive file. + */ +static void +Nu_CloseAndFree(NuArchive* pArchive) +{ + if (pArchive->archiveFp != nil) { + DBUG(("--- Closing archive\n")); + fclose(pArchive->archiveFp); + pArchive->archiveFp = nil; + } + + if (pArchive->tmpFp != nil) { + DBUG(("--- Closing and removing temp file\n")); + fclose(pArchive->tmpFp); + pArchive->tmpFp = nil; + Assert(pArchive->tmpPathname != nil); + if (remove(pArchive->tmpPathname) != 0) { + Nu_ReportError(NU_BLOB, errno, "Unable to remove temp file '%s'", + pArchive->tmpPathname); + /* keep going */ + } + } + + if (pArchive->newlyCreated && Nu_RecordSet_IsEmpty(&pArchive->origRecordSet)) + { + DBUG(("--- Newly-created archive unmodified; removing it\n")); + if (remove(pArchive->archivePathname) != 0) { + Nu_ReportError(NU_BLOB, errno, "Unable to remove archive file '%s'", + pArchive->archivePathname); + } + } + + Nu_NuArchiveFree(pArchive); +} + +/* + * Flush pending changes to the archive, then close it. + */ +NuError +Nu_Close(NuArchive* pArchive) +{ + NuError err = kNuErrNone; + long flushStatus; + + Assert(pArchive != nil); + + if (!Nu_IsReadOnly(pArchive)) + err = Nu_Flush(pArchive, &flushStatus); + if (err == kNuErrNone) + Nu_CloseAndFree(pArchive); + else { + DBUG(("--- Close NuFlush status was 0x%4lx\n", flushStatus)); + } + + return err; +} + + +/* + * =========================================================================== + * Delete and replace an archive + * =========================================================================== + */ + +/* + * Delete the archive file, which should already have been closed. + */ +NuError +Nu_DeleteArchiveFile(NuArchive* pArchive) +{ + Assert(pArchive != nil); + Assert(pArchive->archiveFp == nil); + Assert(pArchive->archivePathname != nil); + + return Nu_DeleteFile(pArchive->archivePathname); +} + +/* + * Rename the temp file on top of the original archive. The temp file + * should be closed, and the archive file should be deleted. + */ +NuError +Nu_RenameTempToArchive(NuArchive* pArchive) +{ + Assert(pArchive != nil); + Assert(pArchive->archiveFp == nil); + Assert(pArchive->tmpFp == nil); + Assert(pArchive->archivePathname != nil); + Assert(pArchive->tmpPathname != nil); + + return Nu_RenameFile(pArchive->tmpPathname, pArchive->archivePathname); +} + diff --git a/nufxlib-0/ArchiveIO.c b/nufxlib-0/ArchiveIO.c new file mode 100644 index 0000000..71f763d --- /dev/null +++ b/nufxlib-0/ArchiveIO.c @@ -0,0 +1,420 @@ +/* + * NuFX archive manipulation library + * 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 Library General Public License, see the file COPYING.LIB. + * + * Functions for reading from and writing to the archive. These are + * specialized functions that deal with byte ordering and CRC computation. + * The functions associated with reading from an archive work equally well + * with streaming archives. + */ +#include "NufxLibPriv.h" + + +/* + * =========================================================================== + * Read and write + * =========================================================================== + */ + +/* + * Read one little-endian bytes, optionally computing a CRC. + */ +uchar +Nu_ReadOneC(NuArchive* pArchive, FILE* fp, ushort* pCrc) +{ + int ic; + + Assert(pArchive != nil); + Assert(fp != nil); + Assert(pCrc != nil); + + ic = getc(fp); + *pCrc = Nu_UpdateCRC16((uchar)ic, *pCrc); + + return ic; +} + +uchar +Nu_ReadOne(NuArchive* pArchive, FILE* fp) +{ + ushort dummyCrc /*= 0*/; + return Nu_ReadOneC(pArchive, fp, &dummyCrc); +} + +/* + * Write one byte, optionally computing a CRC. + */ +void +Nu_WriteOneC(NuArchive* pArchive, FILE* fp, uchar val, ushort* pCrc) +{ + Assert(pArchive != nil); + Assert(fp != nil); + Assert(pCrc != nil); + + putc(val, fp); +} + +void +Nu_WriteOne(NuArchive* pArchive, FILE* fp, uchar val) +{ + ushort dummyCrc /*= 0*/; + Nu_WriteOneC(pArchive, fp, val, &dummyCrc); +} + + +/* + * Read two little-endian bytes, optionally computing a CRC. + */ +ushort +Nu_ReadTwoC(NuArchive* pArchive, FILE* fp, ushort* pCrc) +{ + int ic1, ic2; + + Assert(pArchive != nil); + Assert(fp != nil); + Assert(pCrc != nil); + + ic1 = getc(fp); + *pCrc = Nu_UpdateCRC16((uchar)ic1, *pCrc); + ic2 = getc(fp); + *pCrc = Nu_UpdateCRC16((uchar)ic2, *pCrc); + + return ic1 | ic2 << 8; +} + +ushort +Nu_ReadTwo(NuArchive* pArchive, FILE* fp) +{ + ushort dummyCrc /*= 0*/; + return Nu_ReadTwoC(pArchive, fp, &dummyCrc); +} + + +/* + * Write two little-endian bytes, optionally computing a CRC. + */ +void +Nu_WriteTwoC(NuArchive* pArchive, FILE* fp, ushort val, ushort* pCrc) +{ + int ic1, ic2; + + Assert(pArchive != nil); + Assert(fp != nil); + Assert(pCrc != nil); + + ic1 = val & 0xff; + *pCrc = Nu_UpdateCRC16((uchar)ic1, *pCrc); + ic2 = val >> 8; + *pCrc = Nu_UpdateCRC16((uchar)ic2, *pCrc); + + putc(ic1, fp); + putc(ic2, fp); +} + +void +Nu_WriteTwo(NuArchive* pArchive, FILE* fp, ushort val) +{ + ushort dummyCrc /*= 0*/; + Nu_WriteTwoC(pArchive, fp, val, &dummyCrc); +} + + +/* + * Read four little-endian bytes, optionally computing a CRC. + */ +ulong +Nu_ReadFourC(NuArchive* pArchive, FILE* fp, ushort* pCrc) +{ + int ic1, ic2, ic3, ic4; + + Assert(pArchive != nil); + Assert(fp != nil); + Assert(pCrc != nil); + + ic1 = getc(fp); + *pCrc = Nu_UpdateCRC16((uchar)ic1, *pCrc); + ic2 = getc(fp); + *pCrc = Nu_UpdateCRC16((uchar)ic2, *pCrc); + ic3 = getc(fp); + *pCrc = Nu_UpdateCRC16((uchar)ic3, *pCrc); + ic4 = getc(fp); + *pCrc = Nu_UpdateCRC16((uchar)ic4, *pCrc); + + return ic1 | ic2 << 8 | (ulong)ic3 << 16 | (ulong)ic4 << 24; +} + +ulong +Nu_ReadFour(NuArchive* pArchive, FILE* fp) +{ + ushort dummyCrc /*= 0*/; + return Nu_ReadFourC(pArchive, fp, &dummyCrc); +} + + +/* + * Write four little-endian bytes, optionally computing a CRC. + */ +void +Nu_WriteFourC(NuArchive* pArchive, FILE* fp, ulong val, ushort* pCrc) +{ + int ic1, ic2, ic3, ic4; + + Assert(pArchive != nil); + Assert(fp != nil); + Assert(pCrc != nil); + + ic1 = val & 0xff; + *pCrc = Nu_UpdateCRC16((uchar)ic1, *pCrc); + ic2 = (val >> 8) & 0xff; + *pCrc = Nu_UpdateCRC16((uchar)ic2, *pCrc); + ic3 = (val >> 16) & 0xff; + *pCrc = Nu_UpdateCRC16((uchar)ic3, *pCrc); + ic4 = val >> 24; + *pCrc = Nu_UpdateCRC16((uchar)ic4, *pCrc); + + putc(ic1, fp); + putc(ic2, fp); + putc(ic3, fp); + putc(ic4, fp); +} + +void +Nu_WriteFour(NuArchive* pArchive, FILE* fp, ulong val) +{ + ushort dummyCrc /*=0*/; + Nu_WriteFourC(pArchive, fp, val, &dummyCrc); +} + + +/* + * Read an 8-byte NuFX Date/Time structure. + * + * I've chosen *not* to filter away the Y2K differences between P8 ShrinkIt + * and GS/ShrinkIt. It's easy enough to deal with, and I figure the less + * messing-with, the better. + */ +NuDateTime +Nu_ReadDateTimeC(NuArchive* pArchive, FILE* fp, ushort* pCrc) +{ + NuDateTime temp; + int ic; + + Assert(pArchive != nil); + Assert(fp != nil); + Assert(pCrc != nil); + + ic = getc(fp); + *pCrc = Nu_UpdateCRC16((uchar)ic, *pCrc); + temp.second = ic; + ic = getc(fp); + *pCrc = Nu_UpdateCRC16((uchar)ic, *pCrc); + temp.minute = ic; + ic = getc(fp); + *pCrc = Nu_UpdateCRC16((uchar)ic, *pCrc); + temp.hour = ic; + ic = getc(fp); + *pCrc = Nu_UpdateCRC16((uchar)ic, *pCrc); + temp.year = ic; + ic = getc(fp); + *pCrc = Nu_UpdateCRC16((uchar)ic, *pCrc); + temp.day = ic; + ic = getc(fp); + *pCrc = Nu_UpdateCRC16((uchar)ic, *pCrc); + temp.month = ic; + ic = getc(fp); + *pCrc = Nu_UpdateCRC16((uchar)ic, *pCrc); + temp.extra = ic; + ic = getc(fp); + *pCrc = Nu_UpdateCRC16((uchar)ic, *pCrc); + temp.weekDay = ic; + + return temp; +} + +NuDateTime +Nu_ReadDateTime(NuArchive* pArchive, FILE* fp, ushort* pCrc) +{ + ushort dummyCrc /*= 0*/; + return Nu_ReadDateTimeC(pArchive, fp, &dummyCrc); +} + + +/* + * Write an 8-byte NuFX Date/Time structure. + */ +void +Nu_WriteDateTimeC(NuArchive* pArchive, FILE* fp, NuDateTime dateTime, + ushort* pCrc) +{ + int ic; + + Assert(pArchive != nil); + Assert(fp != nil); + Assert(pCrc != nil); + + ic = dateTime.second; + *pCrc = Nu_UpdateCRC16((uchar)ic, *pCrc); + putc(ic, fp); + ic = dateTime.minute; + *pCrc = Nu_UpdateCRC16((uchar)ic, *pCrc); + putc(ic, fp); + ic = dateTime.hour; + *pCrc = Nu_UpdateCRC16((uchar)ic, *pCrc); + putc(ic, fp); + ic = dateTime.year; + *pCrc = Nu_UpdateCRC16((uchar)ic, *pCrc); + putc(ic, fp); + ic = dateTime.day; + *pCrc = Nu_UpdateCRC16((uchar)ic, *pCrc); + putc(ic, fp); + ic = dateTime.month; + *pCrc = Nu_UpdateCRC16((uchar)ic, *pCrc); + putc(ic, fp); + ic = dateTime.extra; + *pCrc = Nu_UpdateCRC16((uchar)ic, *pCrc); + putc(ic, fp); + ic = dateTime.weekDay; + *pCrc = Nu_UpdateCRC16((uchar)ic, *pCrc); + putc(ic, fp); +} + +void +Nu_WriteDateTime(NuArchive* pArchive, FILE* fp, NuDateTime dateTime) +{ + ushort dummyCrc /*= 0*/; + Nu_WriteDateTimeC(pArchive, fp, dateTime, &dummyCrc); +} + + +/* + * Read N bytes from the stream, optionally computing a CRC. + */ +void +Nu_ReadBytesC(NuArchive* pArchive, FILE* fp, void* vbuffer, long count, + ushort* pCrc) +{ + uchar* buffer = vbuffer; + int ic; + + Assert(pArchive != nil); + Assert(fp != nil); + Assert(pCrc != nil); + Assert(buffer != nil); + Assert(count > 0); + + while (count--) { + ic = getc(fp); + *pCrc = Nu_UpdateCRC16((uchar)ic, *pCrc); + *buffer++ = ic; + } +} + +void +Nu_ReadBytes(NuArchive* pArchive, FILE* fp, void* vbuffer, long count) +{ + ushort dummyCrc /*= 0*/; + Nu_ReadBytesC(pArchive, fp, vbuffer, count, &dummyCrc); +} + + +/* + * Write N bytes to the stream, optionally computing a CRC. + */ +void +Nu_WriteBytesC(NuArchive* pArchive, FILE* fp, const void* vbuffer, long count, + ushort* pCrc) +{ + const uchar* buffer = vbuffer; + int ic; + + Assert(pArchive != nil); + Assert(fp != nil); + Assert(pCrc != nil); + Assert(buffer != nil); + Assert(count > 0); + + while (count--) { + ic = *buffer++; + *pCrc = Nu_UpdateCRC16((uchar)ic, *pCrc); + putc(ic, fp); + } +} + +void +Nu_WriteBytes(NuArchive* pArchive, FILE* fp, const void* vbuffer, long count) +{ + ushort dummyCrc /*= 0*/; + Nu_WriteBytesC(pArchive, fp, vbuffer, count, &dummyCrc); +} + + +/* + * =========================================================================== + * General + * =========================================================================== + */ + +/* + * Determine whether the stream completed the last set of operations + * successfully. + */ +NuError +Nu_HeaderIOFailed(NuArchive* pArchive, FILE* fp) +{ + if (feof(fp) || ferror(fp)) + return kNuErrFile; + else + return kNuErrNone; +} + + +/* + * Seek around in an archive file. If this is a streaming-mode archive, + * we only allow forward relative seeks, which are emulated with read calls. + * + * The values for "ptrname" are the same as for fseek(). + */ +NuError +Nu_SeekArchive(NuArchive* pArchive, FILE* fp, long offset, int ptrname) +{ + if (Nu_IsStreaming(pArchive)) { + Assert(ptrname == SEEK_CUR); + Assert(offset > 0); + + /* might be faster to fread a chunk at a time */ + while (offset--) + (void) getc(fp); + + if (ferror(fp) || feof(fp)) + return kNuErrFileSeek; + } else { + if (fseek(fp, offset, ptrname) < 0) + return kNuErrFileSeek; + } + + return kNuErrNone; +} + + +/* + * Rewind an archive to the start of NuFX record data. + * + * Note that rewind(3S) resets the error indication, but this doesn't. + */ +NuError +Nu_RewindArchive(NuArchive* pArchive) +{ + Assert(pArchive != nil); + Assert(!Nu_IsStreaming(pArchive)); + + if (Nu_SeekArchive(pArchive, pArchive->archiveFp, + pArchive->headerOffset + kNuMasterHeaderSize, SEEK_SET) != 0) + return kNuErrFileSeek; + + pArchive->currentOffset = pArchive->headerOffset + kNuMasterHeaderSize; + + return kNuErrNone; +} + diff --git a/nufxlib-0/COPYING-LIB b/nufxlib-0/COPYING-LIB new file mode 100644 index 0000000..161a3d1 --- /dev/null +++ b/nufxlib-0/COPYING-LIB @@ -0,0 +1,482 @@ + GNU LIBRARY GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1991 Free Software Foundation, Inc. + 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the library GPL. It is + numbered 2 because it goes with version 2 of the ordinary GPL.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Library General Public License, applies to some +specially designated Free Software Foundation software, and to any +other libraries whose authors decide to use it. You can use it for +your libraries, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if +you distribute copies of the library, or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link a program with the library, you must provide +complete object files to the recipients so that they can relink them +with the library, after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + Our method of protecting your rights has two steps: (1) copyright +the library, and (2) offer you this license which gives you legal +permission to copy, distribute and/or modify the library. + + Also, for each distributor's protection, we want to make certain +that everyone understands that there is no warranty for this free +library. If the library is modified by someone else and passed on, we +want its recipients to know that what they have is not the original +version, so that any problems introduced by others will not reflect on +the original authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that companies distributing free +software will individually obtain patent licenses, thus in effect +transforming the program into proprietary software. To prevent this, +we have made it clear that any patent must be licensed for everyone's +free use or not licensed at all. + + Most GNU software, including some libraries, is covered by the ordinary +GNU General Public License, which was designed for utility programs. This +license, the GNU Library General Public License, applies to certain +designated libraries. This license is quite different from the ordinary +one; be sure to read it in full, and don't assume that anything in it is +the same as in the ordinary license. + + The reason we have a separate public license for some libraries is that +they blur the distinction we usually make between modifying or adding to a +program and simply using it. Linking a program with a library, without +changing the library, is in some sense simply using the library, and is +analogous to running a utility program or application program. However, in +a textual and legal sense, the linked executable is a combined work, a +derivative of the original library, and the ordinary General Public License +treats it as such. + + Because of this blurred distinction, using the ordinary General +Public License for libraries did not effectively promote software +sharing, because most developers did not use the libraries. We +concluded that weaker conditions might promote sharing better. + + However, unrestricted linking of non-free programs would deprive the +users of those programs of all benefit from the free status of the +libraries themselves. This Library General Public License is intended to +permit developers of non-free programs to use free libraries, while +preserving your freedom as a user of such programs to change the free +libraries that are incorporated in them. (We have not seen how to achieve +this as regards changes in header files, but we have achieved it as regards +changes in the actual functions of the Library.) The hope is that this +will lead to faster development of free libraries. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, while the latter only +works together with the library. + + Note that it is possible for a library to be covered by the ordinary +General Public License rather than by this special one. + + GNU LIBRARY GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library which +contains a notice placed by the copyright holder or other authorized +party saying it may be distributed under the terms of this Library +General Public License (also called "this License"). Each licensee is +addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also compile or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + c) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + d) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the source code distributed need not include anything that is normally +distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Library General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + Appendix: How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + MA 02111-1307, USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! diff --git a/nufxlib-0/ChangeLog.txt b/nufxlib-0/ChangeLog.txt new file mode 100644 index 0000000..0b5eb3a --- /dev/null +++ b/nufxlib-0/ChangeLog.txt @@ -0,0 +1,154 @@ +2000/05/18 ***** v1.0.0 shipped ***** + +2000/05/18 fadden + - updated version information to indicate final release + +2000/03/25 ***** v0.6.1 shipped ***** + +2000/03/25 fadden + - Sheppy says Mac OS X PPC v1.02 and v1.2 work with minor SysDefs tweak + +2000/03/05 ***** v0.6.0 (beta) shipped ***** + +2000/03/05 fadden + - modified NuOpenRW to call mktemp or mkstemp if tmpPath looks like + a template + - removed DEBUG_MSGS from default CFLAGS + - updated version information to indicate beta release + +2000/02/24 ***** v0.5.1 shipped ***** + +2000/02/20 changes from Scott Blackman + - portability fixes for DJGPP under Win95 + +2000/02/17 changes from Devin Reade + - portability fixes for BSD, AIX, and others + +2000/02/09 ***** v0.5.0 (alpha) shipped ***** + +2000/02/08 fadden + - tweaked the BeOS/PPC config around a little + - deleted some commas to make "gcc -pendantic" happy + +2000/02/06 fadden + - include @CFLAGS@ in case somebody wants to override them + +2000/02/06 ***** v0.4.0b shipped ***** + +2000/02/06 fadden + - added "install-shared" make target + - portability fixes for HP/UX + - configure.in test for presence of snprintf/vsnprintf declarations + +2000/02/06 ***** v0.4.0a shipped ***** + +2000/02/06 fadden + - massaged configure.in for BeOS, and added some type casts for mwerks + +2000/02/06 ***** v0.4.0 shipped ***** + +2000/02/06 fadden + - added value range checking to Nu_SetValue + +2000/02/05 fadden + - finished "test-basic" + - added an "install" target to copy libnufx and NufxLib.h + - added "mkinstalldirs" + - fixed a memory leak in NuTest + - made several implicit typecasts explicit for Visual C++'s benefit + - renamed MiscStuff's replacement function to "Nu_function" + - use "rb" or "wb" as fopen arg in sample code for Win32 + +2000/02/04 fadden + - wrote a fair piece of "test-basic" + - added "stickyErr" to "toBuffer" data sink so we can catch overruns + +2000/02/02 fadden + - minor changes to get it working under Win32 (Visual C++ 6.0) + - added --enable-dmalloc to configuration + - instead of constantly allocating 16K buffers, use pArchive->compBuf + - ignore DataSink convertEOL value when doExpand is false + +2000/02/01 fadden + - added system-specific PATH_SEP define for samples (imgconv, exerciser) + - set the pathname in ErrorStatus for CRC failures + +2000/01/31 fadden + - fixed a typo causing zero-byte GSHK-damaged files to report CRC errors + - added support for DOS-ordered 2MG images to "imgconv" + +2000/01/29 ***** v0.3.0 shipped ***** + +2000/01/29 fadden + - renamed "tests" to "samples" + - changed library version to x.y.z format (major, minor, bug-fix) + - added DEBUG_VERBOSE define, took some stuff out of DEBUG_MSGS + +2000/01/28 fadden + - make the Skip result work when an input file can't be opened + - don't allow leading fssep chars in AddRecord + - don't treat a multi-file BNY that happens to have a ShrinkIt archive + in the first slot as a BXY + - added "-t" flag (write to temp) to "launder" + - in OpenReadWrite, treat zero-length archive files as newly-created + - added workaround for GSHK's zero-byte data fork bug + +2000/01/26 fadden + - added status result flags to NuFlush + - dropped kNuAbortAll and added kNuIgnore + - implemented kNuValueIgnoreCRC + - update the storageType whenever we change the record + +2000/01/25 fadden + - don't remove the temp file if the rename fails + - Nu_ReportError now optionally uses a callback instead of stderr + - pass NuArchive* and all the trimmings into Nu_ReportError so we can + do the callback thing; required adding arguments to lots of places + - clearly labeled BailError output as debug-only, then replaced most + of the BailErrorQuiet calls with BailError + - added global error message for when pArchive doesn't exist (e.g. Open) + +2000/01/24 fadden + - added args to "launder", and made it work right with 0-length threads + - reject disk image threads that aren't a valid size + - in NuFlush, recognize when a "copy" set hasn't had any changes made + - AddThread no longer makes a copy of the DataSource + +2000/01/24 ***** v0.2 shipped ***** + +2000/01/23 fadden + - added "sec" (Set ErrorHandler Callback) to exerciser + - wrote "launder" test program + - made "doExpand" option on data sinks work + +2000/01/22 fadden + - added OnlyUpdateOlder attribute and implemented for add and extract + - made HandleExisting work for AddFile/AddRecord + - AddThread's validation now blocks data and control threads in same + record + - AddFile and AddRecord now use same validation function as AddThread + +2000/01/20 fadden + - added Eric Shepherd's BeOS shared lib stuff to configure.in + - restructed the progress updater, and made it work when adding files + +2000/01/19 fadden + - normalized SysDefs.h, changing UNIX to UNIX_LIKE and defining for BeOS + - added "shared" target to makefile + - added BeOS stuff to autoconf setup + +2000/01/17 fadden + - fixed Makefile issue preventing "tests" from working with old GNU make + - fixed Lzw.c problem fouling up SunOS gcc v2.5.8 + - discovered "<" vs "<=" flapping in GSHK, which I can't Mimic + - fixed option list dump in debug print + - properly return from all Malloc errors; abort is now debug-only again + - lots of BeOS/Metrowerks "it's not gcc" changes from Eric Shepherd + +2000/01/17 ***** v0.1 shipped ***** + +(much time passes) + +mid-1998 fadden + - work begins + diff --git a/nufxlib-0/Compress.c b/nufxlib-0/Compress.c new file mode 100644 index 0000000..d68a3de --- /dev/null +++ b/nufxlib-0/Compress.c @@ -0,0 +1,363 @@ +/* + * NuFX archive manipulation library + * 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 Library General Public License, see the file COPYING.LIB. + * + * Compress data into an archive. + */ +#include "NufxLibPriv.h" + +/* for ShrinkIt-mimic mode, don't compress files under 512 bytes */ +#define kNuSHKLZWThreshold 512 + + +/* + * "Compress" an uncompressed thread. + */ +static NuError +Nu_CompressUncompressed(NuArchive* pArchive, NuStraw* pStraw, + FILE* fp, ulong srcLen, ulong* pDstLen, ushort *pCrc) +{ + NuError err = kNuErrNone; + /*uchar* buffer = nil;*/ + ulong count, getsize; + + Assert(pArchive != nil); + Assert(pStraw != nil); + Assert(fp != nil); + Assert(srcLen > 0); + + *pDstLen = srcLen; /* get this over with */ + + /* doesn't have to be same size as funnel, but it's not a bad idea */ + /*buffer = Nu_Malloc(pArchive, kNuFunnelBufSize);*/ + /*BailAlloc(buffer);*/ + err = Nu_AllocCompressionBufferIFN(pArchive); + BailError(err); + + if (pCrc != nil) + *pCrc = kNuInitialThreadCRC; + count = srcLen; + + while (count) { + getsize = (count > kNuGenCompBufSize) ? kNuGenCompBufSize : count; + + err = Nu_StrawRead(pArchive, pStraw, pArchive->compBuf, getsize); + BailError(err); + if (pCrc != nil) + *pCrc = Nu_CalcCRC16(*pCrc, pArchive->compBuf, getsize); + err = Nu_FWrite(fp, pArchive->compBuf, getsize); + BailError(err); + + count -= getsize; + } + +bail: + /*Nu_Free(pArchive, buffer);*/ + return err; +} + + +/* + * Compress from a data source to an archive. + * + * All archive-specified fields in "pThread" will be filled in, as will + * "actualThreadEOF". The "nuThreadIdx" and "fileOffset" fields will + * not be modified. + * + * If "sourceFormat" is uncompressed: + * "targetFormat" will be used to compress the data + * the data source length will be placed into pThread->thThreadEOF + * the compressed size will be placed into pThread->thCompThreadEOF + * + * If "sourceFormat" is compressed: + * the data will be copied without compression (targetFormat is ignored) + * the data source "otherLen" value will be placed into pThread->thThreadEOF + * the data source length will be placed into pThread->thCompThreadEOF + * + * The actual format used will be placed in pThread->thThreadFormat, and + * the CRC of the uncompressed data will be placed in pThread->thThreadCRC. + * The remaining fields of "pThread", thThreadClass and thThreadKind, will + * be set based on the fields in "pDataSource". + * + * The output file will be positioned after the last byte of the output. + * (For a pre-sized buffer, this may not be the desired result.) + */ +NuError +Nu_CompressToArchive(NuArchive* pArchive, NuDataSource* pDataSource, + NuThreadID threadID, NuThreadFormat sourceFormat, + NuThreadFormat targetFormat, NuProgressData* pProgressData, FILE* dstFp, + NuThread* pThread) +{ + NuError err; + long origOffset; + NuStraw* pStraw = nil; + NuDataSink* pDataSink = nil; + ulong srcLen, dstLen; + ushort threadCrc; + + Assert(pArchive != nil); + Assert(pDataSource != nil); + /* okay if pProgressData is nil */ + Assert(dstFp != nil); + Assert(pThread != nil); + + /* remember file offset, so we can back up if compression fails */ + err = Nu_FTell(dstFp, &origOffset); + BailError(err); + Assert(origOffset == pThread->fileOffset); /* can get rid of ftell? */ + + /* fill in some thread fields */ + threadCrc = kNuInitialThreadCRC; + + pThread->thThreadClass = NuThreadIDGetClass(threadID); + pThread->thThreadKind = NuThreadIDGetKind(threadID); + pThread->actualThreadEOF = (ulong)-1; + /* nuThreadIdx and fileOffset should already be set */ + + /* + * Get the input length. For "buffer" and "fp" sources, this is just + * a value passed in. For "file" sources, this is the length of the + * file on disk. The file should already have been opened successfully + * by the caller. + * + * If the input file is zero bytes long, "store" it uncompressed and + * bail immediately. + * + * (Our desire to store uncompressible data without compression clashes + * with a passing interest in doing CRLF conversions on input data. We + * want to know the length ahead of time, which potentially makes the + * compression code simpler, but prevents us from doing the conversion + * unless we pre-flight the conversion with a separate pass through the + * input file. Of course, it's still possible for the application to + * convert the file into a temp file and add from there, so all is + * not lost.) + */ + srcLen = Nu_DataSourceGetDataLen(pDataSource); + /*DBUG(("+++ input file length is %lu\n", srcLen));*/ + + /* + * Create a "Straw" to slurp the input through and track progress. + */ + err = Nu_StrawNew(pArchive, pDataSource, pProgressData, &pStraw); + BailError(err); + + if (!srcLen) { + /* empty file! */ + pThread->thThreadFormat = kNuThreadFormatUncompressed; + pThread->thThreadCRC = threadCrc; + pThread->thThreadEOF = 0; + pThread->thCompThreadEOF = 0; + pThread->actualThreadEOF = 0; + goto done; /* send final progress message */ + } + + if (sourceFormat == kNuThreadFormatUncompressed) { + /* + * Compress the input. + */ + + /* GSHK doesn't compress anything under 512 bytes */ + if (pArchive->valMimicSHK && srcLen < kNuSHKLZWThreshold) + targetFormat = kNuThreadFormatUncompressed; + + if (pProgressData != nil) { + if (targetFormat != kNuThreadFormatUncompressed) + Nu_StrawSetProgressState(pStraw, kNuProgressCompressing); + else + Nu_StrawSetProgressState(pStraw, kNuProgressStoring); + } + err = Nu_ProgressDataCompressPrep(pArchive, pStraw, targetFormat, + srcLen); + BailError(err); + + switch (targetFormat) { + case kNuThreadFormatUncompressed: + err = Nu_CompressUncompressed(pArchive, pStraw, dstFp, srcLen, + &dstLen, &threadCrc); + break; + case kNuThreadFormatLZW1: + err = Nu_CompressLZW1(pArchive, pStraw, dstFp, srcLen, &dstLen, + &threadCrc); + break; + case kNuThreadFormatLZW2: + err = Nu_CompressLZW2(pArchive, pStraw, dstFp, srcLen, &dstLen, + &threadCrc); + break; + default: + Assert(0); + err = kNuErrInternal; + goto bail; + } + + BailError(err); + + pThread->thThreadCRC = threadCrc; /* CRC of uncompressed data */ + + if (dstLen < srcLen || + (dstLen == srcLen && targetFormat == kNuThreadFormatUncompressed)) + { + /* got smaller, or we didn't try to compress it; keep it */ + pThread->thThreadEOF = srcLen; + pThread->thCompThreadEOF = dstLen; + pThread->thThreadFormat = targetFormat; + } else { + /* got bigger, store it uncompressed */ + err = Nu_FSeek(dstFp, origOffset, SEEK_SET); + BailError(err); + err = Nu_StrawRewind(pArchive, pStraw); + BailError(err); + if (pProgressData != nil) + Nu_StrawSetProgressState(pStraw, kNuProgressStoring); + err = Nu_ProgressDataCompressPrep(pArchive, pStraw, + kNuThreadFormatUncompressed, srcLen); + BailError(err); + + DBUG(("--- compression (%d) failed (%ld vs %ld), storing\n", + targetFormat, dstLen, srcLen)); + err = Nu_CompressUncompressed(pArchive, pStraw, dstFp, srcLen, + &dstLen, &threadCrc); + BailError(err); + + /* [didn't need to recompute CRC, but I was being paranoid] */ + Assert(threadCrc == pThread->thThreadCRC); + + pThread->thThreadEOF = srcLen; + pThread->thCompThreadEOF = dstLen; + pThread->thThreadFormat = kNuThreadFormatUncompressed; + } + + } else { + /* + * Copy the already-compressed input. + */ + if (pProgressData != nil) + Nu_StrawSetProgressState(pStraw, kNuProgressCopying); + err = Nu_ProgressDataCompressPrep(pArchive, pStraw, + kNuThreadFormatUncompressed, srcLen); + BailError(err); + + err = Nu_CompressUncompressed(pArchive, pStraw, dstFp, srcLen, + &dstLen, nil); + BailError(err); + + pThread->thThreadEOF = Nu_DataSourceGetOtherLen(pDataSource); + pThread->thCompThreadEOF = srcLen; + pThread->thThreadFormat = sourceFormat; + pThread->thThreadCRC = Nu_DataSourceGetRawCrc(pDataSource); + } + pThread->actualThreadEOF = pThread->thThreadEOF; + +done: + DBUG(("+++ srcLen=%ld, dstLen=%ld, actual=%ld\n", + srcLen, dstLen, pThread->actualThreadEOF)); + + /* make sure we send a final "success" progress message at 100% */ + if (pProgressData != nil) { + (void) Nu_StrawSetProgressState(pStraw, kNuProgressDone); + err = Nu_StrawSendProgressUpdate(pArchive, pStraw); + BailError(err); + } + +bail: + (void) Nu_StrawFree(pArchive, pStraw); + (void) Nu_DataSinkFree(pDataSink); + return err; +} + + +/* + * Copy pre-sized data into the archive at the current offset. + * + * All archive-specified fields in "pThread" will be filled in, as will + * "actualThreadEOF". The "nuThreadIdx" and "fileOffset" fields will + * not be modified. + * + * Pre-sized data is always uncompressed, and doesn't have a CRC. This + * will copy the data, and then continue writing zeros to fill out the rest + * of the pre-sized buffer. + */ +NuError +Nu_CopyPresizedToArchive(NuArchive* pArchive, NuDataSource* pDataSource, + NuThreadID threadID, FILE* dstFp, NuThread* pThread, char** ppSavedCopy) +{ + NuError err = kNuErrNone; + NuStraw* pStraw = nil; + /*uchar* buffer = nil;*/ + ulong srcLen, bufferLen; + ulong count, getsize; + + srcLen = Nu_DataSourceGetDataLen(pDataSource); + bufferLen = Nu_DataSourceGetOtherLen(pDataSource); + if (bufferLen < srcLen) { + /* hey, this won't fit! */ + DBUG(("--- can't fit %lu into buffer of %lu!\n", srcLen, bufferLen)); + err = kNuErrPreSizeOverflow; + goto bail; + } + DBUG(("+++ copying %lu into buffer of %lu\n", srcLen, bufferLen)); + + pThread->thThreadClass = NuThreadIDGetClass(threadID); + pThread->thThreadFormat = kNuThreadFormatUncompressed; + pThread->thThreadKind = NuThreadIDGetKind(threadID); + pThread->thThreadCRC = 0; /* no CRC on pre-sized stuff */ + pThread->thThreadEOF = srcLen; + pThread->thCompThreadEOF = bufferLen; + pThread->actualThreadEOF = bufferLen; + /* nuThreadIdx and fileOffset should already be set */ + + /* + * Prepare to copy the data through a buffer. The "straw" thing + * is a convenient way to deal with the dataSource, even though we + * don't have a progress updater. + */ + err = Nu_StrawNew(pArchive, pDataSource, nil, &pStraw); + BailError(err); + + count = srcLen; + /*buffer = Nu_Malloc(pArchive, kNuFunnelBufSize);*/ + /*BailAlloc(buffer);*/ + err = Nu_AllocCompressionBufferIFN(pArchive); + BailError(err); + + while (count) { + getsize = (count > kNuGenCompBufSize) ? kNuGenCompBufSize : count; + + err = Nu_StrawRead(pArchive, pStraw, pArchive->compBuf, getsize); + BailError(err); + err = Nu_FWrite(dstFp, pArchive->compBuf, getsize); + BailError(err); + + if (ppSavedCopy != nil && *ppSavedCopy == nil) { + /* + * Grab a copy of the filename for our own use. This assumes + * that the filename fits in kNuGenCompBufSize, which is a + * pretty safe thing to assume. + */ + Assert(threadID == kNuThreadIDFilename); + Assert(count == getsize); + *ppSavedCopy = Nu_Malloc(pArchive, getsize+1); + BailAlloc(*ppSavedCopy); + memcpy(*ppSavedCopy, pArchive->compBuf, getsize); + (*ppSavedCopy)[getsize] = '\0'; /* make sure it's terminated */ + } + + count -= getsize; + } + + /* + * Pad out the rest of the buffer. Could probably do this more + * efficiently through the buffer we've allocated, but these regions + * tend to be either 32 or 200 bytes. + */ + count = bufferLen - srcLen; + while (count--) + Nu_WriteOne(pArchive, dstFp, 0); + +bail: + (void) Nu_StrawFree(pArchive, pStraw); + /*Nu_Free(pArchive, buffer);*/ + return err; +} + diff --git a/nufxlib-0/Crc16.c b/nufxlib-0/Crc16.c new file mode 100644 index 0000000..112853f --- /dev/null +++ b/nufxlib-0/Crc16.c @@ -0,0 +1,109 @@ +/* + * NuFX archive manipulation library + * 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 Library General Public License, see the file COPYING.LIB. + * + * Compute 16-bit CRCs. + */ +#define __Crc16_c__ 1 +#include "NufxLibPriv.h" + +#define CRC_TAB +#ifdef CRC_TAB +/* + * updcrc macro derived from article Copyright (C) 1986 Stephen Satchell. + * NOTE: First srgument must be in range 0 to 255. + * Second argument is referenced twice. + * + * Programmers may incorporate any or all code into their programs, + * giving proper credit within the source. Publication of the + * source routines is permitted so long as proper credit is given + * to Stephen Satchell, Satchell Evaluations and Chuck Forsberg, + * Omen Technology. + */ + + +/*#define updcrc(cp, crc) ( crctab[((crc >> 8) & 255)] ^ (crc << 8) ^ cp)*/ +#define updcrc(cp, crc) ( (crctab[((crc >> 8) & 0xFF) ^ cp] ^ (crc << 8)) & 0xFFFF) + + +/* crctab calculated by Mark G. Mendel, Network Systems Corporation */ +const ushort gNuCrc16Table[256] = { + 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7, + 0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef, + 0x1231, 0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6, + 0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de, + 0x2462, 0x3443, 0x0420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485, + 0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d, + 0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4, + 0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, 0xc7bc, + 0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823, + 0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b, + 0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12, + 0xdbfd, 0xcbdc, 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a, + 0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41, + 0xedae, 0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49, + 0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70, + 0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 0x9f59, 0x8f78, + 0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f, + 0x1080, 0x00a1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067, + 0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e, + 0x02b1, 0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256, + 0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d, + 0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, + 0xa7db, 0xb7fa, 0x8799, 0x97b8, 0xe75f, 0xf77e, 0xc71d, 0xd73c, + 0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634, + 0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab, + 0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, 0x28a3, + 0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a, + 0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92, + 0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9, + 0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1, + 0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8, + 0x6e17, 0x7e36, 0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0 +}; +#endif + + +/* + * Calculate CRC on a region + * + * A CRC is the result of a mathematical operation based on the + * coefficients of a polynomial when multiplied by X^16 then divided by + * the generator polynomial (X^16 + X^12 + X^5 + 1) using modulo two + * arithmetic. + * + * This routine is a slightly modified verison of one found in: + * _Advanced Programming Techniques for the Apple //gs Toolbox_ + * By Morgan Davis and Dan Gookin (Compute! Publications, Inc.) + * It can either calculate the CRC bit-by-bit or use a table. + * + * Depending on CPU architecture, one may be dramatically faster than + * the other. + */ +ushort +Nu_CalcCRC16(ushort seed, const uchar* ptr, int count) +{ + ushort CRC = seed; +#ifndef CRC_TAB + int x; + Assert(sizeof(ushort) == 2); /* I think this is assumed */ +#endif + + do { +#ifndef CRC_TAB + CRC ^= *ptr++ << 8; /* XOR hi-byte of CRC w/dat */ + for (x = 8; x; --x) /* Then, for 8 bit shifts... */ + if (CRC & 0x8000) /* Test hi order bit of CRC */ + CRC = CRC << 1 ^ 0x1021; /* if set, shift & XOR w/$1021 */ + else + CRC <<= 1; /* Else, just shift left once. */ +#else + CRC = Nu_UpdateCRC16(*ptr++, CRC); /* look up new value in table */ +#endif + } while (--count); + + return (CRC); +} + diff --git a/nufxlib-0/Debug.c b/nufxlib-0/Debug.c new file mode 100644 index 0000000..4077075 --- /dev/null +++ b/nufxlib-0/Debug.c @@ -0,0 +1,385 @@ +/* + * NuFX archive manipulation library + * 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 Library General Public License, see the file COPYING.LIB. + * + * Debugging functions. These are omitted from the non-debug build. + */ +#include "NufxLibPriv.h" + +#if defined(DEBUG_MSGS) + + +/* pull a string out of one of the static arrays */ +#define GetStaticString(index, staticArray) ( \ + (index) >= NELEM(staticArray) ? "" : staticArray[index] \ + ) + +/* thread's thread_class */ +static const char* gThreadClassNames[] = { + "message_thread", + "control_thread", + "data_thread", + "filename_thread", +}; + +/* thread's thread_format */ +static const char* gThreadFormatNames[] = { + "uncompressed", + "Huffman Squeeze", + "dynamic LZW/1", + "dynamic LZW/2", + "12-bit LZC", + "16-bit LZC", +}; + +/* days of the week */ +static const char* gDayNames[] = { + "[ null ]", + "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" +}; +/* months of the year */ +static const char* gMonths[] = { + "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" +}; + +#define kNuDateOutputLen 64 + +/* file_sys_id values */ +static const char* gFileSysIDs[] = { + "Reserved/unknown ($00)", "ProDOS/SOS", "DOS 3.3", "DOS 3.2", + "Apple II Pascal", "Macintosh (MFS)", "Macintosh (HFS)", + "LISA file system", "Apple CP/M", "Reserved 0x09", "MS-DOS", + "High-Sierra", "ISO 9660", "AppleShare" +}; + + +/* + * Convert a DateTime structure into something printable. + * + * The buffer passed in must hold at least kNuDateOutputLen bytes. + * + * Returns "buffer" for the benefit of printf() calls. + */ +static char* +Nu_DebugDumpDate(const NuDateTime* pDateTime, char* buffer) +{ + char* cp; + + /* is it valid? */ + if (pDateTime->day > 30 || pDateTime->month > 11 || pDateTime->hour > 24 || + pDateTime->minute > 59) + { + strcpy(buffer, " "); + goto bail; + } + + /* is it empty? */ + if ((pDateTime->second | pDateTime->minute | pDateTime->hour | + pDateTime->year | pDateTime->day | pDateTime->month | + pDateTime->extra | pDateTime->weekDay) == 0) + { + strcpy(buffer, " [No Date] "); + goto bail; + } + + cp = buffer; + + /* only print weekDay if one was stored */ + if (pDateTime->weekDay) { + if (pDateTime->weekDay < NELEM(gDayNames)) + sprintf(cp, "%s, ", gDayNames[pDateTime->weekDay]); + else + sprintf(cp, "??%d, ", pDateTime->weekDay); + cp += strlen(cp); + } + + sprintf(cp, "%02d-%s-%04d %02d:%02d:%02d", + pDateTime->day+1, gMonths[pDateTime->month], + pDateTime->year < 40 ? pDateTime->year + 2000 : pDateTime->year + 1900, + pDateTime->hour, pDateTime->minute, pDateTime->second); + +bail: + sprintf(buffer + strlen(buffer), " [s%d m%d h%d Y%d D%d M%d x%d w%d]", + pDateTime->second, pDateTime->minute, pDateTime->hour, + pDateTime->year, pDateTime->day, pDateTime->month, pDateTime->extra, + pDateTime->weekDay); + + return buffer; +} + + +/* + * Convert a buffer into a hexadecimal character string. + * + * The result will be 2x the size of the original, +1 for a null byte. + */ +static void +ConvertToHexStr(const uchar* inBuf, int inLen, char* outBuf) +{ + while (inLen--) { + *outBuf++ = HexConv((*inBuf >> 4) & 0x0f); + *outBuf++ = HexConv(*inBuf & 0x0f); + inBuf++; + } + *outBuf = '\0'; +} + + +/* + * Dump everything we know about pThread. + */ +void +Nu_DebugDumpThread(const NuThread* pThread) +{ + static const char* kInd = " "; + NuThreadID threadID; + const char* descr; + + Assert(pThread != nil); + + printf("%sThreadClass: 0x%04x (%s)\n", kInd, + pThread->thThreadClass, + GetStaticString(pThread->thThreadClass, gThreadClassNames)); + printf("%sThreadFormat: 0x%04x (%s)\n", kInd, + pThread->thThreadFormat, + GetStaticString(pThread->thThreadFormat, gThreadFormatNames)); + + threadID = NuMakeThreadID(pThread->thThreadClass, pThread->thThreadKind); + switch (threadID) { + case kNuThreadIDOldComment: descr = "old comment"; break; + case kNuThreadIDComment: descr = "comment"; break; + case kNuThreadIDIcon: descr = "icon"; break; + case kNuThreadIDMkdir: descr = "mkdir"; break; + case kNuThreadIDDataFork: descr = "data fork"; break; + case kNuThreadIDDiskImage: descr = "disk image"; break; + case kNuThreadIDRsrcFork: descr = "rsrc fork"; break; + case kNuThreadIDFilename: descr = "filename"; break; + default: descr = ""; break; + } + printf("%sThreadKind: 0x%04x (%s)\n", kInd, + pThread->thThreadKind, descr); + + printf("%sThreadCRC: 0x%04x ThreadEOF: %lu CompThreadEOF: %lu\n", kInd, + pThread->thThreadCRC, pThread->thThreadEOF, pThread->thCompThreadEOF); + printf("%s*File data offset: %ld actualThreadEOF: %ld\n", kInd, + pThread->fileOffset, pThread->actualThreadEOF); +} + +/* + * Dump everything we know about pRecord, including its threads and ThreadMods. + * + * Changes to existing records are made to the "copy" set, not the "orig" + * set. Pass in the "orig" copy in "pRecord", and optionally pass in the + * "copy" set in "pXrefRecord" to glean data from both. + */ +static void +Nu_DebugDumpRecord(NuArchive* pArchive, const NuRecord* pRecord, + const NuRecord* pXrefRecord, Boolean isDeleted) +{ + NuError err; /* dummy */ + static const char* kInd = " "; + char dateBuf[kNuDateOutputLen]; + const NuThreadMod* pThreadMod; + const NuThread* pThread; + ulong idx; + + Assert(pRecord != nil); + + printf("%s%s%sFilename: '%s' (idx=%lu)\n", kInd, + isDeleted ? "[DEL] " : "", + pXrefRecord != nil && pXrefRecord->pThreadMods != nil ? "[MOD] " : "", + pRecord->filename == nil ? "" : pRecord->filename, + pRecord->recordIdx); + printf("%sHeaderID: '%.4s' VersionNumber: 0x%04x HeaderCRC: 0x%04x\n", + kInd, + pRecord->recNufxID, pRecord->recVersionNumber, pRecord->recHeaderCRC); + printf("%sAttribCount: %u TotalThreads: %lu\n", kInd, + pRecord->recAttribCount, pRecord->recTotalThreads); + printf("%sFileSysID: %u (%s) FileSysInfo: 0x%04x ('%c')\n", kInd, + pRecord->recFileSysID, + GetStaticString(pRecord->recFileSysID, gFileSysIDs), + pRecord->recFileSysInfo, + NuGetSepFromSysInfo(pRecord->recFileSysInfo)); + /* do something fancy for ProDOS? */ + printf("%sFileType: 0x%08lx ExtraType: 0x%08lx Access: 0x%08lx\n", kInd, + pRecord->recFileType, pRecord->recExtraType, pRecord->recAccess); + printf("%sCreateWhen: %s\n", kInd, + Nu_DebugDumpDate(&pRecord->recCreateWhen, dateBuf)); + printf("%sModWhen: %s\n", kInd, + Nu_DebugDumpDate(&pRecord->recModWhen, dateBuf)); + printf("%sArchiveWhen: %s\n", kInd, + Nu_DebugDumpDate(&pRecord->recArchiveWhen, dateBuf)); + printf("%sStorageType: %u OptionSize: %u FilenameLength: %u\n", kInd, + pRecord->recStorageType, pRecord->recOptionSize, + pRecord->recFilenameLength); + if (pRecord->recOptionSize) { + char* outBuf = Nu_Malloc(pArchive, pRecord->recOptionSize * 2 +1); + BailAlloc(outBuf); + Assert(pRecord->recOptionList != nil); + ConvertToHexStr(pRecord->recOptionList, pRecord->recOptionSize, outBuf); + printf("%sOptionList: [%s]\n", kInd, outBuf); + Nu_Free(pArchive, outBuf); + } + + printf("%s*ExtraCount: %ld RecFileOffset: %ld RecHeaderLength: %ld\n", + kInd, + pRecord->extraCount, pRecord->fileOffset, pRecord->recHeaderLength); + + for (idx = 0; idx < pRecord->recTotalThreads; idx++) { + pThread = Nu_GetThread(pRecord, idx); + Assert(pThread != nil); + + printf("%s--Thread #%lu (idx=%lu)\n", kInd, idx, pThread->threadIdx); + Nu_DebugDumpThread(pThread); + } + + if (pXrefRecord != nil) + pThreadMod = pXrefRecord->pThreadMods; + else + pThreadMod = pRecord->pThreadMods; /* probably empty */ + + if (pThreadMod != nil) + printf("%s*ThreadMods -----\n", kInd); + while (pThreadMod != nil) { + switch (pThreadMod->entry.kind) { + case kNuThreadModAdd: + printf("%s *-ThreadMod ADD 0x%08lx 0x%04x (sourceType=%d)\n", kInd, + pThreadMod->entry.add.threadID, + pThreadMod->entry.add.threadFormat, + Nu_DataSourceGetType(pThreadMod->entry.add.pDataSource)); + break; + case kNuThreadModUpdate: + printf("%s *-ThreadMod UPDATE %06ld\n", kInd, + pThreadMod->entry.update.threadIdx); + break; + case kNuThreadModDelete: + printf("%s *-ThreadMod DELETE %06ld\n", kInd, + pThreadMod->entry.delete.threadIdx); + break; + case kNuThreadModUnknown: + default: + Assert(0); + printf("%s++ThreadMod UNKNOWN\n", kInd); + break; + } + + pThreadMod = pThreadMod->pNext; + } + + /*printf("%s*TotalLength: %ld TotalCompLength: %ld\n", + kInd, pRecord->totalLength, pRecord->totalCompLength);*/ + printf("%s*TotalCompLength: %ld\n", kInd, pRecord->totalCompLength); + printf("\n"); + +bail: + return; +} + +/* + * Dump the records in a RecordSet. + */ +static void +Nu_DebugDumpRecordSet(NuArchive* pArchive, const NuRecordSet* pRecordSet, + const NuRecordSet* pXrefSet) +{ + const NuRecord* pRecord; + const NuRecord* pXrefRecord; + Boolean doXref; + long count; + + doXref = false; + pXrefRecord = nil; + if (pXrefSet != nil && Nu_RecordSet_GetLoaded(pXrefSet)) { + pXrefRecord = Nu_RecordSet_GetListHead(pXrefSet); + doXref = true; + } + + /* dump every record, if we've loaded them */ + count = Nu_RecordSet_GetNumRecords(pRecordSet); + pRecord = Nu_RecordSet_GetListHead(pRecordSet); + if (pRecord != nil) { + Assert(count != 0); + while (count--) { + Assert(pRecord != nil); + + if (pXrefRecord != nil && + pRecord->recordIdx == pXrefRecord->recordIdx) + { + Nu_DebugDumpRecord(pArchive, pRecord, pXrefRecord, false); + pXrefRecord = pXrefRecord->pNext; + } else { + Nu_DebugDumpRecord(pArchive, pRecord, nil, doXref); + } + pRecord = pRecord->pNext; + } + } else { + Assert(count == 0); + } +} + +/* + * Dump the master header block. + */ +static void +Nu_DebugDumpMH(const NuMasterHeader* pMasterHeader) +{ + static const char* kInd = " "; + char dateBuf1[kNuDateOutputLen]; + + Assert(pMasterHeader != nil); + + printf("%sNufileID: '%.6s' MasterCRC: 0x%04x TotalRecords: %lu\n", kInd, + pMasterHeader->mhNufileID, pMasterHeader->mhMasterCRC, + pMasterHeader->mhTotalRecords); + printf("%sArchiveCreateWhen: %s\n", kInd, + Nu_DebugDumpDate(&pMasterHeader->mhArchiveCreateWhen, dateBuf1)); + printf("%sArchiveModWhen: %s\n", kInd, + Nu_DebugDumpDate(&pMasterHeader->mhArchiveModWhen, dateBuf1)); + printf("%sMasterVersion: %u MasterEOF: %lu\n", kInd, + pMasterHeader->mhMasterVersion, pMasterHeader->mhMasterEOF); +} + +/* + * Dump everything we know about pArchive. + * + * This will only print the records that we have seen so far. If the + * application hasn't caused us to scan through all of the records in + * the archive, then this won't be very interesting. This will never + * show any records for streaming-mode archives. + */ +void +Nu_DebugDumpAll(NuArchive* pArchive) +{ + Assert(pArchive != nil); + + printf("*Archive pathname: '%s'\n", pArchive->archivePathname); + printf("*Archive type: %d\n", pArchive->archiveType); + printf("*Header offset: %ld\n", pArchive->headerOffset); + printf("*Num records: %ld orig, %ld copy, %ld new\n", + Nu_RecordSet_GetNumRecords(&pArchive->origRecordSet), + Nu_RecordSet_GetNumRecords(&pArchive->copyRecordSet), + Nu_RecordSet_GetNumRecords(&pArchive->newRecordSet)); + printf("*NuRecordIdx seed: %lu NuRecordIdx next: %lu\n", + pArchive->recordIdxSeed, pArchive->nextRecordIdx); + + /* master header */ + Nu_DebugDumpMH(&pArchive->masterHeader); + + printf(" *ORIG record set (x-ref with COPY):\n"); + Nu_DebugDumpRecordSet(pArchive, &pArchive->origRecordSet, + &pArchive->copyRecordSet); + printf(" *NEW record set:\n"); + Nu_DebugDumpRecordSet(pArchive, &pArchive->newRecordSet, nil); + + if (!Nu_RecordSet_GetLoaded(&pArchive->origRecordSet) && + !Nu_RecordSet_GetLoaded(&pArchive->newRecordSet)) + { + printf("*** DEBUG: original records not loaded yet? ***\n"); + } + +} + +#endif /*DEBUG_MSGS*/ diff --git a/nufxlib-0/Deferred.c b/nufxlib-0/Deferred.c new file mode 100644 index 0000000..2c84aaf --- /dev/null +++ b/nufxlib-0/Deferred.c @@ -0,0 +1,2495 @@ +/* + * NuFX archive manipulation library + * 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 Library General Public License, see the file COPYING.LIB. + * + * Deferred write handling. + */ +#include "NufxLibPriv.h" + + +/* + * =========================================================================== + * NuThreadMod functions + * =========================================================================== + */ + +/* + * Alloc and initialize a new "add" ThreadMod. + * + * Caller is allowed to dispose of the data source, as this makes a copy. + * + * NOTE: threadFormat is how you want the data to be compressed. The + * threadFormat passed to DataSource describes the source data. + */ +NuError +Nu_ThreadModAdd_New(NuArchive* pArchive, NuThreadID threadID, + NuThreadFormat threadFormat, NuDataSource* pDataSource, + NuThreadMod** ppThreadMod) +{ + Assert(ppThreadMod != nil); + Assert(pDataSource != nil); + + *ppThreadMod = Nu_Calloc(pArchive, sizeof(**ppThreadMod)); + if (*ppThreadMod == nil) + return kNuErrMalloc; + + (*ppThreadMod)->entry.kind = kNuThreadModAdd; + (*ppThreadMod)->entry.add.threadIdx = Nu_GetNextThreadIdx(pArchive); + (*ppThreadMod)->entry.add.threadID = threadID; + (*ppThreadMod)->entry.add.threadFormat = threadFormat; + (*ppThreadMod)->entry.add.pDataSource = pDataSource; + + /* decide if this is a pre-sized thread [do we want to do this here??] */ + (*ppThreadMod)->entry.add.isPresized = Nu_IsPresizedThreadID(threadID); + + return kNuErrNone; +} + +/* + * Alloc and initialize a new "update" ThreadMod. + * + * Caller is allowed to dispose of the data source. + */ +NuError +Nu_ThreadModUpdate_New(NuArchive* pArchive, NuThreadIdx threadIdx, + NuDataSource* pDataSource, NuThreadMod** ppThreadMod) +{ + Assert(ppThreadMod != nil); + Assert(pDataSource != nil); + + *ppThreadMod = Nu_Calloc(pArchive, sizeof(**ppThreadMod)); + if (*ppThreadMod == nil) + return kNuErrMalloc; + + (*ppThreadMod)->entry.kind = kNuThreadModUpdate; + (*ppThreadMod)->entry.update.threadIdx = threadIdx; + (*ppThreadMod)->entry.update.pDataSource = Nu_DataSourceCopy(pDataSource); + + return kNuErrNone; +} + +/* + * Alloc and initialize a new "delete" ThreadMod. + * + * The "threadID" argument is really only needed for filename threads. We + * use it when trying to track how many filename threads we really have. + */ +NuError +Nu_ThreadModDelete_New(NuArchive* pArchive, NuThreadIdx threadIdx, + NuThreadID threadID, NuThreadMod** ppThreadMod) +{ + Assert(ppThreadMod != nil); + + *ppThreadMod = Nu_Calloc(pArchive, sizeof(**ppThreadMod)); + if (*ppThreadMod == nil) + return kNuErrMalloc; + + (*ppThreadMod)->entry.kind = kNuThreadModDelete; + (*ppThreadMod)->entry.delete.threadIdx = threadIdx; + (*ppThreadMod)->entry.delete.threadID = threadID; + + return kNuErrNone; +} + +/* + * Free a single NuThreadMod. + */ +void +Nu_ThreadModFree(NuArchive* pArchive, NuThreadMod* pThreadMod) +{ + if (pThreadMod == nil) + return; + + switch (pThreadMod->entry.kind) { + case kNuThreadModAdd: + Nu_DataSourceFree(pThreadMod->entry.add.pDataSource); + break; + case kNuThreadModUpdate: + Nu_DataSourceFree(pThreadMod->entry.update.pDataSource); + break; + default: + break; + } + + Nu_Free(pArchive, pThreadMod); +} + + +/* + * Return a threadMod with a matching "threadIdx", if any. Because "add" + * threads can't have a threadIdx that matches an existing thread, this + * will only return updates and deletes. + * + * We don't allow more than one threadMod on the same thread, so we don't + * have to deal with having more than one match. (To be safe, we go + * ahead and do debug-only checks for multiple matches. There shouldn't + * be more than three or four threads per record, so the extra search + * isn't costly.) + * + * Returns "nil" if nothing found. + */ +NuThreadMod* +Nu_ThreadMod_FindByThreadIdx(const NuRecord* pRecord, NuThreadIdx threadIdx) +{ + NuThreadMod* pThreadMod; + NuThreadMod* pMatch = nil; + + pThreadMod = pRecord->pThreadMods; + while (pThreadMod) { + switch (pThreadMod->entry.kind) { + case kNuThreadModAdd: + /* can't happen */ + Assert(pThreadMod->entry.add.threadIdx != threadIdx); + break; + case kNuThreadModUpdate: + if (pThreadMod->entry.update.threadIdx == threadIdx) { + Assert(pMatch == nil); + pMatch = pThreadMod; + } + break; + case kNuThreadModDelete: + if (pThreadMod->entry.delete.threadIdx == threadIdx) { + Assert(pMatch == nil); + pMatch = pThreadMod; + } + break; + default: + Assert(0); + /* keep going, I guess */ + } + pThreadMod = pThreadMod->pNext; + } + + return pMatch; +} + + +/* + * =========================================================================== + * ThreadMod list operations + * =========================================================================== + */ + +/* + * Search for an "add" ThreadMod, by threadID. + */ +NuError +Nu_ThreadModAdd_FindByThreadID(const NuRecord* pRecord, NuThreadID threadID, + NuThreadMod** ppThreadMod) +{ + NuThreadMod* pThreadMod; + + Assert(pRecord != nil); + Assert(ppThreadMod != nil); + + pThreadMod = pRecord->pThreadMods; + while (pThreadMod != nil) { + if (pThreadMod->entry.kind != kNuThreadModAdd) + continue; + + if (pThreadMod->entry.add.threadID == threadID) { + *ppThreadMod = pThreadMod; + return kNuErrNone; + } + + pThreadMod = pThreadMod->pNext; + } + + return kNuErrNotFound; +} + + +/* + * Free up the list of NuThreadMods in this record. + */ +void +Nu_FreeThreadMods(NuArchive* pArchive, NuRecord* pRecord) +{ + NuThreadMod* pThreadMod; + NuThreadMod* pNext; + + Assert(pRecord != nil); + pThreadMod = pRecord->pThreadMods; + + if (pThreadMod == nil) + return; + + while (pThreadMod != nil) { + pNext = pThreadMod->pNext; + + Nu_ThreadModFree(pArchive, pThreadMod); + pThreadMod = pNext; + } + + pRecord->pThreadMods = nil; +} + + +/* + * =========================================================================== + * Temporary structure for holding updated thread info + * =========================================================================== + */ + +/* used when constructing a new set of threads */ +typedef struct { + int numThreads; /* max #of threads */ + int nextSlot; /* where the next one goes */ + NuThread* pThreads; /* static-sized array */ +} NuNewThreads; + +/* + * Allocate and initialize a NuNewThreads struct. + */ +static NuError +Nu_NewThreads_New(NuArchive* pArchive, NuNewThreads** ppNewThreads, + long numThreads) +{ + NuError err = kNuErrNone; + + *ppNewThreads = Nu_Malloc(pArchive, sizeof(**ppNewThreads)); + BailAlloc(*ppNewThreads); + (*ppNewThreads)->numThreads = numThreads; + (*ppNewThreads)->nextSlot = 0; + (*ppNewThreads)->pThreads = Nu_Malloc(pArchive, numThreads * sizeof(NuThread)); + BailAlloc((*ppNewThreads)->pThreads); + +bail: + return err; +} + +/* + * Free a NuNewThreads struct. + */ +static void +Nu_NewThreads_Free(NuArchive* pArchive, NuNewThreads* pNewThreads) +{ + if (pNewThreads != nil) { + Nu_Free(pArchive, pNewThreads->pThreads); + Nu_Free(pArchive, pNewThreads); + } +} + +/* + * Returns true if "pNewThreads" has room for another entry, false otherwise. + */ +static Boolean +Nu_NewThreads_HasRoom(const NuNewThreads* pNewThreads) +{ + if (pNewThreads->nextSlot < pNewThreads->numThreads) + return true; + else + return false; +} + +/* + * Get the next available slot. The contents of the slot are first + * initialized. + * + * The "next slot" marker is automatically advanced. + */ +static NuThread* +Nu_NewThreads_GetNext(NuNewThreads* pNewThreads, NuArchive* pArchive) +{ + NuThread* pThread; + + pThread = &pNewThreads->pThreads[pNewThreads->nextSlot]; + memset(pThread, 0, sizeof(*pThread)); + + pThread->fileOffset = -1; /* mark as invalid */ + + /* advance slot */ + pNewThreads->nextSlot++; + Assert(pNewThreads->nextSlot <= pNewThreads->numThreads); + + return pThread; +} + +/* + * Return the #of threads we're meant to hold. + */ +static int +Nu_NewThreads_GetNumThreads(const NuNewThreads* pNewThreads) +{ + Assert(pNewThreads != nil); + + return pNewThreads->numThreads; +} + +/* + * Total up the compressed EOFs of all threads. + */ +static ulong +Nu_NewThreads_TotalCompThreadEOF(NuNewThreads* pNewThreads) +{ + ulong compThreadEOF; + int i; + + /* we should be all full up at this point; if not, we have a bug */ + Assert(pNewThreads != nil); + Assert(pNewThreads->numThreads == pNewThreads->nextSlot); + + compThreadEOF = 0; + for (i = 0; i < pNewThreads->numThreads; i++) + compThreadEOF += pNewThreads->pThreads[i].thCompThreadEOF; + + return compThreadEOF; +} + + +/* + * "Donate" the thread collection to the caller. This returns a pointer + * to the thread array, and then nukes our copy of the pointer. This + * allows us to transfer ownership of the storage to the caller. + */ +static NuThread* +Nu_NewThreads_DonateThreads(NuNewThreads* pNewThreads) +{ + NuThread* pThreads = pNewThreads->pThreads; + + pNewThreads->pThreads = nil; + return pThreads; +} + + +/* + * =========================================================================== + * Archive construction - Record-level functions + * =========================================================================== + */ + +/* + * Copy an entire record (threads and all) from the source archive to the + * current offset in the temp file. + * + * Pass in the record from the *copy* set, not the original. + */ +static NuError +Nu_CopyArchiveRecord(NuArchive* pArchive, NuRecord* pRecord) +{ + NuError err = kNuErrNone; + long offsetAdjust; + long outputOffset; + int i; + + err = Nu_FTell(pArchive->tmpFp, &outputOffset); + BailError(err); + offsetAdjust = outputOffset - pRecord->fileOffset; + + DBUG(("--- Copying record '%s' (adj=%ld)\n", pRecord->filename, + offsetAdjust)); + + /* seek to the start point in the source file, and copy the whole thing */ + err = Nu_FSeek(pArchive->archiveFp, pRecord->fileOffset, SEEK_SET); + BailError(err); + err = Nu_CopyFileSection(pArchive, pArchive->tmpFp, pArchive->archiveFp, + pRecord->recHeaderLength + pRecord->totalCompLength); + BailError(err); + + /* adjust the file offsets in the record header and in the threads */ + pRecord->fileOffset += offsetAdjust; + + for (i = 0; i < (int)pRecord->recTotalThreads; i++) { + NuThread* pThread = Nu_GetThread(pRecord, i); + + pThread->fileOffset += offsetAdjust; + } + + Assert(outputOffset + pRecord->recHeaderLength + pRecord->totalCompLength == + (ulong)ftell(pArchive->tmpFp)); + +bail: + return err; +} + + +/* + * Count the number of threads that will eventually inhabit this record. + * + * Returns -1 on error. + */ +static NuError +Nu_CountEventualThreads(const NuRecord* pRecord, long* pTotalThreads, + long* pFilenameThreads) +{ + const NuThreadMod* pThreadMod; + const NuThread* pThread; + long idx, numThreads, numFilenameThreads; + + /* + * Number of threads is equal to: + * the number of existing threads + * MINUS the number of "delete" threadMods (you can't delete the same + * thread more than once) + * PLUS the number of "add" threadMods + */ + numThreads = pRecord->recTotalThreads; + numFilenameThreads = 0; + + pThreadMod = pRecord->pThreadMods; + while (pThreadMod != nil) { + switch (pThreadMod->entry.kind) { + case kNuThreadModAdd: + numThreads++; + if (pThreadMod->entry.add.threadID == kNuThreadIDFilename) + numFilenameThreads++; + break; + case kNuThreadModDelete: + numThreads--; + if (pThreadMod->entry.delete.threadID == kNuThreadIDFilename) + numFilenameThreads--; + break; + case kNuThreadModUpdate: + break; + default: + Assert(0); + break; + } + + pThreadMod = pThreadMod->pNext; + } + + /* + * If the record has more than one filename thread, we only keep + * the first one, so remove it from our accounting here. It should + * not have been possible to add a new filename thread when an + * existing one was present, so we don't check the threadMods. + */ + for (idx = 0; idx < (long)pRecord->recTotalThreads; idx++) { + pThread = Nu_GetThread(pRecord, idx); + Assert(pThread != nil); + + if (NuGetThreadID(pThread) == kNuThreadIDFilename) + numFilenameThreads++; + } + Assert(numFilenameThreads >= 0); + if (numFilenameThreads > 1) { + DBUG(("--- ODD: found multiple filename threads (%ld)\n", + numFilenameThreads)); + numThreads -= (numFilenameThreads -1); + } + + /* + * Records with no threads should've been screened out already. + */ + if (numThreads <= 0) + return kNuErrInternal; + + *pTotalThreads = numThreads; + *pFilenameThreads = numFilenameThreads; /* [should cap this at 1?] */ + return kNuErrNone; +} + + +/* + * Verify that all of the threads and threadMods in a record have + * been touched. This is done after the record has been written to + * the destination archive, in order to ensure that we don't leave + * anything behind. + * + * All items, including things like duplicate filename threads that + * we ignore, are marked "used" during processing, so we don't need + * to be terribly bright here. + */ +static Boolean +Nu_VerifyAllTouched(NuArchive* pArchive, const NuRecord* pRecord) +{ + const NuThreadMod* pThreadMod; + const NuThread* pThread; + long idx; + + Assert(pArchive != nil); + Assert(pRecord != nil); + + pThreadMod = pRecord->pThreadMods; + while (pThreadMod != nil) { + if (!pThreadMod->entry.generic.used) + return false; + pThreadMod = pThreadMod->pNext; + } + + for (idx = 0; idx < (long)pRecord->recTotalThreads; idx++) { + pThread = Nu_GetThread(pRecord, idx); + Assert(pThread != nil); + + if (!pThread->used) + return false; + } + + return true; +} + + +/* + * Set the threadFilename field of a record to a new value. This does + * not affect the record header filename. + * + * This call should only be made after an "add" or "update" threadMod has + * successfully completed. + * + * "newName" must be allocated storage. + */ +static void +Nu_SetNewThreadFilename(NuArchive* pArchive, NuRecord* pRecord, char* newName) +{ + Assert(pRecord != nil); + Assert(newName != nil); + + Nu_Free(pArchive, pRecord->threadFilename); + pRecord->threadFilename = newName; + pRecord->filename = pRecord->threadFilename; +} + +/* + * If this is a disk image, we require that the uncompressed length + * be equal to recExtraType * recStorageType (where recStorageType + * is the block size, usually 512). If they haven't set those to + * appropriate values, we'll set them on their behalf, so long as + * the uncompressed size is a multiple of 512. + */ +static NuError +Nu_UpdateDiskImageFields(NuArchive* pArchive, NuRecord* pRecord, long sourceLen) +{ + NuError err = kNuErrNone; + long actualLen; + + if (pRecord->recStorageType <= 13) + pRecord->recStorageType = 512; + actualLen = pRecord->recExtraType * pRecord->recStorageType; + + if (actualLen != sourceLen) { + /* didn't match, see if we can fix it */ + DBUG(("--- fixing up disk image size\n")); + if ((sourceLen & 0x1ff) == 0) { + pRecord->recStorageType = 512; + pRecord->recExtraType = sourceLen / 512; + } else { + /* oh dear */ + err = kNuErrBadData; + Nu_ReportError(NU_BLOB, kNuErrNone,"disk image size of %ld invalid", + sourceLen); + /* fall through and out */ + } + } + + return err; +} + +/* + * As part of thread construction or in-place updating, handle a single + * "update" threadMod. We have an existing thread, and are replacing + * the contents of it with new data. + * + * "pThread" is a thread from the copy list or a "new" thread (a copy of + * the thread from the "copy" list), and "pThreadMod" is a threadMod that + * effects pThread. + * + * "fp" is a pointer into the archive at the offset where the data is + * to be written. On exit, "fp" will point past the end of the pre-sized + * buffer. + * + * Possible side-effects on "pRecord": threadFilename may be updated. + */ +static NuError +Nu_ConstructArchiveUpdate(NuArchive* pArchive, FILE* fp, NuRecord* pRecord, + NuThread* pThread, const NuThreadMod* pThreadMod) +{ + NuError err; + NuDataSource* pDataSource = nil; + ulong sourceLen; + ulong threadBufSize; + + /* + * We're going to copy the data out of the data source. Because + * "update" actions only operate on pre-sized chunks, and the data + * is never compressed, this should be straightforward. However, + * we do need to make sure that the data will fit. + * + * I expect these to be small, and it's just a raw data copy, so no + * progress updater is used. + */ + Assert(Nu_IsPresizedThreadID(NuGetThreadID(pThread))); + Assert(pThread->thCompThreadEOF >= pThread->thThreadEOF); + threadBufSize = pThread->thCompThreadEOF; + pDataSource = pThreadMod->entry.update.pDataSource; + Assert(pDataSource != nil); + + err = Nu_DataSourcePrepareInput(pArchive, pDataSource); + if (err == kNuErrSkipped) { + /* something failed (during file open?), just skip this one */ + DBUG(("--- skipping pre-sized thread update to %ld\n", + pThread->threadIdx)); + err = kNuErrNone; + goto skip_update; + } else if (err != kNuErrNone) + goto bail; + + /* + * Check to see if the data will fit. In some cases we can verify + * the size during the UpdatePresizedThread call, but if it's being + * added from a file we can't tell until now. + * + * We could be nice and give the user a chance to do something about + * this, but frankly the application should have checked the file + * size before handing it to us. + */ + sourceLen = Nu_DataSourceGetDataLen(pDataSource); + if (sourceLen > pThread->thCompThreadEOF) { + err = kNuErrPreSizeOverflow; + Nu_ReportError(NU_BLOB, err, "can't fit %ld bytes into %ld-byte buffer", + sourceLen, pThread->thCompThreadEOF); + goto bail; + } + + /* + * During an update operation, the user's specification of "otherLen" + * doesn't really matter, because we're not going to change the size + * of the region in the archive. However, this size *is* used by + * the code to figure out how big the buffer should be, and will + * determine where the file pointer ends up when the call returns. + * So, we jam in the "real" value. + */ + Nu_DataSourceSetOtherLen(pDataSource, pThread->thCompThreadEOF); + + if (NuGetThreadID(pThread) == kNuThreadIDFilename) { + /* special handling for filename updates */ + char* savedCopy = nil; + err = Nu_CopyPresizedToArchive(pArchive, pDataSource, + NuGetThreadID(pThread), fp, pThread, &savedCopy); + if (err != kNuErrNone) { + Nu_ReportError(NU_BLOB, err, "thread update failed"); + goto bail; + } + Nu_SetNewThreadFilename(pArchive, pRecord, savedCopy); + + } else { + err = Nu_CopyPresizedToArchive(pArchive, pDataSource, + NuGetThreadID(pThread), fp, pThread, nil); + if (err != kNuErrNone) { + Nu_ReportError(NU_BLOB, err, "thread update failed"); + goto bail; + } + } + Assert((ulong)ftell(fp) == pThread->fileOffset + threadBufSize); + +skip_update: + Nu_DataSourceUnPrepareInput(pArchive, pDataSource); + +bail: + return err; +} + + +/* + * Handle all "add" threadMods in the current record. This is invoked both + * when creating a new record from the "new" list or constructing a + * modified record from the "copy" list. + * + * Writes to either the archiveFp or tmpFp; pass in the correct one, + * properly positioned. + * + * If something goes wrong with one of the "adds", this will return + * immediately with kNuErrSkipped. The caller is expected to abort the + * entire record, so there's no point in continuing to process other + * threads. + * + * Possible side-effects on "pRecord": disk image fields may be revised + * (storage type, extra type), and threadFilename may be updated. + */ +static NuError +Nu_HandleAddThreadMods(NuArchive* pArchive, NuRecord* pRecord, + NuThreadID threadID, Boolean doKeepFirstOnly, NuNewThreads* pNewThreads, + FILE* dstFp) +{ + NuError err = kNuErrNone; + + NuProgressData progressData; + NuProgressData* pProgressData; + NuThreadMod* pThreadMod; + NuThread* pNewThread; + Boolean foundOne = false; + + /* + * Now find all "add" threadMods with matching threadIDs. Allow + * matching by wildcards, but don't re-use "used" entries. + */ + pThreadMod = pRecord->pThreadMods; + while (pThreadMod != nil) { + if (pThreadMod->entry.kind == kNuThreadModAdd && + !pThreadMod->entry.generic.used && + (pThreadMod->entry.add.threadID == threadID || + threadID == kNuThreadIDWildcard)) + { + DBUG(("+++ found ADD for 0x%08lx\n", pThreadMod->entry.add.threadID)); + pThreadMod->entry.generic.used = true; + + /* if we're adding filename threads, stop after first one */ + /* [shouldn't be able to happen... we only allow one filename!] */ + if (doKeepFirstOnly && foundOne) { + assert(0); /* can this happen?? */ + continue; + } + foundOne = true; + + if (!Nu_NewThreads_HasRoom(pNewThreads)) { + Assert(0); + err = kNuErrInternal; + goto bail; + } + + /* if this is a data thread, prepare the progress message */ + pProgressData = nil; + if (NuThreadIDGetClass(pThreadMod->entry.add.threadID) == + kNuThreadClassData) + { + /* + * We're going to show the name as it appears in the + * archive, rather than the name of the file we're + * reading the data out of. We could do this differently + * for a "file" data source, but we might as well be + * consistent. + */ + err = Nu_ProgressDataInit_Compress(pArchive, &progressData, + pRecord, pRecord->filename); + BailError(err); + + /* send initial progress so they see name if "open" fails */ + progressData.state = kNuProgressOpening; + err = Nu_SendInitialProgress(pArchive, &progressData); + BailError(err); + + pProgressData = &progressData; + } + + /* get new thread storage, and init the thread's data offset */ + /* (the threadIdx is set by GetNext) */ + pNewThread = Nu_NewThreads_GetNext(pNewThreads, pArchive); + pNewThread->threadIdx = pThreadMod->entry.add.threadIdx; + err = Nu_FTell(dstFp, &pNewThread->fileOffset); + BailError(err); + + /* this returns kNuErrSkipped if user elects to skip */ + err = Nu_DataSourcePrepareInput(pArchive, + pThreadMod->entry.add.pDataSource); + BailError(err); + + /* + * If they're adding a disk image thread, make sure the disk- + * related fields in the record header are correct. + */ + if (pThreadMod->entry.add.threadID == kNuThreadIDDiskImage) { + const NuDataSource* pDataSource = + pThreadMod->entry.add.pDataSource; + ulong uncompLen; + + if (Nu_DataSourceGetThreadFormat(pDataSource) == + kNuThreadFormatUncompressed) + { + uncompLen = Nu_DataSourceGetDataLen(pDataSource); + } else { + uncompLen = Nu_DataSourceGetOtherLen(pDataSource); + } + + err = Nu_UpdateDiskImageFields(pArchive, pRecord, uncompLen); + BailError(err); + } + + if (Nu_DataSourceGetType(pThreadMod->entry.add.pDataSource) == + kNuDataSourceFromFile) + { + DBUG(("+++ ADDING from '%s' for '%s' (idx=%ld id=0x%08lx)\n", + Nu_DataSourceFile_GetPathname(pThreadMod->entry.add.pDataSource), + pRecord->filename, + pThreadMod->entry.add.threadIdx, + pThreadMod->entry.add.threadID)); + } else { + DBUG(("+++ ADDING from (type=%d) for '%s' (idx=%ld id=0x%08lx)\n", + Nu_DataSourceGetType(pThreadMod->entry.add.pDataSource), + pRecord->filename, + pThreadMod->entry.add.threadIdx, + pThreadMod->entry.add.threadID)); + } + + if (pThreadMod->entry.add.threadID == kNuThreadIDFilename) { + /* filenames are special */ + char* savedCopy = nil; + + Assert(pThreadMod->entry.add.threadFormat == + kNuThreadFormatUncompressed); + err = Nu_CopyPresizedToArchive(pArchive, + pThreadMod->entry.add.pDataSource, + pThreadMod->entry.add.threadID, + dstFp, pNewThread, &savedCopy); + if (err != kNuErrNone) { + Nu_ReportError(NU_BLOB, err, "fn thread add failed"); + goto bail; + } + /* NOTE: on failure, "dropRecFilename" is still set. This + doesn't matter though, since we'll either copy the original + record, or abort the entire thing. At any rate, we can't + just clear it, because we've already made space for the + record header, and didn't include the filename in it. */ + + Nu_SetNewThreadFilename(pArchive, pRecord, savedCopy); + + } else if (pThreadMod->entry.add.isPresized) { + /* don't compress, just copy */ + Assert(pThreadMod->entry.add.threadFormat == + kNuThreadFormatUncompressed); + err = Nu_CopyPresizedToArchive(pArchive, + pThreadMod->entry.add.pDataSource, + pThreadMod->entry.add.threadID, + dstFp, pNewThread, nil); + /* fall through with err */ + + } else { + /* compress (possibly by just copying) the source to dstFp */ + err = Nu_CompressToArchive(pArchive, + pThreadMod->entry.add.pDataSource, + pThreadMod->entry.add.threadID, + Nu_DataSourceGetThreadFormat( + pThreadMod->entry.add.pDataSource), + pThreadMod->entry.add.threadFormat, + pProgressData, dstFp, pNewThread); + /* fall through with err */ + } + + if (err != kNuErrNone) { + Nu_ReportError(NU_BLOB, err, "thread add failed"); + goto bail; + } + Nu_DataSourceUnPrepareInput(pArchive, + pThreadMod->entry.add.pDataSource); + } + + pThreadMod = pThreadMod->pNext; + } + +bail: + return err; +} + +/* + * Run through the list of threads and threadMods, looking for threads + * with an ID that matches "threadID". When one is found, we take all + * the appropriate steps to get the data into the archive. + * + * This takes into account the ThreadMods, including "delete" (ignore + * existing thread), "update" (use data from threadMod instead of + * existing thread), and "add" (use data from threadMod). + * + * Threads that are used or discarded will have a flag set so that + * future examinations, notably those where "threadID" is a wildcard, + * will ignore them. + * + * Always writes to the temp file. The temp file must be positioned in + * the proper location. + * + * "pRecord" must be from the "copy" data set. + */ +static NuError +Nu_ConstructArchiveThreads(NuArchive* pArchive, NuRecord* pRecord, + NuThreadID threadID, Boolean doKeepFirstOnly, NuNewThreads* pNewThreads) +{ + NuError err = kNuErrNone; + NuThread* pThread; + NuThreadMod* pThreadMod; + Boolean foundOne = false; + NuThread* pNewThread; + int idx; + + /* + * First, find any existing threads that match. If they have a + * "delete" threadMod, ignore them; if they have an "update" threadMod, + * use that instead. + */ + for (idx = 0; idx < (int)pRecord->recTotalThreads; idx++) { + pThread = Nu_GetThread(pRecord, idx); + Assert(pThread != nil); + + DBUG(("+++ THREAD #%d (used=%d)\n", idx, pThread->used)); + if (threadID == kNuThreadIDWildcard || + threadID == NuGetThreadID(pThread)) + { + /* match! */ + DBUG(("+++ MATCH THREAD #%d\n", idx)); + if (pThread->used) + continue; + pThread->used = true; /* no matter what, we're done with this */ + + pThreadMod = Nu_ThreadMod_FindByThreadIdx(pRecord, + pThread->threadIdx); + + if (pThreadMod != nil) { + /* + * The thread has a related ThreadMod. Deal with it. + */ + + pThreadMod->entry.generic.used = true; /* for assert, later */ + + if (pThreadMod->entry.kind == kNuThreadModDelete) { + /* this is a delete, ignore this thread */ + DBUG(("+++ deleted %ld!\n", pThread->threadIdx)); + continue; + } else if (pThreadMod->entry.kind == kNuThreadModUpdate) { + /* update pre-sized data in place */ + + DBUG(("+++ updating threadIdx=%ld\n", + pThread->threadIdx)); + + /* only one filename per customer */ + /* [does this make sense here??] */ + if (doKeepFirstOnly && foundOne) + continue; + foundOne = true; + + /* add an entry in the new list of threads */ + pNewThread = Nu_NewThreads_GetNext(pNewThreads, pArchive); + Nu_CopyThreadContents(pNewThread, pThread); + + /* set the thread's file offset */ + err = Nu_FTell(pArchive->tmpFp, &pNewThread->fileOffset); + BailError(err); + + err = Nu_ConstructArchiveUpdate(pArchive, pArchive->tmpFp, + pRecord, pNewThread, pThreadMod); + BailError(err); + } else { + /* unknown ThreadMod type - this shouldn't happen! */ + Assert(0); + err = kNuErrInternal; + goto bail; + } + } else { + /* + * Thread is unmodified. + */ + + /* only one filename per customer */ + if (doKeepFirstOnly && foundOne) + continue; + foundOne = true; + + /* + * Copy the original data to the new location. Right now, + * pThread->fileOffset has the correct offset for the + * original file, and tmpFp is positioned at the correct + * output offset. We want to seek the source file, replace + * pThread->fileOffset with the *new* offset, and then + * copy the data. + * + * This feels skankier than it really is because we're + * using the thread in the "copy" set for two purposes. + * It'd be cleaner to pass in the thread from the "orig" + * set, but there's really not much value in doing so. + * + * [should this have a progress meter associated?] + */ + DBUG(("+++ just copying threadIdx=%ld\n", + pThread->threadIdx)); + err = Nu_FSeek(pArchive->archiveFp, pThread->fileOffset, + SEEK_SET); + BailError(err); + err = Nu_FTell(pArchive->tmpFp, &pThread->fileOffset); + BailError(err); + err = Nu_CopyFileSection(pArchive, pArchive->tmpFp, + pArchive->archiveFp, pThread->thCompThreadEOF); + BailError(err); + + /* copy an entry over into the replacement thread list */ + pNewThread = Nu_NewThreads_GetNext(pNewThreads, pArchive); + Nu_CopyThreadContents(pNewThread, pThread); + } + } + } + + /* no need to check for "add" mods; there can't be one for us */ + if (doKeepFirstOnly && foundOne) + goto bail; + + /* + * Now handle any "add" threadMods. + */ + err = Nu_HandleAddThreadMods(pArchive, pRecord, threadID, doKeepFirstOnly, + pNewThreads, pArchive->tmpFp); + BailError(err); + +bail: + return err; +} + +/* + * Construct a record in the temp file, based on the contents of the + * original. Takes into account "dirty" headers and threadMod changes. + * + * Pass in the record from the *copy* set, not the original. The temp + * file should be positioned at the correct spot. + * + * If something goes wrong, and the user wants to abort the record but + * not the entire operation, we rewind the temp file to the initial + * position. It's not possible to abandon part of a record; either you + * get everything you asked for or nothing at all. We then return + * kNuErrSkipped, which should cause the caller to simply copy the + * previous record. + */ +static NuError +Nu_ConstructArchiveRecord(NuArchive* pArchive, NuRecord* pRecord) +{ + NuError err; + NuNewThreads* pNewThreads = nil; + long threadDisp; + long initialOffset, finalOffset; + long numThreads, numFilenameThreads; + int newHeaderSize; + + Assert(pArchive != nil); + Assert(pRecord != nil); + + DBUG(("--- Reconstructing '%s'\n", pRecord->filename)); + + err = Nu_FTell(pArchive->tmpFp, &initialOffset); + BailError(err); + Assert(initialOffset != 0); + + /* + * Figure out how large the record header is. This requires + * measuring the static elements, the not-so-static elements like + * the GS/OS option list and perhaps the filename, and getting an + * accurate count of the number of threads. + * + * Since we're going to keep any option lists and extra junk stored in + * the header originally, the size of the new base record header is + * equal to the original recAttribCount. The attribute count conveniently + * does *not* include the filename, so if we've moved it out of the + * record header and into a thread, it won't affect us here. + */ + err = Nu_CountEventualThreads(pRecord, &numThreads, &numFilenameThreads); + BailError(err); + Assert(numThreads > 0); /* threadless records should already be gone */ + if (numThreads <= 0) { + err = kNuErrInternal; + goto bail; + } + + /* + * Handle filename deletion. + */ + if (!numFilenameThreads && pRecord->threadFilename) { + /* looks like a previously existing filename thread got removed */ + DBUG(("--- Dropping thread filename '%s'\n", pRecord->threadFilename)); + if (pRecord->filename == pRecord->threadFilename) + pRecord->filename = nil; /* don't point at freed memory! */ + Nu_Free(pArchive, pRecord->threadFilename); + pRecord->threadFilename = nil; + + /* I don't think this is possible, but check it anyway */ + if (pRecord->filename == nil && pRecord->recFilename != nil && + !pRecord->dropRecFilename) + { + DBUG(("--- HEY, how did this happen?\n")); + pRecord->filename = pRecord->recFilename; + } + } + if (pRecord->filename == nil) + pRecord->filename = kNuDefaultRecordName; + + /* make a hole, including the header filename if we're not dropping it */ + newHeaderSize = pRecord->recAttribCount + numThreads * kNuThreadHeaderSize; + if (!pRecord->dropRecFilename) + newHeaderSize += pRecord->recFilenameLength; + + DBUG(("+++ new header size = %d\n", newHeaderSize)); + err = Nu_FSeek(pArchive->tmpFp, newHeaderSize, SEEK_CUR); + BailError(err); + + /* + * It is important to arrange the threads in a specific order. For + * example, we can have trouble doing a streaming archive read if the + * filename isn't the first thread the collection. It's prudent to + * mimic GSHK's behavior, so we act to ensure that things appear in + * the following order: + * + * (1) filename thread + * (2) comment thread(s) + * (3) data thread with data fork + * (4) data thread with disk image + * (5) data thread with rsrc fork + * (6) everything else + * + * If we ended up with two filename threads (perhaps some other aberrant + * application created the archive; we certainly wouldn't do that), we + * keep the first one. We're more lenient on propagating strange + * multiple comment and data thread situations, even though the + * thread updating mechanism in this library won't necessarily allow + * such situations. + */ + + err = Nu_NewThreads_New(pArchive, &pNewThreads, numThreads); + BailError(err); + + err = Nu_ConstructArchiveThreads(pArchive, pRecord, kNuThreadIDFilename, + true, pNewThreads); + BailError(err); + err = Nu_ConstructArchiveThreads(pArchive, pRecord, kNuThreadIDComment, + false, pNewThreads); + BailError(err); + err = Nu_ConstructArchiveThreads(pArchive, pRecord, kNuThreadIDDataFork, + false, pNewThreads); + BailError(err); + err = Nu_ConstructArchiveThreads(pArchive, pRecord, kNuThreadIDDiskImage, + false, pNewThreads); + BailError(err); + err = Nu_ConstructArchiveThreads(pArchive, pRecord, kNuThreadIDRsrcFork, + false, pNewThreads); + BailError(err); + err = Nu_ConstructArchiveThreads(pArchive, pRecord, kNuThreadIDWildcard, + false, pNewThreads); + BailError(err); + + /* + * Perform some sanity checks. + */ + Assert(!Nu_NewThreads_HasRoom(pNewThreads)); + + /* verify that all threads and threadMods have been touched */ + if (!Nu_VerifyAllTouched(pArchive, pRecord)) { + err = kNuErrInternal; + goto bail; + } + + /* verify that file displacement is where it should be */ + threadDisp = (long)Nu_NewThreads_TotalCompThreadEOF(pNewThreads); + err = Nu_FTell(pArchive->tmpFp, &finalOffset); + BailError(err); + Assert(finalOffset > initialOffset); + if (finalOffset - (initialOffset + newHeaderSize) != threadDisp) { + Nu_ReportError(NU_BLOB, kNuErrNone, + "ERROR: didn't end up where expected (%ld %ld %ld)", + initialOffset, finalOffset, threadDisp); + err = kNuErrInternal; + Assert(0); + } + + /* + * Free existing Threads and ThreadMods, and move the list from + * pNewThreads over. + */ + Nu_Free(pArchive, pRecord->pThreads); + Nu_FreeThreadMods(pArchive, pRecord); + pRecord->pThreads = Nu_NewThreads_DonateThreads(pNewThreads); + pRecord->recTotalThreads = Nu_NewThreads_GetNumThreads(pNewThreads); + + /* + * Now, seek back and write the record header. + */ + err = Nu_FSeek(pArchive->tmpFp, initialOffset, SEEK_SET); + BailError(err); + err = Nu_WriteRecordHeader(pArchive, pRecord, pArchive->tmpFp); + BailError(err); + + /* + * Seek forward once again, so we are positioned at the correct + * place to write the next record. + */ + err = Nu_FSeek(pArchive->tmpFp, finalOffset, SEEK_SET); + BailError(err); + +bail: + if (err == kNuErrSkipped) { + /* + * Something went wrong and they want to skip this record but + * keep going otherwise. We need to back up in the file so the + * original copy of the record can go here. + */ + err = Nu_FSeek(pArchive->tmpFp, initialOffset, SEEK_SET); + if (err == kNuErrNone) + err = kNuErrSkipped; /* tell the caller we skipped it */ + } + + Nu_NewThreads_Free(pArchive, pNewThreads); + return err; +} + + +/* + * Construct a new record and add it to the original or temp file. The + * new record has no threads but some number of threadMods. (This + * function is a cousin to Nu_ConstructArchiveRecord.) "pRecord" must + * come from the "new" record set. + * + * The original/temp file should be positioned at the correct spot. + * + * If something goes wrong, and the user wants to abort the record but + * not the entire operation, we rewind the temp file to the initial + * position and return kNuErrSkipped. + */ +static NuError +Nu_ConstructNewRecord(NuArchive* pArchive, NuRecord* pRecord, FILE* fp) +{ + NuError err; + NuNewThreads* pNewThreads = nil; + NuThreadMod* pThreadMod; + long threadDisp; + long initialOffset, finalOffset; + long numThreadMods, numFilenameThreads; + int newHeaderSize; + + Assert(pArchive != nil); + Assert(pRecord != nil); + + DBUG(("--- Constructing '%s'\n", pRecord->filename)); + + err = Nu_FTell(fp, &initialOffset); + BailError(err); + Assert(initialOffset != 0); + + /* + * Quick sanity check: verify that the record has no threads of its + * own, and all threadMods are "add" threadMods. While we're at it, + * make ourselves useful by counting up the number of eventual + * threads, and verify that there is exactly one filename thread. + */ + Assert(pRecord->pThreads == nil); + + numThreadMods = 0; + numFilenameThreads = 0; + pThreadMod = pRecord->pThreadMods; + while (pThreadMod) { + if (pThreadMod->entry.kind != kNuThreadModAdd) { + Nu_ReportError(NU_BLOB, kNuErrNone, "unexpected non-add threadMod"); + err = kNuErrInternal; + Assert(0); + goto bail; + } + numThreadMods++; + if (pThreadMod->entry.add.threadID == kNuThreadIDFilename) + numFilenameThreads++; + + pThreadMod = pThreadMod->pNext; + } + Assert(numFilenameThreads <= 1); + + /* + * If there's no filename thread, make one. We do this for brand-new + * records when the application doesn't explicitly add a thread. + */ + if (!numFilenameThreads) { + NuDataSource* pTmpDataSource = nil; + NuThreadMod* pNewThreadMod = nil; + int len, maxLen; + + /* + * Generally speaking, the "add file" call should set the + * filename. If somehow it didn't, assign a default. + */ + if (pRecord->filename == nil) { + pRecord->newFilename = strdup(kNuDefaultRecordName); + pRecord->filename = pRecord->newFilename; + } + + DBUG(("--- No filename thread found, adding one ('%s')\n", + pRecord->filename)); + + /* + * Create a trivial data source for the filename. The size of + * the filename buffer is the larger of the filename length and + * the default filename buffer size. This mimics GSHK's behavior. + * (If we're really serious about renaming it, maybe we should + * leave some extra space on the end...?) + */ + len = strlen(pRecord->filename); + maxLen = len > kNuDefaultFilenameThreadSize ? + len : kNuDefaultFilenameThreadSize; + err = Nu_DataSourceBuffer_New(kNuThreadFormatUncompressed, false, + maxLen, (const uchar*)pRecord->filename, 0, + strlen(pRecord->filename), &pTmpDataSource); + BailError(err); + + /* put in a new "add" threadMod */ + err = Nu_ThreadModAdd_New(pArchive, kNuThreadIDFilename, + kNuThreadFormatUncompressed, pTmpDataSource, &pNewThreadMod); + BailError(err); + + /* add it to the list */ + Nu_RecordAddThreadMod(pRecord, pNewThreadMod); + pNewThreadMod = nil; + + numFilenameThreads++; + numThreadMods++; + } + + /* + * Figure out how large the record header is. We don't generate + * GS/OS option lists or "extra" data here, and we always put the + * filename in a thread, so the size is constant. (If somebody + * does a GS/OS or Mac port and wants to add option lists, it should + * not be hard to adjust the size accordingly.) + * + * This initializes the record's attribCount. We use the "base size" + * and add two for the (unused) filename length. + */ + pRecord->recAttribCount = kNuRecordHeaderBaseSize +2; + newHeaderSize = pRecord->recAttribCount + numThreadMods * kNuThreadHeaderSize; + + DBUG(("+++ new header size = %d\n", newHeaderSize)); + + /* leave a hole */ + err = Nu_FSeek(fp, newHeaderSize, SEEK_CUR); + BailError(err); + + /* + * It is important to arrange the threads in a specific order. See + * the comments in Nu_ConstructArchiveRecord for the rationale. + */ + err = Nu_NewThreads_New(pArchive, &pNewThreads, numThreadMods); + BailError(err); + + err = Nu_HandleAddThreadMods(pArchive, pRecord, kNuThreadIDFilename, + true, pNewThreads, fp); + BailError(err); + err = Nu_HandleAddThreadMods(pArchive, pRecord, kNuThreadIDComment, + false, pNewThreads, fp); + BailError(err); + err = Nu_HandleAddThreadMods(pArchive, pRecord, kNuThreadIDDataFork, + false, pNewThreads, fp); + BailError(err); + err = Nu_HandleAddThreadMods(pArchive, pRecord, kNuThreadIDDiskImage, + false, pNewThreads, fp); + BailError(err); + err = Nu_HandleAddThreadMods(pArchive, pRecord, kNuThreadIDRsrcFork, + false, pNewThreads, fp); + BailError(err); + err = Nu_HandleAddThreadMods(pArchive, pRecord, kNuThreadIDWildcard, + false, pNewThreads, fp); + BailError(err); + + /* + * Perform some sanity checks. + */ + Assert(!Nu_NewThreads_HasRoom(pNewThreads)); + + /* verify that all threads and threadMods have been touched */ + if (!Nu_VerifyAllTouched(pArchive, pRecord)) { + err = kNuErrInternal; + goto bail; + } + + /* verify that file displacement is where it should be */ + threadDisp = Nu_NewThreads_TotalCompThreadEOF(pNewThreads); + err = Nu_FTell(fp, &finalOffset); + BailError(err); + Assert(finalOffset > initialOffset); + if (finalOffset - (initialOffset + newHeaderSize) != threadDisp) { + Nu_ReportError(NU_BLOB, kNuErrNone, + "ERROR: didn't end up where expected (%ld %ld %ld)", + initialOffset, finalOffset, threadDisp); + err = kNuErrInternal; + Assert(0); + } + + /* + * Install pNewThreads as the thread list. + */ + Assert(pRecord->pThreads == nil && pRecord->recTotalThreads == 0); + pRecord->pThreads = Nu_NewThreads_DonateThreads(pNewThreads); + pRecord->recTotalThreads = Nu_NewThreads_GetNumThreads(pNewThreads); + + /* + * Fill in misc record header fields. + * + * We could set recArchiveWhen here, if we wanted to override what + * the application set, but I don't think there's any value in that. + */ + pRecord->fileOffset = initialOffset; + + /* + * Now, seek back and write the record header. + */ + err = Nu_FSeek(fp, initialOffset, SEEK_SET); + BailError(err); + err = Nu_WriteRecordHeader(pArchive, pRecord, fp); + BailError(err); + + /* + * Seek forward once again, so we are positioned at the correct + * place to write the next record. + */ + err = Nu_FSeek(fp, finalOffset, SEEK_SET); + BailError(err); + + /* + * Trash the threadMods. + */ + Nu_FreeThreadMods(pArchive, pRecord); + +bail: + if (err == kNuErrSkipped) { + /* + * Something went wrong and they want to skip this record but + * keep going otherwise. We need to back up in the file so the + * next record can go here. + */ + err = Nu_FSeek(fp, initialOffset, SEEK_SET); + if (err == kNuErrNone) + err = kNuErrSkipped; /* tell the caller we skipped it */ + } + + Nu_NewThreads_Free(pArchive, pNewThreads); + return err; +} + + +/* + * Update a given record in the original archive file. + * + * "pRecord" is the record from the "copy" set. It can have the + * "dirtyHeader" flag set, and may have "update" threadMods, but + * that's all. + * + * The position of pArchive->archiveFp on entry and on exit is not + * defined. + */ +static NuError +Nu_UpdateRecordInOriginal(NuArchive* pArchive, NuRecord* pRecord) +{ + NuError err = kNuErrNone; + NuThread* pThread; + const NuThreadMod* pThreadMod; + + /* + * Loop through all threadMods. + */ + pThreadMod = pRecord->pThreadMods; + while (pThreadMod != nil) { + Assert(pThreadMod->entry.kind == kNuThreadModUpdate); + + /* find the thread associated with this threadMod */ + err = Nu_FindThreadByIdx(pRecord, pThreadMod->entry.update.threadIdx, + &pThread); + BailError(err); /* should never happen */ + + /* seek to the appropriate spot */ + err = Nu_FSeek(pArchive->archiveFp, pThread->fileOffset, SEEK_SET); + BailError(err); + + /* do the update; this updates "pThread" with the new info */ + err = Nu_ConstructArchiveUpdate(pArchive, pArchive->archiveFp, + pRecord, pThread, pThreadMod); + BailError(err); + + pThreadMod = pThreadMod->pNext; + } + + + /* + * We have to write a new record header without disturbing + * anything around it. Nothing we've done should've changed + * the size of the record header, so just go ahead and write it. + * + * We have to do this regardless of "dirtyHeader", because we just + * tweaked some of our threads around, and we need to rewrite the + * thread headers (which updates the record header CRC, and so on). + */ + err = Nu_FSeek(pArchive->archiveFp, pRecord->fileOffset, SEEK_SET); + BailError(err); + err = Nu_WriteRecordHeader(pArchive, pRecord, pArchive->archiveFp); + BailError(err); + + /* + * Let's be paranoid and verify that the write didn't overflow + * into the thread header. We compare our current offset against + * the offset of the first thread. (If we're in a weird record + * with no threads, we could compare against the offset of the + * next record, but I don't want to deal with a case that should + * never happen anyway.) + */ + DBUG(("--- record header wrote %ld bytes\n", + pArchive->currentOffset - pRecord->fileOffset)); + pThread = pRecord->pThreads; + if (pThread != nil && pArchive->currentOffset != pThread->fileOffset) { + /* guess what, we just trashed the archive */ + err = kNuErrDamaged; + Nu_ReportError(NU_BLOB, err, + "Bad record header write (off by %ld), archive damaged", + pArchive->currentOffset - pThread->fileOffset); + goto bail; + } + DBUG(("--- record header written safely\n")); + + + /* + * It's customary to throw out the thread mods when you're done. (I'm + * not really sure why I'm doing this now, but here we are.) + */ + Nu_FreeThreadMods(pArchive, pRecord); + +bail: + return err; +} + + +/* + * =========================================================================== + * Archive construction - main functions + * =========================================================================== + */ + +/* + * Fill in the temp file with the contents of the original archive. The + * file offsets and any other generated data in the "copy" set will be + * updated as appropriate, so that the "copy" set can eventually replace + * the "orig" set. + * + * On exit, pArchive->tmpFp will point at the archive EOF. + */ +static NuError +Nu_CreateTempFromOriginal(NuArchive* pArchive) +{ + NuError err = kNuErrNone; + NuRecord* pRecord; + + Assert(pArchive->tmpFp != 0); + Assert(ftell(pArchive->tmpFp) == 0); /* should be empty as well */ + + /* + * Leave space for the master header and (if we're preserving it) any + * header gunk. + */ + Assert(!pArchive->valDiscardWrapper || pArchive->headerOffset == 0); + err = Nu_FSeek(pArchive->tmpFp, + pArchive->headerOffset + kNuMasterHeaderSize, SEEK_SET); + BailError(err); + + if (Nu_RecordSet_GetLoaded(&pArchive->copyRecordSet)) { + /* + * Run through the "copy" records. If the original record header is + * umodified, just copy it; otherwise write a new one with a new CRC. + */ + if (Nu_RecordSet_IsEmpty(&pArchive->copyRecordSet)) { + /* new archive or all records deleted */ + DBUG(("--- No records in 'copy' set\n")); + goto bail; + } + pRecord = Nu_RecordSet_GetListHead(&pArchive->copyRecordSet); + } else { + /* + * There's no "copy" set defined. If we have an "orig" set, we + * must be doing nothing but add files to an existing archive + * without the "modify orig" flag set. + */ + if (Nu_RecordSet_IsEmpty(&pArchive->origRecordSet)) { + DBUG(("--- No records in 'copy' or 'orig' set\n")); + goto bail; + } + pRecord = Nu_RecordSet_GetListHead(&pArchive->origRecordSet); + } + + /* + * Reconstruct or copy the records. It's probably not necessary + * to reconstruct the entire record if we're just updating the + * record header, but since all we do is copy the data anyway, + * it's not much slower. + */ + while (pRecord != nil) { + if (!pRecord->dirtyHeader && pRecord->pThreadMods == nil) { + err = Nu_CopyArchiveRecord(pArchive, pRecord); + BailError(err); + } else { + err = Nu_ConstructArchiveRecord(pArchive, pRecord); + if (err == kNuErrSkipped) { + /* + * We're going to retain the original. This requires us + * to copy the original record from the "orig" record set + * and replace what we had in the "copy" set, so that at + * the end of the day the "copy" set accurately reflects + * what's in the archive. + */ + DBUG(("--- Skipping, copying %ld instead\n", + pRecord->recordIdx)); + err = Nu_RecordSet_ReplaceRecord(pArchive, + &pArchive->copyRecordSet, pRecord, + &pArchive->origRecordSet, &pRecord); + BailError(err); + err = Nu_CopyArchiveRecord(pArchive, pRecord); + BailError(err); + } + BailError(err); + } + + pRecord = pRecord->pNext; + } + +bail: + return err; +} + + +/* + * Perform updates to certain items in the original archive. None of + * the operations changes the position of items within. + * + * On exit, pArchive->archiveFp will point at the archive EOF. + */ +static NuError +Nu_UpdateInOriginal(NuArchive* pArchive) +{ + NuError err = kNuErrNone; + NuRecord* pRecord; + + if (!Nu_RecordSet_GetLoaded(&pArchive->copyRecordSet)) { + /* + * There's nothing for us to do; we probably just have a + * bunch of new stuff being added. + */ + DBUG(("--- UpdateInOriginal: nothing to do\n")); + goto done; + } + + /* + * Run through and process all the updates. + */ + pRecord = Nu_RecordSet_GetListHead(&pArchive->copyRecordSet); + while (pRecord != nil) { + if (pRecord->dirtyHeader || pRecord->pThreadMods != nil) { + err = Nu_UpdateRecordInOriginal(pArchive, pRecord); + BailError(err); + } + + pRecord = pRecord->pNext; + } + +done: + /* seek to the end of the archive */ + err = Nu_FSeek(pArchive->archiveFp, + pArchive->headerOffset + pArchive->masterHeader.mhMasterEOF, + SEEK_SET); + BailError(err); + +bail: + return err; +} + + +/* + * Create new records for all items in the "new" list, writing them to + * "fp" at the current offset. + * + * On completion, "fp" will point at the end of the archive. + */ +static NuError +Nu_CreateNewRecords(NuArchive* pArchive, FILE* fp) +{ + NuError err = kNuErrNone; + NuRecord* pRecord; + + pRecord = Nu_RecordSet_GetListHead(&pArchive->newRecordSet); + while (pRecord != nil) { + err = Nu_ConstructNewRecord(pArchive, pRecord, fp); + if (err == kNuErrSkipped) { + /* + * We decided to skip this record, so delete it from "new". + * + * (I think this is the only time we delete something from the + * "new" set...) + */ + NuRecord* pNextRecord = pRecord->pNext; + + DBUG(("--- Skipping, deleting new %ld\n", pRecord->recordIdx)); + err = Nu_RecordSet_DeleteRecord(pArchive, &pArchive->newRecordSet, + pRecord); + Assert(err == kNuErrNone); + BailError(err); + pRecord = pNextRecord; + } else { + BailError(err); + pRecord = pRecord->pNext; + } + } + +bail: + return err; +} + + +/* + * =========================================================================== + * Archive update helpers + * =========================================================================== + */ + +/* + * Determine if any "heavy updates" have been made. A "heavy" update is + * one that requires us to create and rename a temp file. + * + * If the "copy" record set hasn't been loaded, we're done. If it has + * been loaded, we scan through the list for thread mods other than updates + * to pre-sized fields. We also have to check to see if any records were + * deleted. + * + * At present, a "dirtyHeader" flag is not of itself cause to rebuild + * the archive, so we don't test for it here. + */ +static Boolean +Nu_NoHeavyUpdates(NuArchive* pArchive) +{ + const NuRecord* pRecord; + long count; + + /* if not loaded, then *no* changes were made to original records */ + if (!Nu_RecordSet_GetLoaded(&pArchive->copyRecordSet)) + return true; + + /* + * You can't add to "copy" set, so any deletions are visible by the + * reduced record count. The function that deletes records from + * which all threads have been removed should be called before we + * get here. + */ + if (Nu_RecordSet_GetNumRecords(&pArchive->copyRecordSet) != + Nu_RecordSet_GetNumRecords(&pArchive->origRecordSet)) + { + return false; + } + + /* + * Run through the set of records, looking for a threadMod with a + * change type we can't handle in place. + */ + count = Nu_RecordSet_GetNumRecords(&pArchive->copyRecordSet); + pRecord = Nu_RecordSet_GetListHead(&pArchive->copyRecordSet); + while (count--) { + const NuThreadMod* pThreadMod; + + Assert(pRecord != nil); + + pThreadMod = pRecord->pThreadMods; + while (pThreadMod != nil) { + /* the only acceptable kind is "update" */ + if (pThreadMod->entry.kind != kNuThreadModUpdate) + return false; + + pThreadMod = pThreadMod->pNext; + } + + pRecord = pRecord->pNext; + } + + return true; +} + + +/* + * Purge any records that don't have any threads. This has to take into + * account pending modifications, so that we dispose of any records that + * have had all of their threads deleted. + * + * Simplest approach is to count up the #of "delete" mods and subtract + * it from the number of threads, skipping on if the record has any + * "add" thread mods. + */ +static NuError +Nu_PurgeEmptyRecords(NuArchive* pArchive, NuRecordSet* pRecordSet) +{ + NuError err = kNuErrNone; + NuRecord* pRecord; + NuRecord** ppRecord; + + Assert(pArchive != nil); + Assert(pRecordSet != nil); + + if (Nu_RecordSet_IsEmpty(pRecordSet)) + return kNuErrNone; + + ppRecord = Nu_RecordSet_GetListHeadPtr(pRecordSet); + Assert(ppRecord != nil); + Assert(*ppRecord != nil); + + /* maintain a pointer to the pointer, so we can delete easily */ + while (*ppRecord != nil) { + pRecord = *ppRecord; + + if (Nu_RecordIsEmpty(pArchive, pRecord)) { + DBUG(("--- Purging empty record %06ld '%s' (0x%08lx-->0x%08lx)\n", + pRecord->recordIdx, pRecord->filename, + (ulong)ppRecord, (ulong)pRecord)); + err = Nu_RecordSet_DeleteRecordPtr(pArchive, pRecordSet, ppRecord); + BailError(err); + /* pRecord is now invalid, and *ppRecord has been updated */ + } else { + ppRecord = &pRecord->pNext; + } + } + +bail: + return err; +} + + +/* + * Update the "new" master header block with the contents of the modified + * archive, and write it to the file. + * + * Pass in a correctly positioned "fp" and the total length of the archive + * file. + */ +static NuError +Nu_UpdateMasterHeader(NuArchive* pArchive, FILE* fp, long archiveEOF) +{ + NuError err; + long numRecords; + + Nu_MasterHeaderCopy(pArchive, &pArchive->newMasterHeader, + &pArchive->masterHeader); + + numRecords = 0; + if (Nu_RecordSet_GetLoaded(&pArchive->copyRecordSet)) + numRecords += Nu_RecordSet_GetNumRecords(&pArchive->copyRecordSet); + else + numRecords += Nu_RecordSet_GetNumRecords(&pArchive->origRecordSet); + if (Nu_RecordSet_GetLoaded(&pArchive->newRecordSet)) + numRecords += Nu_RecordSet_GetNumRecords(&pArchive->newRecordSet); + if (numRecords == 0) { + /* don't allow empty archives */ + DBUG(("--- Didn't find any records\n")); + err = kNuErrNoRecords; + goto bail; + } + + pArchive->newMasterHeader.mhTotalRecords = numRecords; + pArchive->newMasterHeader.mhMasterEOF = archiveEOF; + pArchive->newMasterHeader.mhMasterVersion = kNuOurMHVersion; + Nu_SetCurrentDateTime(&pArchive->newMasterHeader.mhArchiveModWhen); + + err = Nu_WriteMasterHeader(pArchive, fp, &pArchive->newMasterHeader); + BailError(err); + +bail: + return err; +} + + +/* + * Reset the temp file to a known (empty) state. + */ +static NuError +Nu_ResetTempFile(NuArchive* pArchive) +{ + NuError err = kNuErrNone; + + /* read-only archives don't have a temp file */ + if (Nu_IsReadOnly(pArchive)) + return kNuErrNone; /* or kNuErrArchiveRO? */ + + Assert(pArchive != nil); + Assert(pArchive->tmpPathname != nil); + +#if 0 /* keep the temp file around for examination */ +if (pArchive->tmpFp != nil) { + DBUG(("--- NOT Resetting temp file\n")); + fflush(pArchive->tmpFp); + goto bail; +} +#endif + + DBUG(("--- Resetting temp file\n")); + + /* if we renamed the temp over the original, we need to open a new temp */ + if (pArchive->tmpFp == nil) { + pArchive->tmpFp = fopen(pArchive->tmpPathname, kNuFileOpenReadWriteCreat); + if (pArchive->tmpFp == nil) { + err = errno ? errno : kNuErrFileOpen; + Nu_ReportError(NU_BLOB, errno, "Unable to open temp file '%s'", + pArchive->tmpPathname); + goto bail; + } + } else { + /* + * Truncate the temp file. + */ + err = Nu_FSeek(pArchive->tmpFp, 0, SEEK_SET); + BailError(err); + err = Nu_TruncateOpenFile(pArchive->tmpFp, 0); + if (err == kNuErrInternal) { + /* do it the hard way if we don't have ftruncate or equivalent */ + err = kNuErrNone; + fclose(pArchive->tmpFp); + pArchive->tmpFp = fopen(pArchive->tmpPathname, kNuFileOpenWriteTrunc); + if (pArchive->tmpFp == nil) { + err = errno ? errno : kNuErrFileOpen; + Nu_ReportError(NU_BLOB, err, "failed truncating tmp file"); + goto bail; + } + fclose(pArchive->tmpFp); + pArchive->tmpFp = + fopen(pArchive->tmpPathname, kNuFileOpenReadWriteCreat); + if (pArchive->tmpFp == nil) { + err = errno ? errno : kNuErrFileOpen; + Nu_ReportError(NU_BLOB, err, "Unable to open temp file '%s'", + pArchive->tmpPathname); + goto bail; + } + } + } + +bail: + return err; +} + +/* + * Ensure that all of the threads and threadMods in a record are in + * a pristine state, i.e. "threads" aren't marked used and "threadMods" + * don't even exist. This is done as we are cleaning up the record sets + * after a successful (or aborted) update. + */ +static NuError +Nu_RecordResetUsedFlags(NuArchive* pArchive, NuRecord* pRecord) +{ + NuThread* pThread; + long idx; + + Assert(pArchive != nil); + Assert(pRecord != nil); + + /* these should already be clear */ + if (pRecord->pThreadMods) { + Assert(0); + return kNuErrInternal; + } + + /* these might still be set */ + for (idx = 0; idx < (long)pRecord->recTotalThreads; idx++) { + pThread = Nu_GetThread(pRecord, idx); + Assert(pThread != nil); + + pThread->used = false; + } + + /* and this */ + pRecord->dirtyHeader = false; + + return kNuErrNone; +} + +/* + * Invoke Nu_RecordResetUsedFlags on all records in a record set. + */ +static NuError +Nu_ResetUsedFlags(NuArchive* pArchive, NuRecordSet* pRecordSet) +{ + NuError err = kNuErrNone; + NuRecord* pRecord; + + pRecord = Nu_RecordSet_GetListHead(pRecordSet); + while (pRecord != nil) { + err = Nu_RecordResetUsedFlags(pArchive, pRecord); + if (err != kNuErrNone) { + Assert(0); + break; + } + + pRecord = pRecord->pNext; + } + + return err; +} + + +/* + * If nothing in the "copy" set has actually been disturbed, throw it out. + */ +static void +Nu_ResetCopySetIfUntouched(NuArchive* pArchive) +{ + const NuRecord* pRecord; + + /* have any records been deleted? */ + if (Nu_RecordSet_GetNumRecords(&pArchive->copyRecordSet) != + pArchive->masterHeader.mhTotalRecords) + { + return; + } + + /* do we have any thread mods or dirty record headers? */ + pRecord = Nu_RecordSet_GetListHead(&pArchive->copyRecordSet); + while (pRecord != nil) { + if (pRecord->pThreadMods != nil || pRecord->dirtyHeader) + return; + + pRecord = pRecord->pNext; + } + + /* looks like nothing has been touched */ + DBUG(("--- copy set untouched, trashing it\n")); + (void) Nu_RecordSet_FreeAllRecords(pArchive, &pArchive->copyRecordSet); +} + + +/* + * GSHK always adds a comment to the first new record added to an archive. + * Imitate this behavior. + */ +static NuError +Nu_AddCommentToFirstNewRecord(NuArchive* pArchive) +{ + NuError err = kNuErrNone; + NuRecord* pRecord; + NuThreadMod* pThreadMod = nil; + NuThreadMod* pExistingThreadMod = nil; + NuDataSource* pDataSource = nil; + + /* if there aren't any records there, skip this */ + if (Nu_RecordSet_IsEmpty(&pArchive->newRecordSet)) + goto bail; + + pRecord = Nu_RecordSet_GetListHead(&pArchive->newRecordSet); + + /* + * See if this record already has a comment. If so, don't add + * another one. + */ + err = Nu_ThreadModAdd_FindByThreadID(pRecord, kNuThreadIDComment, + &pExistingThreadMod); + if (err == kNuErrNone) { + DBUG(("+++ record already has a comment, not adding another\n")); + goto bail; /* already exists */ + } + err = kNuErrNone; + + /* create a new data source with nothing in it */ + err = Nu_DataSourceBuffer_New(kNuThreadFormatUncompressed, false, + kNuDefaultCommentSize, nil, 0, 0, &pDataSource); + BailError(err); + Assert(pDataSource != nil); + + /* create a new ThreadMod */ + err = Nu_ThreadModAdd_New(pArchive, kNuThreadIDComment, + kNuThreadFormatUncompressed, pDataSource, &pThreadMod); + BailError(err); + Assert(pThreadMod != nil); + pDataSource = nil; /* don't free on exit */ + + /* add the thread mod to the record */ + Nu_RecordAddThreadMod(pRecord, pThreadMod); + pThreadMod = nil; /* don't free on exit */ + +bail: + Nu_ThreadModFree(pArchive, pThreadMod); + Nu_DataSourceFree(pDataSource); + return err; +} + + +/* + * =========================================================================== + * Main entry points + * =========================================================================== + */ + +/* + * Force all deferred changes to occur. + * + * If the flush fails, the archive state may be aborted or even placed + * into read-only mode to prevent problems from compounding. + */ +NuError +Nu_Flush(NuArchive* pArchive, long* pStatusFlags) +{ + NuError err = kNuErrNone; + Boolean canAbort = true; + Boolean writeToTemp = true; + long initialEOF, finalOffset; + + DBUG(("--- FLUSH\n")); + + if (pStatusFlags == nil) + return kNuErrInvalidArg; + /* these do get set on error, so clear them no matter what */ + *pStatusFlags = 0; + + if (Nu_IsReadOnly(pArchive)) + return kNuErrArchiveRO; + + err = Nu_GetFileLength(pArchive, pArchive->archiveFp, &initialEOF); + BailError(err); + + /* + * Step 1: figure out if we have anything to do. If the "copy" and "new" + * lists are empty, then there's nothing for us to do. + * + * As a special case, we test for an archive that had all of its + * records deleted. This looks a lot like an archive that has had + * nothing done, because we would have made a "copy" list and then + * deleted all the records, leaving us with an empty list. + * + * In some cases, such as doing a bulk delete that doesn't end up + * matching anything or an attempted UpdatePresizedThread on a thread + * that isn't actually pre-sized, we create the "copy" list but don't + * actually change anything. We deal with that by frying the "copy" + * list if it doesn't have anything interesting in it. + */ + Nu_ResetCopySetIfUntouched(pArchive); + if (Nu_RecordSet_IsEmpty(&pArchive->copyRecordSet) && + Nu_RecordSet_IsEmpty(&pArchive->newRecordSet)) + { + if (Nu_RecordSet_GetLoaded(&pArchive->copyRecordSet)) { + DBUG(("--- All records deleted!\n")); + /* + * Options: + * (1) allow it, leaving an archive with nothing but a header + * that will probably be rejected by other NuFX applications + * (2) reject it, returning an error + * (3) allow it, and just delete the original archive + * + * I dislike #1, and #3 can be implemented by the application + * when it gets a #2. + */ + err = kNuErrAllDeleted; + goto bail; + } else { + DBUG(("--- Nothing pending\n")); + goto flushed; + } + } + + /* if we have any changes, we certainly should have the TOC by now */ + Assert(pArchive->haveToc); + Assert(Nu_RecordSet_GetLoaded(&pArchive->origRecordSet)); + + /* + * Step 2: purge any records from the "copy" and "new" lists that don't + * have any threads. You can't delete from the "new" list, but it's + * possible somebody called NuAddRecord and never put anything in it. + */ + err = Nu_PurgeEmptyRecords(pArchive, &pArchive->copyRecordSet); + BailError(err); + err = Nu_PurgeEmptyRecords(pArchive, &pArchive->newRecordSet); + BailError(err); + + /* we rejected delete-all actions above, so just check for empty */ + if (Nu_RecordSet_IsEmpty(&pArchive->copyRecordSet) && + Nu_RecordSet_IsEmpty(&pArchive->newRecordSet)) + { + DBUG(("--- Nothing pending after purge\n")); + goto flushed; + } + + /* + * Step 3: if we're in ShrinkIt-compatibility mode, add a comment + * thread to the first record in the new list. GSHK does this every + * time it adds files, regardless of the prior contents of the archive. + */ + if (pArchive->valMimicSHK) { + err = Nu_AddCommentToFirstNewRecord(pArchive); + BailError(err); + } + + /* + * Step 4: decide if we want to make changes in place, or write to + * a temp file. Any deletions or additions to existing records will + * require writing to a temp file. Additions of new records and + * updates to pre-sized threads can be done in place. + */ + writeToTemp = true; + if (pArchive->valModifyOrig && Nu_NoHeavyUpdates(pArchive)) + writeToTemp = false; + /* discard the wrapper, if desired */ + if (writeToTemp && pArchive->valDiscardWrapper) + pArchive->headerOffset = 0; + + /* + * Step 5: handle updates to existing records. + */ + if (!writeToTemp) { + /* + * Step 5a: modifying in place, process all UPDATE ThreadMods now. + */ + DBUG(("--- No heavy updates found, updating in place\n")); + if (Nu_RecordSet_GetLoaded(&pArchive->copyRecordSet)) + canAbort = false; /* modifying original, can't cleanly abort */ + + err = Nu_UpdateInOriginal(pArchive); + if (err == kNuErrDamaged) + *pStatusFlags |= kNuFlushCorrupted; + if (err != kNuErrNone) { + Nu_ReportError(NU_BLOB, err, "update to original failed"); + goto bail; + } + } else { + /* + * Step 5b: not modifying in place, reconstruct the appropriate + * parts of the original archive in the temp file, possibly copying + * the front bits over first. Updates and thread-adds will be + * done here. + */ + DBUG(("--- Updating to temp file (valModifyOrig=%ld)\n", + pArchive->valModifyOrig)); + err = Nu_CreateTempFromOriginal(pArchive); + if (err != kNuErrNone) { + DBUG(("--- Create temp from original failed\n")); + goto bail; + } + } + /* on completion, tmpFp (or archiveFp) points to current archive EOF */ + + /* + * Step 6: add the new records from the "new" list, if any. Add a + * filename thread to records where one wasn't provided. These records + * are either added to the original archive or the temp file as + * appropriate. + */ + if (writeToTemp) + err = Nu_CreateNewRecords(pArchive, pArchive->tmpFp); + else + err = Nu_CreateNewRecords(pArchive, pArchive->archiveFp); + BailError(err); + + /* on completion, tmpFp (or archiveFp) points to current archive EOF */ + + /* + * Step 7: truncate the archive. This isn't strictly necessary. It + * comes in handy if we were compressing the very last file and it + * actually expanded. We went back and wrote the uncompressed data, + * but there's a bunch of junk after it from the first try. + * + * On systems like Win32 that don't support ftruncate, this will fail, + * so we just ignore the result. + */ + if (writeToTemp) { + err = Nu_FTell(pArchive->tmpFp, &finalOffset); + BailError(err); + (void) Nu_TruncateOpenFile(pArchive->tmpFp, finalOffset); + } else { + err = Nu_FTell(pArchive->archiveFp, &finalOffset); + BailError(err); + (void) Nu_TruncateOpenFile(pArchive->archiveFp, finalOffset); + } + + /* + * Step 8: create an updated master header, and write it to the + * appropriate file. The "newMasterHeader" field in pArchive will + * hold the new header. + */ + Assert(!pArchive->newMasterHeader.isValid); + if (writeToTemp) { + err = Nu_FSeek(pArchive->tmpFp, pArchive->headerOffset, SEEK_SET); + BailError(err); + err = Nu_UpdateMasterHeader(pArchive, pArchive->tmpFp, + finalOffset - pArchive->headerOffset); + /* fall through with err */ + } else { + err = Nu_FSeek(pArchive->archiveFp, pArchive->headerOffset, SEEK_SET); + BailError(err); + err = Nu_UpdateMasterHeader(pArchive, pArchive->archiveFp, + finalOffset - pArchive->headerOffset); + /* fall through with err */ + } + if (err == kNuErrNoRecords) { + /* + * Somehow we ended up without any records at all. If we managed + * to get this far, it could only be because the user told us to + * skip adding everything. + */ + Nu_ReportError(NU_BLOB, kNuErrNone, "no records in this archive"); + goto bail; + } else if (err != kNuErrNone) { + Nu_ReportError(NU_BLOB, err, "failed writing master header"); + goto bail; + } + Assert(pArchive->newMasterHeader.isValid); + + + /* + * Step 9: carry forward the BXY, SEA, or BSE header, if necessary. This + * implicitly assumes that the header doesn't change size. If this + * assumption is invalid, we'd need to adjust "headerOffset" earlier, + * or do lots of data copying. Looks like Binary II and SEA headers + * are both fixed size, so we should be okay. + */ + if (pArchive->headerOffset) { + if (writeToTemp) { + if (!pArchive->valDiscardWrapper) { + DBUG(("--- Preserving wrapper\n")); + /* copy header to temp */ + err = Nu_CopyWrapperToTemp(pArchive); + BailError(err); + /* update fields that require it */ + err = Nu_UpdateWrapper(pArchive, pArchive->tmpFp); + BailError(err); + /* check the padding */ + err = Nu_AdjustWrapperPadding(pArchive, pArchive->tmpFp); + BailError(err); + } + } else { + /* may need to tweak what's in place? */ + DBUG(("--- Updating wrapper\n")); + err = Nu_UpdateWrapper(pArchive, pArchive->archiveFp); + BailError(err); + /* should only be necessary if we've added new records */ + err = Nu_AdjustWrapperPadding(pArchive, pArchive->archiveFp); + BailError(err); + } + } + + /* + * Step 10: if necessary, remove the original file and rename the + * temp file over it. + * + * I'm not messing with access permissions on the archive file here, + * because if they opened it read-write then the archive itself + * must also be read-write (unless somebody snuck in and chmodded it + * while we were busy). The temp file is certainly writable, so we + * should be able to just leave it all alone. + * + * I'm closing both temp and archive before renaming, because on some + * operating systems you can't do certain things with open files. + */ + if (writeToTemp) { + canAbort = false; /* no going back */ + fclose(pArchive->tmpFp); + pArchive->tmpFp = nil; + fclose(pArchive->archiveFp); + pArchive->archiveFp = nil; + *pStatusFlags |= kNuFlushSucceeded; + + err = Nu_DeleteArchiveFile(pArchive); + if (err != kNuErrNone) { + Nu_ReportError(NU_BLOB, err, "unable to remove original archive"); + Nu_ReportError(NU_BLOB, kNuErrNone, "New data is in '%s'", + pArchive->tmpPathname); + *pStatusFlags |= kNuFlushInaccessible; + goto bail; + } + + err = Nu_RenameTempToArchive(pArchive); + if (err != kNuErrNone) { + Nu_ReportError(NU_BLOB, err, "unable to rename temp file"); + Nu_ReportError(NU_BLOB, kNuErrNone, + "NOTE: only copy of archive is in '%s'", pArchive->tmpPathname); + /* maintain Entry.c semantics (and keep them from removing temp) */ + Nu_Free(pArchive, pArchive->archivePathname); + pArchive->archivePathname = nil; + Nu_Free(pArchive, pArchive->tmpPathname); + pArchive->tmpPathname = nil; + /* bail will put us into read-only mode, which is what we want */ + goto bail; + } + + pArchive->archiveFp = fopen(pArchive->archivePathname, + kNuFileOpenReadWrite); + if (pArchive->archiveFp == nil) { + err = errno ? errno : -1; + Nu_ReportError(NU_BLOB, err, + "unable to reopen archive file '%s' after rename", + pArchive->archivePathname); + *pStatusFlags |= kNuFlushInaccessible; + goto bail; /* the Entry.c funcs will obstruct further use */ + } + } else { + fflush(pArchive->archiveFp); + if (ferror(pArchive->archiveFp)) { + err = kNuErrFileWrite; + Nu_ReportError(NU_BLOB, kNuErrNone, "final archive flush failed"); + *pStatusFlags |= kNuFlushCorrupted; + goto bail; + } + canAbort = false; + *pStatusFlags |= kNuFlushSucceeded; + } + + Assert(canAbort == false); + + /* + * Step 11: clean up data structures. If we have a "copy" list, then + * throw out the "orig" list and move the "copy" list over it. Append + * anything in the "new" list to it. Move the "new" master header + * over the original. + */ + Assert(pArchive->newMasterHeader.isValid); + Nu_MasterHeaderCopy(pArchive, &pArchive->masterHeader, + &pArchive->newMasterHeader); + if (Nu_RecordSet_GetLoaded(&pArchive->copyRecordSet)) { + err = Nu_RecordSet_FreeAllRecords(pArchive, &pArchive->origRecordSet); + BailError(err); + err = Nu_RecordSet_MoveAllRecords(pArchive, &pArchive->origRecordSet, + &pArchive->copyRecordSet); + BailError(err); + } + err = Nu_RecordSet_MoveAllRecords(pArchive, &pArchive->origRecordSet, + &pArchive->newRecordSet); + BailError(err); + err = Nu_ResetUsedFlags(pArchive, &pArchive->origRecordSet); + BailError(err); + +flushed: + /* + * Step 12: reset the "copy" and "new" lists, and reset the temp file. + * Clear out the "new" master header copy. + */ + err = Nu_RecordSet_FreeAllRecords(pArchive, &pArchive->copyRecordSet); + BailError(err); + err = Nu_RecordSet_FreeAllRecords(pArchive, &pArchive->newRecordSet); + BailError(err); + pArchive->newMasterHeader.isValid = false; + + err = Nu_ResetTempFile(pArchive); + if (err != kNuErrNone) { + /* can't NuAbort() our way out of a bad temp file */ + canAbort = false; + goto bail; + } + +bail: + if (err != kNuErrNone) { + if (canAbort) { + (void) Nu_Abort(pArchive); + Assert(!(*pStatusFlags & kNuFlushSucceeded)); + *pStatusFlags |= kNuFlushAborted; + + /* + * If we were adding to original archive, truncate it back if + * we are able to do so. This retains any BXY/BSE wrapper padding. + */ + if (!writeToTemp) { + err = Nu_TruncateOpenFile(pArchive->archiveFp, initialEOF); + if (err == kNuErrNone) { + DBUG(("+++ truncating orig archive back to %ld\n", + initialEOF)); + } + } + } else { + Nu_ReportError(NU_BLOB, kNuErrNone, + "disabling write access after failed update"); + pArchive->openMode = kNuOpenRO; + *pStatusFlags |= kNuFlushReadOnly; + } + } + return err; +} + + +/* + * Abort any pending changes. + */ +NuError +Nu_Abort(NuArchive* pArchive) +{ + Assert(pArchive != nil); + + if (Nu_IsReadOnly(pArchive)) + return kNuErrArchiveRO; + + DBUG(("--- Aborting changes\n")); + + /* + * Throw out the "copy" and "new" record sets, and reset the + * temp file. + */ + (void) Nu_RecordSet_FreeAllRecords(pArchive, &pArchive->copyRecordSet); + (void) Nu_RecordSet_FreeAllRecords(pArchive, &pArchive->newRecordSet); + pArchive->newMasterHeader.isValid = false; + + return Nu_ResetTempFile(pArchive); +} + diff --git a/nufxlib-0/Entry.c b/nufxlib-0/Entry.c new file mode 100644 index 0000000..fe388e4 --- /dev/null +++ b/nufxlib-0/Entry.c @@ -0,0 +1,794 @@ +/* + * NuFX archive manipulation library + * 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 Library General Public License, see the file COPYING.LIB. + * + * All external entry points. + */ +#include "NufxLibPriv.h" + + +/* + * =========================================================================== + * Misc utils + * =========================================================================== + */ + +/* + * Set the busy flag. + * + * The busy flag is intended to prevent the caller from executing illegal + * operations while inside a callback function. It is NOT intended to + * allow concurrent access to the same archive from multiple threads, so + * it does not follow all sorts of crazy semaphore semantics. If you + * have the need, go ahead and fix it. + */ +static inline void +Nu_SetBusy(NuArchive* pArchive) +{ + pArchive->busy = true; +} + +/* + * Clear the busy flag. + */ +static inline void +Nu_ClearBusy(NuArchive* pArchive) +{ + pArchive->busy = false; +} + + +/* + * Do a partial validation on NuArchive. Some calls, such as GetExtraData, + * can be made during callback functions when the archive isn't fully + * consistent. + */ +static NuError +Nu_PartiallyValidateNuArchive(const NuArchive* pArchive) +{ + if (pArchive == nil) + return kNuErrInvalidArg; + + pArchive = pArchive; + if (pArchive->structMagic != kNuArchiveStructMagic) + return kNuErrBadStruct; + + return kNuErrNone; +} + +/* + * Validate the NuArchive* argument passed in to us. + */ +static NuError +Nu_ValidateNuArchive(const NuArchive* pArchive) +{ + NuError err; + + err = Nu_PartiallyValidateNuArchive(pArchive); + if (err != kNuErrNone) + return err; + + /* explicitly block reentrant calls */ + if (pArchive->busy) + return kNuErrBusy; + + /* make sure the TOC state is consistent */ + if (pArchive->haveToc) { + if (pArchive->masterHeader.mhTotalRecords != 0) + Assert(Nu_RecordSet_GetListHead(&pArchive->origRecordSet) != nil); + Assert(Nu_RecordSet_GetNumRecords(&pArchive->origRecordSet) == + pArchive->masterHeader.mhTotalRecords); + } else { + Assert(Nu_RecordSet_GetListHead(&pArchive->origRecordSet) == nil); + } + + /* make sure we have open files to work with */ + Assert(pArchive->archivePathname == nil || pArchive->archiveFp != nil); + if (pArchive->archivePathname != nil && pArchive->archiveFp == nil) + return kNuErrInternal; + Assert(pArchive->tmpPathname == nil || pArchive->tmpFp != nil); + if (pArchive->tmpPathname != nil && pArchive->tmpFp == nil) + return kNuErrInternal; + + /* further validations */ + + return kNuErrNone; +} + + +/* + * =========================================================================== + * Streaming and non-streaming read-only + * =========================================================================== + */ + +NuError +NuStreamOpenRO(FILE* infp, NuArchive** ppArchive) +{ + NuError err; + + if (infp == nil || ppArchive == nil) + return kNuErrInvalidArg; + + err = Nu_StreamOpenRO(infp, (NuArchive**) ppArchive); + + return err; +} + +NuError +NuContents(NuArchive* pArchive, NuCallback contentFunc) +{ + NuError err; + + if ((err = Nu_ValidateNuArchive(pArchive)) == kNuErrNone) { + Nu_SetBusy(pArchive); + if (Nu_IsStreaming(pArchive)) + err = Nu_StreamContents(pArchive, contentFunc); + else + err = Nu_Contents(pArchive, contentFunc); + Nu_ClearBusy(pArchive); + } + + return err; +} + +NuError +NuExtract(NuArchive* pArchive) +{ + NuError err; + + if ((err = Nu_ValidateNuArchive(pArchive)) == kNuErrNone) { + Nu_SetBusy(pArchive); + if (Nu_IsStreaming(pArchive)) + err = Nu_StreamExtract(pArchive); + else + err = Nu_Extract(pArchive); + Nu_ClearBusy(pArchive); + } + + return err; +} + +NuError +NuTest(NuArchive* pArchive) +{ + NuError err; + + if ((err = Nu_ValidateNuArchive(pArchive)) == kNuErrNone) { + Nu_SetBusy(pArchive); + if (Nu_IsStreaming(pArchive)) + err = Nu_StreamTest(pArchive); + else + err = Nu_Test(pArchive); + Nu_ClearBusy(pArchive); + } + + return err; +} + + +/* + * =========================================================================== + * Strictly non-streaming read-only + * =========================================================================== + */ + +NuError +NuOpenRO(const char* filename, NuArchive** ppArchive) +{ + NuError err; + + err = Nu_OpenRO(filename, (NuArchive**) ppArchive); + + return err; +} + +NuError +NuExtractRecord(NuArchive* pArchive, NuRecordIdx recordIdx) +{ + NuError err; + + if ((err = Nu_ValidateNuArchive(pArchive)) == kNuErrNone) { + Nu_SetBusy(pArchive); + err = Nu_ExtractRecord(pArchive, recordIdx); + Nu_ClearBusy(pArchive); + } + + return err; +} + +NuError +NuExtractThread(NuArchive* pArchive, NuThreadIdx threadIdx, + NuDataSink* pDataSink) +{ + NuError err; + + if ((err = Nu_ValidateNuArchive(pArchive)) == kNuErrNone) { + Nu_SetBusy(pArchive); + err = Nu_ExtractThread(pArchive, threadIdx, pDataSink); + Nu_ClearBusy(pArchive); + } + + return err; +} + +NuError +NuGetRecord(NuArchive* pArchive, NuRecordIdx recordIdx, + const NuRecord** ppRecord) +{ + NuError err; + + if ((err = Nu_ValidateNuArchive(pArchive)) == kNuErrNone) { + Nu_SetBusy(pArchive); + err = Nu_GetRecord(pArchive, recordIdx, ppRecord); + Nu_ClearBusy(pArchive); + } + + return err; +} + +NuError +NuGetRecordIdxByName(NuArchive* pArchive, const char* name, + NuRecordIdx* pRecordIdx) +{ + NuError err; + + if ((err = Nu_ValidateNuArchive(pArchive)) == kNuErrNone) { + Nu_SetBusy(pArchive); + err = Nu_GetRecordIdxByName(pArchive, name, pRecordIdx); + Nu_ClearBusy(pArchive); + } + + return err; +} + +NuError +NuGetRecordIdxByPosition(NuArchive* pArchive, unsigned long position, + NuRecordIdx* pRecordIdx) +{ + NuError err; + + if ((err = Nu_ValidateNuArchive(pArchive)) == kNuErrNone) { + Nu_SetBusy(pArchive); + err = Nu_GetRecordIdxByPosition(pArchive, position, pRecordIdx); + Nu_ClearBusy(pArchive); + } + + return err; +} + + +/* + * =========================================================================== + * Read/Write + * =========================================================================== + */ + +NuError +NuOpenRW(const char* archivePathname, const char* tmpPathname, + unsigned long flags, NuArchive** ppArchive) +{ + NuError err; + + err = Nu_OpenRW(archivePathname, tmpPathname, flags, + (NuArchive**) ppArchive); + + return err; +} + +NuError +NuFlush(NuArchive* pArchive, long* pStatusFlags) +{ + NuError err; + + if ((err = Nu_ValidateNuArchive(pArchive)) == kNuErrNone) { + Nu_SetBusy(pArchive); + err = Nu_Flush(pArchive, pStatusFlags); + Nu_ClearBusy(pArchive); + } + + return err; +} + +NuError +NuAbort(NuArchive* pArchive) +{ + NuError err; + + if ((err = Nu_ValidateNuArchive(pArchive)) == kNuErrNone) { + Nu_SetBusy(pArchive); + err = Nu_Abort(pArchive); + Nu_ClearBusy(pArchive); + } + + return err; +} + +NuError +NuAddRecord(NuArchive* pArchive, const NuFileDetails* pFileDetails, + NuRecordIdx* pRecordIdx) +{ + NuError err; + + if ((err = Nu_ValidateNuArchive(pArchive)) == kNuErrNone) { + Nu_SetBusy(pArchive); + err = Nu_AddRecord(pArchive, pFileDetails, pRecordIdx, nil); + Nu_ClearBusy(pArchive); + } + + return err; +} + +NuError +NuAddThread(NuArchive* pArchive, NuRecordIdx recordIdx, NuThreadID threadID, + NuDataSource* pDataSource, NuThreadIdx* pThreadIdx) +{ + NuError err; + + if ((err = Nu_ValidateNuArchive(pArchive)) == kNuErrNone) { + Nu_SetBusy(pArchive); + err = Nu_AddThread(pArchive, recordIdx, threadID, + pDataSource, pThreadIdx); + Nu_ClearBusy(pArchive); + } + + return err; +} + +NuError +NuAddFile(NuArchive* pArchive, const char* pathname, + const NuFileDetails* pFileDetails, short isFromRsrcFork, + NuRecordIdx* pRecordIdx) +{ + NuError err; + + if ((err = Nu_ValidateNuArchive(pArchive)) == kNuErrNone) { + Nu_SetBusy(pArchive); + err = Nu_AddFile(pArchive, pathname, pFileDetails, + (Boolean)(isFromRsrcFork != 0), pRecordIdx); + Nu_ClearBusy(pArchive); + } + + return err; +} + +NuError +NuRename(NuArchive* pArchive, NuRecordIdx recordIdx, const char* pathname, + char fssep) +{ + NuError err; + + if ((err = Nu_ValidateNuArchive(pArchive)) == kNuErrNone) { + Nu_SetBusy(pArchive); + err = Nu_Rename(pArchive, recordIdx, pathname, fssep); + Nu_ClearBusy(pArchive); + } + + return err; +} + + +NuError +NuSetRecordAttr(NuArchive* pArchive, NuRecordIdx recordIdx, + const NuRecordAttr* pRecordAttr) +{ + NuError err; + + if ((err = Nu_ValidateNuArchive(pArchive)) == kNuErrNone) { + Nu_SetBusy(pArchive); + err = Nu_SetRecordAttr(pArchive, recordIdx, pRecordAttr); + Nu_ClearBusy(pArchive); + } + + return err; +} + +NuError +NuUpdatePresizedThread(NuArchive* pArchive, NuThreadIdx threadIdx, + NuDataSource* pDataSource, long* pMaxLen) +{ + NuError err; + + if ((err = Nu_ValidateNuArchive(pArchive)) == kNuErrNone) { + Nu_SetBusy(pArchive); + err = Nu_UpdatePresizedThread(pArchive, threadIdx, + pDataSource, pMaxLen); + Nu_ClearBusy(pArchive); + } + + return err; +} + +NuError +NuDelete(NuArchive* pArchive) +{ + NuError err; + + if ((err = Nu_ValidateNuArchive(pArchive)) == kNuErrNone) { + Nu_SetBusy(pArchive); + err = Nu_Delete(pArchive); + Nu_ClearBusy(pArchive); + } + + return err; +} + +NuError +NuDeleteRecord(NuArchive* pArchive, NuRecordIdx recordIdx) +{ + NuError err; + + if ((err = Nu_ValidateNuArchive(pArchive)) == kNuErrNone) { + Nu_SetBusy(pArchive); + err = Nu_DeleteRecord(pArchive, recordIdx); + Nu_ClearBusy(pArchive); + } + + return err; +} + +NuError +NuDeleteThread(NuArchive* pArchive, NuThreadIdx threadIdx) +{ + NuError err; + + if ((err = Nu_ValidateNuArchive(pArchive)) == kNuErrNone) { + Nu_SetBusy(pArchive); + err = Nu_DeleteThread(pArchive, threadIdx); + Nu_ClearBusy(pArchive); + } + + return err; +} + + +/* + * =========================================================================== + * General interfaces + * =========================================================================== + */ + +NuError +NuClose(NuArchive* pArchive) +{ + NuError err; + + if ((err = Nu_ValidateNuArchive(pArchive)) == kNuErrNone) { + Nu_SetBusy(pArchive); + err = Nu_Close(pArchive); + /* on success, pArchive has been freed */ + if (err != kNuErrNone) + Nu_ClearBusy(pArchive); + } + + return err; +} + +NuError +NuGetMasterHeader(NuArchive* pArchive, const NuMasterHeader** ppMasterHeader) +{ + NuError err; + + if ((err = Nu_ValidateNuArchive(pArchive)) == kNuErrNone) + err = Nu_GetMasterHeader(pArchive, ppMasterHeader); + + return err; +} + +NuError +NuGetExtraData(NuArchive* pArchive, void** ppData) +{ + NuError err; + + if (ppData == nil) + return kNuErrInvalidArg; + if ((err = Nu_PartiallyValidateNuArchive(pArchive)) == kNuErrNone) + *ppData = pArchive->extraData; + + return err; +} + +NuError +NuSetExtraData(NuArchive* pArchive, void* pData) +{ + NuError err; + + if ((err = Nu_PartiallyValidateNuArchive(pArchive)) == kNuErrNone) + pArchive->extraData = pData; + + return err; +} + +NuError +NuGetValue(NuArchive* pArchive, NuValueID ident, NuValue* pValue) +{ + NuError err; + + if ((err = Nu_PartiallyValidateNuArchive(pArchive)) == kNuErrNone) + return Nu_GetValue(pArchive, ident, pValue); + + return err; +} + +NuError +NuSetValue(NuArchive* pArchive, NuValueID ident, NuValue value) +{ + NuError err; + + if ((err = Nu_PartiallyValidateNuArchive(pArchive)) == kNuErrNone) + return Nu_SetValue(pArchive, ident, value); + + return err; +} + +NuError +NuGetAttr(NuArchive* pArchive, NuAttrID ident, NuAttr* pAttr) +{ + NuError err; + + if ((err = Nu_PartiallyValidateNuArchive(pArchive)) == kNuErrNone) + return Nu_GetAttr(pArchive, ident, pAttr); + + return err; +} + +const char* +NuStrError(NuError err) +{ + return Nu_StrError(err); +} + +NuError +NuDebugDumpArchive(NuArchive* pArchive) +{ +#if defined(DEBUG_MSGS) + /* skip validation checks for this one */ + Nu_DebugDumpAll(pArchive); + return kNuErrNone; +#else + /* function doesn't exist */ + return kNuErrGeneric; +#endif +} + +NuError +NuGetVersion(long* pMajorVersion, long* pMinorVersion, long* pBugVersion, + const char** ppBuildDate, const char** ppBuildFlags) +{ + return Nu_GetVersion(pMajorVersion, pMinorVersion, pBugVersion, + ppBuildDate, ppBuildFlags); +} + + +/* + * =========================================================================== + * Sources and Sinks + * =========================================================================== + */ + +NuError +NuCreateDataSourceForFile(NuThreadFormat threadFormat, short doClose, + unsigned long otherLen, const char* pathname, short isFromRsrcFork, + NuDataSource** ppDataSource) +{ + return Nu_DataSourceFile_New(threadFormat, (Boolean)(doClose != 0), + otherLen, pathname, (Boolean)(isFromRsrcFork != 0), ppDataSource); +} + +NuError +NuCreateDataSourceForFP(NuThreadFormat threadFormat, short doClose, + unsigned long otherLen, FILE* fp, long offset, long length, + NuDataSource** ppDataSource) +{ + return Nu_DataSourceFP_New(threadFormat, (Boolean)(doClose != 0), + otherLen, fp, offset, length, ppDataSource); +} + +NuError +NuCreateDataSourceForBuffer(NuThreadFormat threadFormat, short doClose, + unsigned long otherLen, const unsigned char* buffer, long offset, + long length, NuDataSource** ppDataSource) +{ + return Nu_DataSourceBuffer_New(threadFormat, (Boolean)(doClose != 0), + otherLen, buffer, offset, length, ppDataSource); +} + +NuError +NuFreeDataSource(NuDataSource* pDataSource) +{ + return Nu_DataSourceFree(pDataSource); +} + +NuError +NuDataSourceSetRawCrc(NuDataSource* pDataSource, unsigned short crc) +{ + if (pDataSource == nil) + return kNuErrInvalidArg; + Nu_DataSourceSetRawCrc(pDataSource, crc); + return kNuErrNone; +} + +NuError +NuCreateDataSinkForFile(short doExpand, NuValue convertEOL, + const char* pathname, char fssep, NuDataSink** ppDataSink) +{ + return Nu_DataSinkFile_New((Boolean)(doExpand != 0), convertEOL, pathname, + fssep, ppDataSink); +} + +NuError +NuCreateDataSinkForFP(short doExpand, NuValue convertEOL, FILE* fp, + NuDataSink** ppDataSink) +{ + return Nu_DataSinkFP_New((Boolean)(doExpand != 0), convertEOL, fp, + ppDataSink); +} + +NuError +NuCreateDataSinkForBuffer(short doExpand, NuValue convertEOL, + unsigned char* buffer, unsigned long bufLen, NuDataSink** ppDataSink) +{ + return Nu_DataSinkBuffer_New((Boolean)(doExpand != 0), convertEOL, buffer, + bufLen, ppDataSink); +} + +NuError +NuFreeDataSink(NuDataSink* pDataSink) +{ + return Nu_DataSinkFree(pDataSink); +} + +NuError +NuDataSinkGetOutCount(NuDataSink* pDataSink, ulong* pOutCount) +{ + if (pDataSink == nil || pOutCount == nil) + return kNuErrInvalidArg; + + *pOutCount = Nu_DataSinkGetOutCount(pDataSink); + return kNuErrNone; +} + + +/* + * =========================================================================== + * Non-archive operations + * =========================================================================== + */ + +void +NuRecordCopyAttr(NuRecordAttr* pRecordAttr, const NuRecord* pRecord) +{ + pRecordAttr->fileSysID = pRecord->recFileSysID; + /*pRecordAttr->fileSysInfo = pRecord->recFileSysInfo;*/ + pRecordAttr->access = pRecord->recAccess; + pRecordAttr->fileType = pRecord->recFileType; + pRecordAttr->extraType = pRecord->recExtraType; + pRecordAttr->createWhen = pRecord->recCreateWhen; + pRecordAttr->modWhen = pRecord->recModWhen; + pRecordAttr->archiveWhen = pRecord->recArchiveWhen; +} + +NuError +NuRecordCopyThreads(const NuRecord* pNuRecord, NuThread** ppThreads) +{ + if (pNuRecord == nil || ppThreads == nil) + return kNuErrInvalidArg; + + Assert(pNuRecord->pThreads != nil); + + *ppThreads = Nu_Malloc(nil, pNuRecord->recTotalThreads * sizeof(NuThread)); + if (*ppThreads == nil) + return kNuErrMalloc; + + memcpy(*ppThreads, pNuRecord->pThreads, + pNuRecord->recTotalThreads * sizeof(NuThread)); + + return kNuErrNone; +} + +unsigned long +NuRecordGetNumThreads(const NuRecord* pNuRecord) +{ + if (pNuRecord == nil) + return -1; + + return pNuRecord->recTotalThreads; +} + +const NuThread* +NuThreadGetByIdx(const NuThread* pNuThread, long idx) +{ + if (pNuThread == nil) + return nil; + return &pNuThread[idx]; /* can't range-check here */ +} + +short +NuIsPresizedThreadID(NuThreadID threadID) +{ + return Nu_IsPresizedThreadID(threadID); +} + + +/* + * =========================================================================== + * Callback setters + * =========================================================================== + */ + +NuError +NuSetSelectionFilter(NuArchive* pArchive, NuCallback filterFunc) +{ + NuError err; + + /*Assert(!((ulong)filterFunc % 4));*/ + + if ((err = Nu_ValidateNuArchive(pArchive)) == kNuErrNone) + pArchive->selectionFilterFunc = filterFunc; + + return err; +} + +NuError +NuSetOutputPathnameFilter(NuArchive* pArchive, NuCallback filterFunc) +{ + NuError err; + + /*Assert(!((ulong)filterFunc % 4));*/ + + if ((err = Nu_ValidateNuArchive(pArchive)) == kNuErrNone) + pArchive->outputPathnameFunc = filterFunc; + + return err; +} + +NuError +NuSetProgressUpdater(NuArchive* pArchive, NuCallback updateFunc) +{ + NuError err; + + /*Assert(!((ulong)updateFunc % 4));*/ + + if ((err = Nu_ValidateNuArchive(pArchive)) == kNuErrNone) + pArchive->progressUpdaterFunc = updateFunc; + + return err; +} + +NuError +NuSetErrorHandler(NuArchive* pArchive, NuCallback errorFunc) +{ + NuError err; + + /*Assert(!((ulong)errorFunc % 4));*/ + + if ((err = Nu_ValidateNuArchive(pArchive)) == kNuErrNone) + pArchive->errorHandlerFunc = errorFunc; + + return err; +} + +NuError +NuSetErrorMessageHandler(NuArchive* pArchive, NuCallback messageHandlerFunc) +{ + NuError err; + + /*Assert(!((ulong)messageHandlerFunc % 4));*/ + + if ((err = Nu_ValidateNuArchive(pArchive)) == kNuErrNone) + pArchive->messageHandlerFunc = messageHandlerFunc; + + return err; +} + +NuError +NuSetGlobalErrorMessageHandler(NuCallback messageHandlerFunc) +{ + /*Assert(!((ulong)messageHandlerFunc % 4));*/ + + gNuGlobalErrorMessageHandler = messageHandlerFunc; + return kNuErrNone; +} + diff --git a/nufxlib-0/Expand.c b/nufxlib-0/Expand.c new file mode 100644 index 0000000..d033b03 --- /dev/null +++ b/nufxlib-0/Expand.c @@ -0,0 +1,213 @@ +/* + * NuFX archive manipulation library + * 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 Library General Public License, see the file COPYING.LIB. + * + * Expand a thread from an archive. + */ +#include "NufxLibPriv.h" + + +/* + * "Expand" an uncompressed thread. + */ +static NuError +Nu_ExpandUncompressed(NuArchive* pArchive, const NuRecord* pRecord, + const NuThread* pThread, FILE* infp, NuFunnel* pFunnel, ushort* pCrc) +{ + NuError err; + /*uchar* buffer = nil;*/ + ulong count, getsize; + + Assert(pArchive != nil); + Assert(pThread != nil); + Assert(infp != nil); + Assert(pFunnel != nil); + + /* doesn't have to be same size as funnel, but it's not a bad idea */ + /*buffer = Nu_Malloc(pArchive, kNuFunnelBufSize);*/ + /*BailAlloc(buffer);*/ + err = Nu_AllocCompressionBufferIFN(pArchive); + BailError(err); + + /* quick assert for bad archive that should have been caught earlier */ + /* (filename threads are uncompressed, but compThreadEOF is buf len) */ + if (pThread->thThreadClass == kNuThreadClassData) + Assert(pThread->actualThreadEOF == pThread->thCompThreadEOF); + + count = pThread->actualThreadEOF; + + while (count) { + getsize = (count > kNuGenCompBufSize) ? kNuGenCompBufSize : count; + + err = Nu_FRead(infp, pArchive->compBuf, getsize); + BailError(err); + if (pCrc != nil) + *pCrc = Nu_CalcCRC16(*pCrc, pArchive->compBuf, getsize); + err = Nu_FunnelWrite(pArchive, pFunnel, pArchive->compBuf, getsize); + BailError(err); + + count -= getsize; + } + + err = Nu_FunnelFlush(pArchive, pFunnel); + BailError(err); + +bail: + /*Nu_Free(pArchive, buffer);*/ + return err; +} + +/* + * Copy the "raw" data out of the thread. Unlike the preceeding function, + * this reads up to "thCompThreadEOF", and doesn't even try to compute a CRC. + */ +static NuError +Nu_ExpandRaw(NuArchive* pArchive, const NuThread* pThread, FILE* infp, + NuFunnel* pFunnel) +{ + NuError err; + /*uchar* buffer = nil;*/ + ulong count, getsize; + + Assert(pArchive != nil); + Assert(pThread != nil); + Assert(infp != nil); + Assert(pFunnel != nil); + + /* doesn't have to be same size as funnel, but it's not a bad idea */ + /*buffer = Nu_Malloc(pArchive, kNuFunnelBufSize);*/ + /*BailAlloc(buffer);*/ + err = Nu_AllocCompressionBufferIFN(pArchive); + BailError(err); + + count = pThread->thCompThreadEOF; + + while (count) { + getsize = (count > kNuGenCompBufSize) ? kNuGenCompBufSize : count; + + err = Nu_FRead(infp, pArchive->compBuf, getsize); + BailError(err); + err = Nu_FunnelWrite(pArchive, pFunnel, pArchive->compBuf, getsize); + BailError(err); + + count -= getsize; + } + + err = Nu_FunnelFlush(pArchive, pFunnel); + BailError(err); + +bail: + /*Nu_Free(pArchive, buffer);*/ + return err; +} + + +/* + * Expand a thread from "infp" to "pFunnel", using the compression + * and stream length specified by "pThread". + */ +NuError +Nu_ExpandStream(NuArchive* pArchive, const NuRecord* pRecord, + const NuThread* pThread, FILE* infp, NuFunnel* pFunnel) +{ + NuError err = kNuErrNone; + ushort calcCrc; + ushort* pCalcCrc; + + if (!pThread->thThreadEOF && !pThread->thCompThreadEOF) { + /* somebody stored an empty file! */ + goto done; + } + + /* + * A brief history of the "threadCRC" field in the thread header: + * record versions 0 and 1 didn't use the threadCRC field + * record version 2 put the CRC of the compressed data in threadCRC + * record version 3 put the CRC of the uncompressed data in threadCRC + * + * P8 ShrinkIt uses v1, GSHK uses v3. If something ever shipped with + * v2, it didn't last long enough to leave an impression, so I'm not + * going to support it. BTW, P8 ShrinkIt always uses LZW/1, which + * puts a CRC in the compressed stream. Your uncompressed data is, + * unfortunately, unprotected before v3. + */ + calcCrc = kNuInitialThreadCRC; + pCalcCrc = nil; + if (Nu_ThreadHasCRC(pRecord->recVersionNumber, NuGetThreadID(pThread)) && + !pArchive->valIgnoreCRC) + { + pCalcCrc = &calcCrc; + } + + err = Nu_ProgressDataExpandPrep(pArchive, pFunnel, pThread); + BailError(err); + + /* + * If we're not expanding the data, use a simple copier. + */ + if (!Nu_FunnelGetDoExpand(pFunnel)) { + Nu_FunnelSetProgressState(pFunnel, kNuProgressCopying); + err = Nu_ExpandRaw(pArchive, pThread, infp, pFunnel); + BailError(err); + goto done; + } + + Nu_FunnelSetProgressState(pFunnel, kNuProgressExpanding); + switch (pThread->thThreadFormat) { + case kNuThreadFormatUncompressed: + Nu_FunnelSetProgressState(pFunnel, kNuProgressCopying); + err = Nu_ExpandUncompressed(pArchive, pRecord, pThread, infp, pFunnel, + pCalcCrc); + break; + case kNuThreadFormatHuffmanSQ: + err = kNuErrBadFormat; + Nu_ReportError(NU_BLOB, kNuErrNone, + "Huffman-compressed threads not supported"); + break; + case kNuThreadFormatLZW1: + case kNuThreadFormatLZW2: + err = Nu_ExpandLZW(pArchive, pRecord, pThread, infp, pFunnel, pCalcCrc); + break; + case kNuThreadFormatLZC12: + case kNuThreadFormatLZC16: + err = kNuErrBadFormat; + Nu_ReportError(NU_BLOB, kNuErrNone, + "LZC-compressed threads not supported"); + break; + default: + err = kNuErrBadFormat; + Nu_ReportError(NU_BLOB, err, + "format %u unknown", pThread->thThreadFormat); + break; + } + + BailError(err); + + /* + * If we have a CRC to check, check it. + */ + if (pCalcCrc != nil) { + if (calcCrc != pThread->thThreadCRC) { + if (!Nu_ShouldIgnoreBadCRC(pArchive, pRecord, kNuErrBadThreadCRC)) { + err = kNuErrBadThreadCRC; + Nu_ReportError(NU_BLOB, err, "expected 0x%04x, got 0x%04x", + pThread->thThreadCRC, calcCrc); + goto bail; + } + } else { + DBUG(("--- thread CRCs match\n")); + } + } + +done: + /* make sure we send a final "success" progress message at 100% */ + (void) Nu_FunnelSetProgressState(pFunnel, kNuProgressDone); + err = Nu_FunnelSendProgressUpdate(pArchive, pFunnel); + BailError(err); + +bail: + return err; +} + diff --git a/nufxlib-0/FileIO.c b/nufxlib-0/FileIO.c new file mode 100644 index 0000000..86b9a2c --- /dev/null +++ b/nufxlib-0/FileIO.c @@ -0,0 +1,1293 @@ +/* + * NuFX archive manipulation library + * 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 Library General Public License, see the file COPYING.LIB. + * + * Operations on output (i.e. non-archive) files, largely system-specific. + * Portions taken from NuLib, including some code that Devin Reade worked on. + * + * It could be argued that "create file" should be a callback function, + * since it is so heavily system-specific, and most of the other + * system dependencies are handled by the application rather than the + * NuFX library. It would also provide a cleaner solution for renaming + * extracted files. However, the goal of the library is to do the work + * for the application, not the other way around; and while it might be + * nice to offload all direct file handling on the application, it + * complicates rather than simplifies the interface. + */ +#include "NufxLibPriv.h" + +/* + * For systems (e.g. Visual C++ 6.0) that don't have these standard values. + */ +#ifndef S_IRUSR +# define S_IRUSR 0400 +# define S_IWUSR 0200 +# define S_IXUSR 0100 +# define S_IRWXU (S_IRUSR|S_IWUSR|S_IXUSR) +# define S_IRGRP (S_IRUSR >> 3) +# define S_IWGRP (S_IWUSR >> 3) +# define S_IXGRP (S_IXUSR >> 3) +# define S_IRWXG (S_IRWXU >> 3) +# define S_IROTH (S_IRGRP >> 3) +# define S_IWOTH (S_IWGRP >> 3) +# define S_IXOTH (S_IXGRP >> 3) +# define S_IRWXO (S_IRWXG >> 3) +#endif +#ifndef S_ISREG +# define S_ISREG(m) (((m) & S_IFMT) == S_IFREG) +# define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR) +#endif + + +/* + * =========================================================================== + * DateTime conversions + * =========================================================================== + */ + +/* + * Dates and times in a NuFX archive are always considered to be in the + * local time zone. The use of GMT time stamps would have been more + * appropriate for an archive, but local time works well enough. + * + * Regarding Y2K on the Apple II: + * + * Dave says P8 drivers should return year values in the range 0..99, where + * 40..99 = 1940..1999, and 0..39 = 2000..2039. Year values 100..127 should + * never be used. For ProDOS 8, the year 2000 is "00". + * + * The IIgs ReadTimeHex call uses "year minus 1900". For GS/OS, the year + * 2000 is "100". + * + * The NuFX file type note says the archive format should work like + * The IIgs ReadTimeHex call, which uses "year minus 1900" as its + * format. GS/ShrinkIt v1.1 uses the IIgs date calls, and so stores the + * year 2000 as "100". P8 ShrinkIt v3.4 uses the P8 mapping, and stores + * it as "0". Neither really quite understands what the other is doing. + * + * For our purposes, we will follow the NuFX standard and emit "100" + * for the year 2000, but will accept and understand "0" as well. + */ + + +#if defined(UNIX_LIKE) || defined(WINDOWS_LIKE) +/* + * Convert from local time in a NuDateTime struct to GMT seconds since 1970. + * + * If the conversion is invalid, "*pWhen" is set to zero. + */ +static void +Nu_DateTimeToGMTSeconds(const NuDateTime* pDateTime, time_t* pWhen) +{ + struct tm tmbuf; + time_t when; + + Assert(pDateTime != nil); + Assert(pWhen != nil); + + tmbuf.tm_sec = pDateTime->second; + tmbuf.tm_min = pDateTime->minute; + tmbuf.tm_hour = pDateTime->hour; + tmbuf.tm_mday = pDateTime->day +1; + tmbuf.tm_mon = pDateTime->month; + tmbuf.tm_year = pDateTime->year; + if (pDateTime->year < 40) + tmbuf.tm_year += 100; /* P8 uses 0-39 for 2000-2039 */ + tmbuf.tm_wday = 0; + tmbuf.tm_yday = 0; + tmbuf.tm_isdst = -1; /* let it figure DST and time zone */ + + #if defined(HAVE_MKTIME) + when = mktime(&tmbuf); + #elif defined(HAVE_TIMELOCAL) + when = timelocal(&tmbuf); + #else + # error "need time converter" + #endif + + if (when == (time_t) -1) + *pWhen = 0; + else + *pWhen = when; +} + +/* + * Convert from GMT seconds since 1970 to local time in a NuDateTime struct. + */ +static void +Nu_GMTSecondsToDateTime(const time_t* pWhen, NuDateTime *pDateTime) +{ + struct tm* ptm; + + Assert(pWhen != nil); + Assert(pDateTime != nil); + + #if defined(HAVE_LOCALTIME_R) && defined(USE_REENTRANT_CALLS) + struct tm res; + ptm = localtime_r(pWhen, &res); + #else + /* NOTE: not thread-safe */ + ptm = localtime(pWhen); + #endif + pDateTime->second = ptm->tm_sec; + pDateTime->minute = ptm->tm_min; + pDateTime->hour = ptm->tm_hour; + pDateTime->day = ptm->tm_mday -1; + pDateTime->month = ptm->tm_mon; + pDateTime->year = ptm->tm_year; + pDateTime->extra = 0; + pDateTime->weekDay = ptm->tm_wday +1; +} +#endif + + +/* + * Fill in the current time. + */ +void +Nu_SetCurrentDateTime(NuDateTime* pDateTime) +{ + Assert(pDateTime != nil); + +#if defined(UNIX_LIKE) || defined(WINDOWS_LIKE) + { + time_t now = time(nil); + Nu_GMTSecondsToDateTime(&now, pDateTime); + } +#else + #error "Port this" +#endif +} + + +/* + * Returns "true" if "pWhen1" is older than "pWhen2". Returns false if + * "pWhen1" is the same age or newer than "pWhen2". + * + * On systems with mktime, it would be straightforward to convert the dates + * to time in seconds, and compare them that way. However, I don't want + * to rely on that function too heavily, so we just compare fields. + */ +Boolean +Nu_IsOlder(const NuDateTime* pWhen1, const NuDateTime* pWhen2) +{ + long result, year1, year2; + + /* adjust for P8 ShrinkIt Y2K problem */ + year1 = pWhen1->year; + if (year1 < 40) + year1 += 100; + year2 = pWhen2->year; + if (year2 < 40) + year2 += 100; + + result = year1 - year2; + if (!result) + result = pWhen1->month - pWhen2->month; + if (!result) + result = pWhen1->day - pWhen2->day; + if (!result) + result = pWhen1->hour - pWhen2->hour; + if (!result) + result = pWhen1->minute - pWhen2->minute; + if (!result) + result = pWhen1->second - pWhen2->second; + + if (result < 0) + return true; + return false; +} + + +/* + * =========================================================================== + * Get/set file info + * =========================================================================== + */ + +/* + * System-independent (mostly) file info struct. + */ +typedef struct NuFileInfo { + Boolean isValid; /* init to "false", set "true" after we get data */ + + Boolean isRegularFile; /* is this a regular file? */ + Boolean isDirectory; /* is this a directory? */ + + ulong dataEof; + ulong rsrcEof; + + ulong fileType; + ulong auxType; + NuDateTime modWhen; + mode_t unixMode; /* UNIX-style permissions */ +} NuFileInfo; + +#define kDefaultFileType 0 /* "NON" */ +#define kDefaultAuxType 0 /* $0000 */ + + +/* + * Determine whether the record has both data and resource forks. + */ +static Boolean +Nu_IsForkedFile(NuArchive* pArchive, const NuRecord* pRecord) +{ + const NuThread* pThread; + NuThreadID threadID; + Boolean gotData, gotRsrc; + int i; + + gotData = gotRsrc = false; + + for (i = 0; i < (int)pRecord->recTotalThreads; i++) { + pThread = Nu_GetThread(pRecord, i); + Assert(pThread != nil); + + threadID = NuMakeThreadID(pThread->thThreadClass,pThread->thThreadKind); + if (threadID == kNuThreadIDDataFork) + gotData = true; + else if (threadID == kNuThreadIDRsrcFork) + gotRsrc = true; + } + + if (gotData && gotRsrc) + return true; + else + return false; +} + + +/* + * Get the file info into a NuFileInfo struct. Fields which are + * inappropriate for the current system are set to default values. + */ +static NuError +Nu_GetFileInfo(NuArchive* pArchive, const char* pathname, + NuFileInfo* pFileInfo) +{ + NuError err = kNuErrNone; + Assert(pArchive != nil); + Assert(pathname != nil); + Assert(pFileInfo != nil); + + pFileInfo->isValid = false; + +#if defined(UNIX_LIKE) || defined(WINDOWS_LIKE) + { + struct stat sbuf; + int cc; + + cc = stat(pathname, &sbuf); + if (cc) { + if (errno == ENOENT) + err = kNuErrFileNotFound; + else + err = kNuErrFileStat; + goto bail; + } + + pFileInfo->isRegularFile = false; + if (S_ISREG(sbuf.st_mode)) + pFileInfo->isRegularFile = true; + pFileInfo->isDirectory = false; + if (S_ISDIR(sbuf.st_mode)) + pFileInfo->isDirectory = true; + + /* BUG: should check for 32-bit overflow from 64-bit off_t */ + pFileInfo->dataEof = sbuf.st_size; + pFileInfo->rsrcEof = 0; + pFileInfo->fileType = kDefaultFileType; + pFileInfo->auxType = kDefaultAuxType; + Nu_GMTSecondsToDateTime(&sbuf.st_mtime, &pFileInfo->modWhen); + pFileInfo->unixMode = sbuf.st_mode; + pFileInfo->isValid = true; + } +#else + #error "Port this" +#endif + +bail: + return err; +} + + +/* + * Determine whether a specific fork in the file exists. + * + * On systems that don't support forked files, the "checkRsrcFork" argument + * is ignored. If forked files are supported, and we are extracting a + * file with data and resource forks, we only claim it exists if it has + * nonzero length. + */ +static NuError +Nu_FileForkExists(NuArchive* pArchive, const char* pathname, + Boolean isForkedFile, Boolean checkRsrcFork, Boolean* pExists, + NuFileInfo* pFileInfo) +{ + NuError err = kNuErrNone; + + Assert(pArchive != nil); + Assert(pathname != nil); + Assert(checkRsrcFork == true || checkRsrcFork == false); + Assert(pExists != nil); + Assert(pFileInfo != nil); + +#if defined(UNIX_LIKE) || defined(WINDOWS_LIKE) + /* + * We ignore "isForkedFile" and "checkRsrcFork". The file must not + * exist at all. + */ + Assert(pArchive->lastFileCreated == nil); + + *pExists = true; + err = Nu_GetFileInfo(pArchive, pathname, pFileInfo); + if (err == kNuErrFileNotFound) { + err = kNuErrNone; + *pExists = false; + } + +#elif defined(__ORCAC__) + /* + * If the file doesn't exist, great. If it does, and "lastFileCreated" + * matches up with this one, then we know that it exists because we + * created it. + * + * This is great unless the record has two data forks or something + * equally dopey, so we check to be sure that the fork we want to + * put the data into is currently empty. + * + * It is possible, though asinine, for a Mac OS or GS/OS extraction + * program to put the data and resource forks of a record into + * separate files, so we can't just assume that because we wrote + * the data fork to file A it is okay for file B to exist. That's + * why we compare the pathname instead of just remembering that + * we already created a file for this record. + */ + #error "Finish me" + +#else + #error "Port this" +#endif + + return err; +} + + +/* + * Set the dates on a file according to what's in the record. + */ +static NuError +Nu_SetFileDates(NuArchive* pArchive, const NuRecord* pRecord, + const char* pathname) +{ + NuError err = kNuErrNone; + + Assert(pArchive != nil); + Assert(pRecord != nil); + Assert(pathname != nil); + +#if defined(UNIX_LIKE) || defined(WINDOWS_LIKE) + { + struct utimbuf utbuf; + + /* ignore create time, and set access time equal to mod time */ + Nu_DateTimeToGMTSeconds(&pRecord->recModWhen, &utbuf.modtime); + utbuf.actime = utbuf.modtime; + + /* only do it if the NuDateTime was valid */ + if (utbuf.modtime) { + if (utime(pathname, &utbuf) < 0) { + Nu_ReportError(NU_BLOB, errno, + "Unable to set time stamp on '%s'", pathname); + err = kNuErrFileSetDate; + goto bail; + } + } + } + +#else + #error "Port this" +#endif + +bail: + return err; +} + + +/* + * Returns "true" if the record is locked (in the ProDOS sense). + * + * Bits 31-8 reserved, must be zero + * Bit 7 (D) 1 = destroy enabled + * Bit 6 (R) 1 = rename enabled + * Bit 5 (B) 1 = file needs to be backed up + * Bits 4-3 reserved, must be zero + * Bit 2 (I) 1 = file is invisible + * Bit 1 (W) 1 = write enabled + * Bit 0 (R) 1 = read enabled + * + * A "locked" file would be 00?00001, "unlocked" 11?00011, with many + * possible variations. For our purposes, we treat all files as unlocked + * unless they match the classic "locked" bit pattern. + */ +static Boolean +Nu_IsRecordLocked(const NuRecord* pRecord) +{ + if (pRecord->recAccess == 0x21L || pRecord->recAccess == 0x01L) + return true; + else + return false; +} + +/* + * Set the file access permissions based on what's in the record. + * + * This assumes that the file is currently writable, so we only need + * to do something if the original file was "locked". + */ +static NuError +Nu_SetFileAccess(NuArchive* pArchive, const NuRecord* pRecord, + const char* pathname) +{ + NuError err = kNuErrNone; + + Assert(pArchive != nil); + Assert(pRecord != nil); + Assert(pathname != nil); + +#if defined(UNIX_LIKE) || defined(WINDOWS_LIKE) + /* only need to do something if the file was "locked" */ + if (Nu_IsRecordLocked(pRecord)) { + /* set it to 444 */ + if (chmod(pathname, S_IRUSR | S_IRGRP | S_IROTH) < 0) { + Nu_ReportError(NU_BLOB, errno, + "unable to set access for '%s'", pathname); + err = kNuErrFileSetAccess; + goto bail; + } + } + +#else + #error "Port this" +#endif + +bail: + return err; +} + + +/* + * =========================================================================== + * Create/open an output file + * =========================================================================== + */ + +/* + * Prepare an existing file for writing. + * + * Generally this just involves ensuring that the file is writable. If + * this is a convenient place to truncate it, we should do that too. + */ +static NuError +Nu_PrepareForWriting(NuArchive* pArchive, const char* pathname, + Boolean prepRsrc, NuFileInfo* pFileInfo) +{ + NuError err = kNuErrNone; + + Assert(pArchive != nil); + Assert(pathname != nil); + Assert(prepRsrc == true || prepRsrc == false); + Assert(pFileInfo != nil); + + Assert(pFileInfo->isValid == true); + + /* don't go playing with directories, pipes, etc */ + if (pFileInfo->isRegularFile != true) + return kNuErrNotRegularFile; + +#if defined(UNIX_LIKE) || defined(WINDOWS_LIKE) + if (!(pFileInfo->unixMode & S_IWUSR)) { + /* make it writable by owner, plus whatever it was before */ + if (chmod(pathname, S_IWUSR | pFileInfo->unixMode) < 0) { + Nu_ReportError(NU_BLOB, errno, + "unable to set access for '%s'", pathname); + err = kNuErrFileSetAccess; + goto bail; + } + } + + return kNuErrNone; + +#else + #error "Port this" +#endif + +bail: + return err; +} + + +/* + * Invoke the system-dependent directory creation function. + */ +static NuError +Nu_Mkdir(NuArchive* pArchive, const char* dir) +{ + NuError err = kNuErrNone; + + Assert(pArchive != nil); + 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; + Nu_ReportError(NU_BLOB, err, "Unable to create dir '%s'", dir); + goto bail; + } + +#elif defined(WINDOWS_LIKE) + if (mkdir(dir) < 0) { + err = errno ? errno : kNuErrDirCreate; + Nu_ReportError(NU_BLOB, err, "Unable to create dir '%s'", dir); + goto bail; + } + +#else + #error "Port this" +#endif + +bail: + return err; +} + + +/* + * Create a single subdirectory if it doesn't exist. If the next-highest + * subdirectory level doesn't exist either, cut down the pathname and + * recurse. + */ +static NuError +Nu_CreateSubdirIFN(NuArchive* pArchive, const char* pathStart, + const char* pathEnd, char fssep) +{ + NuError err = kNuErrNone; + NuFileInfo fileInfo; + char* tmpBuf = nil; + + Assert(pArchive != nil); + Assert(pathStart != nil); + Assert(pathEnd != nil); + Assert(fssep != '\0'); + + /* pathStart might have whole path, but we only want up to "pathEnd" */ + tmpBuf = strdup(pathStart); + tmpBuf[pathEnd - pathStart +1] = '\0'; + + err = Nu_GetFileInfo(pArchive, tmpBuf, &fileInfo); + if (err == kNuErrFileNotFound) { + /* dir doesn't exist; move up a level and check parent */ + pathEnd = strrchr(tmpBuf, fssep); + if (pathEnd != nil) { + pathEnd--; + Assert(pathEnd >= tmpBuf); + err = Nu_CreateSubdirIFN(pArchive, tmpBuf, pathEnd, fssep); + BailError(err); + } + + /* parent is taken care of; create this one */ + err = Nu_Mkdir(pArchive, tmpBuf); + BailError(err); + } else if (err != kNuErrNone) { + goto bail; + } else { + /* file does exist, make sure it's a directory */ + Assert(fileInfo.isValid == true); + if (!fileInfo.isDirectory) { + err = kNuErrNotDir; + Nu_ReportError(NU_BLOB, err, "Unable to create path '%s'", tmpBuf); + goto bail; + } + } + +bail: + Nu_Free(pArchive, tmpBuf); + return err; +} + +/* + * Create subdirectories, if needed. The paths leading up to the filename + * in "pathname" will be created. + * + * If "pathname" is just a filename, or the set of directories matches + * the last directory we created, we don't do anything. + */ +static NuError +Nu_CreatePathIFN(NuArchive* pArchive, const char* pathname, char fssep) +{ + NuError err = kNuErrNone; + const char* pathStart; + const char* pathEnd; + + Assert(pArchive != nil); + Assert(pathname != nil); + Assert(fssep != '\0'); + + pathStart = pathname; + if (pathname[0] == fssep) + pathStart++; + + /* NOTE: not expecting names like "foo/bar/ack/", with terminating fssep */ + pathEnd = strrchr(pathStart, fssep); + if (pathEnd == nil) { + /* no subdirectory components found */ + goto bail; + } + pathEnd--; + + Assert(pathEnd >= pathStart); + if (pathEnd - pathStart < 0) + goto bail; + + /* (on some filesystems, strncasecmp would be appropriate here) */ + if (pArchive->lastDirCreated && + strncmp(pathStart, pArchive->lastDirCreated, pathEnd - pathStart +1) == 0) + { + /* we created this one recently, don't do it again */ + goto bail; + } + + /* + * Test to determine which directories exist. The most likely case + * is that some or all of the components have already been created, + * so we start with the last one and work backward. + */ + err = Nu_CreateSubdirIFN(pArchive, pathStart, pathEnd, fssep); + BailError(err); + +bail: + return err; +} + + +/* + * Open the file for writing, possibly truncating it. + */ +static NuError +Nu_OpenFileForWrite(NuArchive* pArchive, const char* pathname, + Boolean openRsrc, FILE** pFp) +{ + *pFp = fopen(pathname, kNuFileOpenWriteTrunc); + if (*pFp == nil) + return errno ? errno : -1; + return kNuErrNone; +} + + +/* + * Open an output file and prepare it for writing. + * + * There are a number of things to take into consideration, including + * deal with "file exists" conditions, handling Mac/IIgs file types, + * coping with resource forks on extended files, and handling the + * "freshen" option that requires us to only update files that are + * older than what we have. + */ +NuError +Nu_OpenOutputFile(NuArchive* pArchive, const NuRecord* pRecord, + const NuThread* pThread, const char* newPathname, char newFssep, + FILE** pFp) +{ + NuError err = kNuErrNone; + Boolean exists, isForkedFile, extractingRsrc = false; + NuFileInfo fileInfo; + NuErrorStatus errorStatus; + NuResult result; + + Assert(pArchive != nil); + Assert(pRecord != nil); + Assert(pThread != nil); + Assert(newPathname != nil); + Assert(pFp != nil); + + /* set up some defaults, in case something goes wrong */ + errorStatus.operation = kNuOpExtract; + errorStatus.err = kNuErrInternal; + errorStatus.sysErr = 0; + errorStatus.message = nil; + errorStatus.pRecord = pRecord; + errorStatus.pathname = newPathname; + errorStatus.filenameSeparator = newFssep; + /*errorStatus.origArchiveTouched = false;*/ + errorStatus.canAbort = true; + errorStatus.canRetry = true; + errorStatus.canIgnore = false; + errorStatus.canSkip = true; + errorStatus.canRename = true; + errorStatus.canOverwrite = true; + + /* decide if this is a forked file (i.e. has *both* forks) */ + isForkedFile = Nu_IsForkedFile(pArchive, pRecord); + + /* decide if we're extracting a resource fork */ + if (NuMakeThreadID(pThread->thThreadClass, pThread->thThreadKind) == + kNuThreadIDRsrcFork) + { + extractingRsrc = true; + } + + /* + * Determine whether the file and fork already exists. If the file + * is one we just created, and the fork we want to write to is + * empty, this will *not* set "exists". + */ + fileInfo.isValid = false; + err = Nu_FileForkExists(pArchive, newPathname, isForkedFile, + extractingRsrc, &exists, &fileInfo); + BailError(err); + + if (exists) { + Assert(fileInfo.isValid == true); + + /* + * The file exists when it shouldn't. Decide what to do, based + * on the options configured by the application. + */ + + /* + * Start by checking to see if we're willing to overwrite older files. + * If not, see if the application wants to rename the file, or force + * the overwrite. Most likely they'll just want to skip it. + */ + if ((pArchive->valOnlyUpdateOlder) && + !Nu_IsOlder(&fileInfo.modWhen, &pRecord->recModWhen)) + { + if (pArchive->errorHandlerFunc != nil) { + errorStatus.err = kNuErrNotNewer; + result = (*pArchive->errorHandlerFunc)(pArchive, &errorStatus); + + switch (result) { + case kNuAbort: + err = kNuErrAborted; + goto bail; + case kNuRetry: + case kNuRename: + err = kNuErrRename; + goto bail; + case kNuSkip: + err = kNuErrSkipped; + goto bail; + case kNuOverwrite: + break; /* fall back into main code */ + case kNuIgnore: + default: + err = kNuErrSyntax; + Nu_ReportError(NU_BLOB, err, + "Wasn't expecting result %d here", result); + goto bail; + } + } else { + err = kNuErrNotNewer; + goto bail; + } + } + + /* If they "might" allow overwrites, and they have an error-handling + * callback defined, call that to find out what they want to do + * here. Options include skipping the file, overwriting the file, + * and extracting to a different file. + */ + if (pArchive->valHandleExisting == kNuMaybeOverwrite) { + if (pArchive->errorHandlerFunc != nil) { + errorStatus.err = kNuErrFileExists; + result = (*pArchive->errorHandlerFunc)(pArchive, &errorStatus); + + switch (result) { + case kNuAbort: + err = kNuErrAborted; + goto bail; + case kNuRetry: + case kNuRename: + err = kNuErrRename; + goto bail; + case kNuSkip: + err = kNuErrSkipped; + goto bail; + case kNuOverwrite: + break; /* fall back into main code */ + case kNuIgnore: + default: + err = kNuErrSyntax; + Nu_ReportError(NU_BLOB, err, + "Wasn't expecting result %d here", result); + goto bail; + } + } else { + /* no error handler, return an error to the caller */ + err = kNuErrFileExists; + goto bail; + } + } else if (pArchive->valHandleExisting == kNuNeverOverwrite) { + err = kNuErrSkipped; + goto bail; + } + } else { + /* + * The file doesn't exist. If we're doing a "freshen" from the + * archive, we don't want to create a new file, so we return an + * error to the user instead. However, we give the application + * a chance to straighten things out. Most likely they'll just + * return kNuSkip. + */ + if (pArchive->valHandleExisting == kNuMustOverwrite) { + DBUG(("+++ can't freshen nonexistent file '%s'\n", newPathname)); + if (pArchive->errorHandlerFunc != nil) { + errorStatus.err = kNuErrDuplicateNotFound; + + /* give them a chance to rename */ + result = (*pArchive->errorHandlerFunc)(pArchive, &errorStatus); + switch (result) { + case kNuAbort: + err = kNuErrAborted; + goto bail; + case kNuRetry: + case kNuRename: + err = kNuErrRename; + goto bail; + case kNuSkip: + err = kNuErrSkipped; + goto bail; + case kNuOverwrite: + break; /* fall back into main code */ + case kNuIgnore: + default: + err = kNuErrSyntax; + Nu_ReportError(NU_BLOB, err, + "Wasn't expecting result %d here", result); + goto bail; + } + } else { + /* no error handler, return an error to the caller */ + err = kNuErrDuplicateNotFound; + goto bail; + } + } + } + + Assert(err == kNuErrNone); + + /* + * After the above, if the file exists then we need to prepare it for + * writing. On some systems -- notably those with forked files -- it + * may be easiest to delete the entire file and start over. On + * simpler systems, an (optional) chmod followed by an open that + * truncates the file should be sufficient. + * + * If the file didn't exist, we need to be sure that the path leading + * up to its eventual location exists. This might require creating + * several directories. We distinguish the case of "file isn't there" + * from "file is there but fork isn't" by seeing if we were able to + * get valid file info. + */ + if (exists) { + Assert(fileInfo.isValid == true); + err = Nu_PrepareForWriting(pArchive, newPathname, extractingRsrc, + &fileInfo); + BailError(err); + } else if (!fileInfo.isValid) { + err = Nu_CreatePathIFN(pArchive, newPathname, newFssep); + BailError(err); + } + + /* + * Open sesame. + */ + err = Nu_OpenFileForWrite(pArchive, newPathname, extractingRsrc, pFp); + BailError(err); + + +#if defined(HAS_RESOURCE_FORKS) + pArchive->lastFileCreated = newPathname; +#endif + +bail: + if (err != kNuErrNone) { + if (err != kNuErrSkipped && err != kNuErrRename && + err != kNuErrFileExists) + { + Nu_ReportError(NU_BLOB, err, "Unable to open '%s'%s", + newPathname, extractingRsrc ? " (rsrc fork)" : ""); + } + } + return err; +} + + +/* + * Close the output file, adjusting the modification date and access + * permissions as needed. + * + * On GS/OS and Mac OS, we may need to set the file type here, depending on + * how much we managed to do when the file was first created. IIRC, + * the GS/OS Open call should allow setting the file type. + * + * BUG: on GS/OS, if we set the file access after writing the data fork, + * we may not be able to open the same file for writing the rsrc fork. + * We can't suppress setting the access permissions, because we don't know + * if the application will want to write both forks to the same file, or + * for that matter will want to write the resource fork at all. Looks + * like we will have to be smart enough to reset the access permissions + * when writing a rsrc fork to a file with just a data fork. This isn't + * quite right, but it's close enough. + */ +NuError +Nu_CloseOutputFile(NuArchive* pArchive, const NuRecord* pRecord, FILE* fp, + const char* pathname) +{ + NuError err; + + Assert(pArchive != nil); + Assert(pRecord != nil); + Assert(fp != nil); + + fclose(fp); + + err = Nu_SetFileDates(pArchive, pRecord, pathname); + BailError(err); + + err = Nu_SetFileAccess(pArchive, pRecord, pathname); + BailError(err); + +bail: + return kNuErrNone; +} + +/* + * =========================================================================== + * Open an input file + * =========================================================================== + */ + +/* + * Open the file for reading, in "binary" mode when necessary. + */ +static NuError +Nu_OpenFileForRead(NuArchive* pArchive, const char* pathname, + Boolean openRsrc, FILE** pFp) +{ + *pFp = fopen(pathname, kNuFileOpenReadOnly); + if (*pFp == nil) + return errno ? errno : -1; + return kNuErrNone; +} + + +/* + * Open an input file and prepare it for reading. + * + * If the file can't be found, we give the application an opportunity to + * skip the absent file, retry, or abort the whole thing. + */ +NuError +Nu_OpenInputFile(NuArchive* pArchive, const char* pathname, + Boolean openRsrc, FILE** pFp) +{ + NuError err = kNuErrNone; + NuError openErr = kNuErrNone; + NuErrorStatus errorStatus; + NuResult result; + + Assert(pArchive != nil); + Assert(pathname != nil); + Assert(pFp != nil); + +retry: + /* + * Open sesame. + */ + err = Nu_OpenFileForRead(pArchive, pathname, openRsrc, pFp); + if (err == kNuErrNone) /* success! */ + goto bail; + + if (err == ENOENT) + openErr = kNuErrFileNotFound; + + if (pArchive->errorHandlerFunc != nil) { + errorStatus.operation = kNuOpAdd; + errorStatus.err = openErr; + errorStatus.sysErr = 0; + errorStatus.message = nil; + errorStatus.pRecord = nil; + errorStatus.pathname = pathname; + errorStatus.filenameSeparator = '\0'; + /*errorStatus.origArchiveTouched = false;*/ + errorStatus.canAbort = true; + errorStatus.canRetry = true; + errorStatus.canIgnore = false; + errorStatus.canSkip = true; + errorStatus.canRename = false; + errorStatus.canOverwrite = false; + + DBUG(("--- invoking error handler function\n")); + result = (*pArchive->errorHandlerFunc)(pArchive, &errorStatus); + + switch (result) { + case kNuAbort: + err = kNuErrAborted; + goto bail; + case kNuRetry: + goto retry; + case kNuSkip: + err = kNuErrSkipped; + goto bail; + case kNuRename: + case kNuOverwrite: + case kNuIgnore: + default: + err = kNuErrSyntax; + Nu_ReportError(NU_BLOB, err, + "Wasn't expecting result %d here", result); + goto bail; + } + } else { + DBUG(("+++ no error callback in OpenInputFile\n")); + } + +bail: + if (err == kNuErrNone) { + Assert(*pFp != nil); + } else { + if (err != kNuErrSkipped && err != kNuErrRename && + err != kNuErrFileExists) + { + Nu_ReportError(NU_BLOB, err, "Unable to open '%s'%s", + pathname, openRsrc ? " (rsrc fork)" : ""); + } + } + return err; +} + + +/* + * =========================================================================== + * Delete and rename files + * =========================================================================== + */ + +/* + * Delete a file. + */ +NuError +Nu_DeleteFile(const char* pathname) +{ +#if defined(UNIX_LIKE) || defined(WINDOWS_LIKE) + int cc; + + DBUG(("--- Deleting '%s'\n", pathname)); + + cc = unlink(pathname); + if (cc < 0) + return errno ? errno : -1; + else + return kNuErrNone; +#else + #error "Port this" +#endif +} + +/* + * Rename a file from "fromPath" to "toPath". + */ +NuError +Nu_RenameFile(const char* fromPath, const char* toPath) +{ +#if defined(UNIX_LIKE) || defined(WINDOWS_LIKE) + int cc; + + DBUG(("--- Renaming '%s' to '%s'\n", fromPath, toPath)); + + cc = rename(fromPath, toPath); + if (cc < 0) + return errno ? errno : -1; + else + return kNuErrNone; +#else + #error "Port this" +#endif +} + + +/* + * =========================================================================== + * NuError wrappers for std functions + * =========================================================================== + */ + +/* + * Wrapper for ftell(). + */ +NuError +Nu_FTell(FILE* fp, long* pOffset) +{ + Assert(fp != nil); + Assert(pOffset != nil); + + errno = 0; + *pOffset = ftell(fp); + if (*pOffset < 0) { + Nu_ReportError(NU_NILBLOB, errno, "file ftell failed"); + return errno ? errno : kNuErrFileSeek; + } + return kNuErrNone; +} + +/* + * Wrapper for fseek(). + */ +NuError +Nu_FSeek(FILE* fp, long offset, int ptrname) +{ + Assert(fp != nil); + Assert(ptrname == SEEK_SET || ptrname == SEEK_CUR || ptrname == SEEK_END); + + errno = 0; + if (fseek(fp, offset, ptrname) < 0) { + Nu_ReportError(NU_NILBLOB, errno, + "file fseek(%ld, %d) failed", offset, ptrname); + return errno ? errno : kNuErrFileSeek; + } + return kNuErrNone; +} + +/* + * Wrapper for fread(). Note the arguments resemble read(2) rather than the + * slightly silly ones used by fread(3S). + */ +NuError +Nu_FRead(FILE* fp, void* buf, size_t nbyte) +{ + size_t result; + + errno = 0; + result = fread(buf, nbyte, 1, fp); + if (result != 1) + return errno ? errno : kNuErrFileRead; + return kNuErrNone; +} + +/* + * Wrapper for fwrite(). Note the arguments resemble write(2) rather than the + * slightly silly ones used by fwrite(3S). + */ +NuError +Nu_FWrite(FILE* fp, const void* buf, size_t nbyte) +{ + size_t result; + + errno = 0; + result = fwrite(buf, nbyte, 1, fp); + if (result != 1) + return errno ? errno : kNuErrFileWrite; + return kNuErrNone; +} + +/* + * =========================================================================== + * Misc functions + * =========================================================================== + */ + +/* + * Copy a section from one file to another. + */ +NuError +Nu_CopyFileSection(NuArchive* pArchive, FILE* dstFp, FILE* srcFp, long length) +{ + NuError err; + long readLen; + + Assert(pArchive != nil); + Assert(dstFp != nil); + Assert(srcFp != nil); + Assert(length); + + /* nice big buffer, for speed... could use getc/putc for simplicity */ + err = Nu_AllocCompressionBufferIFN(pArchive); + BailError(err); + + DBUG(("+++ Copying %ld bytes\n", length)); + + while (length) { + readLen = length > kNuGenCompBufSize ? kNuGenCompBufSize : length; + + err = Nu_FRead(srcFp, pArchive->compBuf, readLen); + BailError(err); + err = Nu_FWrite(dstFp, pArchive->compBuf, readLen); + BailError(err); + + length -= readLen; + } + +bail: + return err; +} + + +/* + * Find the length of an open file. + * + * On UNIX it would be easier to just call fstat(), but fseek is portable. + * + * Only useful for files < 2GB in size. + * + * (pArchive is only used for BailError message reporting, so it's okay + * to call here with a nil pointer if the archive isn't open yet.) + */ +NuError +Nu_GetFileLength(NuArchive* pArchive, FILE* fp, long* pLength) +{ + NuError err; + long oldpos; + + Assert(fp != nil); + Assert(pLength != nil); + + err = Nu_FTell(fp, &oldpos); + BailError(err); + + err = Nu_FSeek(fp, 0, SEEK_END); + BailError(err); + + err = Nu_FTell(fp, pLength); + BailError(err); + + err = Nu_FSeek(fp, oldpos, SEEK_SET); + BailError(err); + +bail: + return err; +} + + +/* + * Truncate an open file. This differs from ftruncate() in that it takes + * a FILE* instead of an fd, and the length is a long instead of off_t. + */ +NuError +Nu_TruncateOpenFile(FILE* fp, long length) +{ + #if defined(HAVE_FTRUNCATE) + if (ftruncate(fileno(fp), length) < 0) + return errno ? errno : -1; + return kNuErrNone; + #elif defined(HAVE_CHSIZE) + if (chsize(fileno(fp), length) < 0) + return errno ? errno : -1; + return kNuErrNone; + #else + return kNuErrInternal; + #endif +} + diff --git a/nufxlib-0/Funnel.c b/nufxlib-0/Funnel.c new file mode 100644 index 0000000..0b498b2 --- /dev/null +++ b/nufxlib-0/Funnel.c @@ -0,0 +1,804 @@ +/* + * NuFX archive manipulation library + * 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 Library General Public License, see the file COPYING.LIB. + * + * Implementation of NuFunnel, NuStraw and ProgressUpdater. + */ +#include "NufxLibPriv.h" + + +/* + * =========================================================================== + * Progress updater + * =========================================================================== + */ + +/* + * Initialize the fields in a ProgressData structure, prior to compressing + * data into a record. + * + * The same structure will be used when expanding all threads in a given + * record. + */ +NuError +Nu_ProgressDataInit_Compress(NuArchive* pArchive, NuProgressData* pProgressData, + const NuRecord* pRecord, const char* origPathname) +{ + const char* cp; + + Assert(pProgressData != nil); + Assert(pArchive != nil); + Assert(pRecord != nil); + Assert(origPathname != nil); + + pProgressData->pRecord = pRecord; + + pProgressData->origPathname = origPathname; + pProgressData->pathname = pRecord->filename; + cp = strrchr(pRecord->filename, + NuGetSepFromSysInfo(pRecord->recFileSysInfo)); + if (cp == nil || *(cp+1) == '\0') + pProgressData->filename = pProgressData->pathname; + else + pProgressData->filename = cp+1; + + pProgressData->operation = kNuOpAdd; + pProgressData->state = kNuProgressPreparing; + /*pProgressData->compressedLength = 0;*/ + /*pProgressData->compressedProgress = 0;*/ + pProgressData->uncompressedLength = 0; + pProgressData->uncompressedProgress = 0; + + pProgressData->compress.threadFormat = (NuThreadFormat)-1; + + /* ya know... if this is nil, none of the above matters much */ + pProgressData->progressFunc = pArchive->progressUpdaterFunc; + + return kNuErrNone; +} + + +/* + * Initialize the fields in a ProgressData structure, prior to expanding + * data from a record. + * + * The same structure will be used when expanding all threads in a given + * record. + */ +NuError +Nu_ProgressDataInit_Expand(NuArchive* pArchive, NuProgressData* pProgressData, + const NuRecord* pRecord, const char* newPathname, char newFssep, + NuValue convertEOL) +{ + const NuThread* pThreadIter; + const char* cp; + int i; + + Assert(pProgressData != nil); + Assert(pArchive != nil); + Assert(pRecord != nil); + Assert(newPathname != nil); + Assert(newFssep != 0); + + pProgressData->pRecord = pRecord; + pProgressData->expand.pThread = nil; + + pProgressData->origPathname = pRecord->filename; + pProgressData->pathname = newPathname; + cp = strrchr(newPathname, newFssep); + if (cp == nil || *(cp+1) == '\0') + pProgressData->filename = newPathname; + else + pProgressData->filename = cp+1; + + pProgressData->expand.convertEOL = convertEOL; + + /* total up the data threads */ + pProgressData->expand.totalCompressedLength = 0; + pProgressData->expand.totalUncompressedLength = 0; + + for (i = 0; i < (int)pRecord->recTotalThreads; i++) { + pThreadIter = Nu_GetThread(pRecord, i); + if (pThreadIter->thThreadClass != kNuThreadClassData) + continue; + pProgressData->expand.totalCompressedLength += pThreadIter->thCompThreadEOF; + pProgressData->expand.totalUncompressedLength += pThreadIter->actualThreadEOF; + } + + pProgressData->operation = kNuOpExtract; + if (pArchive->testMode) + pProgressData->operation = kNuOpTest; + pProgressData->state = kNuProgressPreparing; + /*pProgressData->expand.compressedLength = 0;*/ + /*pProgressData->expand.compressedProgress = 0;*/ + pProgressData->uncompressedLength = 0; + pProgressData->uncompressedProgress = 0; + + /* ya know... if this is nil, none of the above matters much */ + pProgressData->progressFunc = pArchive->progressUpdaterFunc; + + return kNuErrNone; +} + + +/* + * Do the setup on a ProgressData prior to compressing a thread. + */ +NuError +Nu_ProgressDataCompressPrep(NuArchive* pArchive, NuStraw* pStraw, + NuThreadFormat threadFormat, ulong sourceLen) +{ + NuProgressData* pProgressData; + + Assert(pArchive != nil); + Assert(pStraw != nil); + Assert(sourceLen < 32767*65536); + + pProgressData = pStraw->pProgress; + if (pProgressData == nil) + return kNuErrNone; + + pProgressData->uncompressedLength = sourceLen; + pProgressData->compress.threadFormat = threadFormat; + + return kNuErrNone; +} + +/* + * Do the setup on a ProgressData prior to expanding a thread. + * + * "pThread" is the thread being expanded. + */ +NuError +Nu_ProgressDataExpandPrep(NuArchive* pArchive, NuFunnel* pFunnel, + const NuThread* pThread) +{ + NuProgressData* pProgressData; + + Assert(pArchive != nil); + Assert(pFunnel != nil); + Assert(pThread != nil); + + pProgressData = pFunnel->pProgress; + if (pProgressData == nil) + return kNuErrNone; + + /*pProgressData->compressedLength = pThread->thCompThreadEOF;*/ + pProgressData->uncompressedLength = pThread->actualThreadEOF; + pProgressData->expand.pThread = pThread; + + return kNuErrNone; +} + +/* + * Send the initial progress message, before the output file is opened + * (when extracting) or the input file is opened (when adding). + */ +NuError +Nu_SendInitialProgress(NuArchive* pArchive, const NuProgressData* pProgress) +{ + NuResult result; + + Assert(pArchive != nil); + Assert(pProgress != nil); + + if (pProgress->progressFunc == nil) + return kNuErrNone; + + result = (*pProgress->progressFunc)(pArchive, (NuProgressData*) pProgress); + + if (result == kNuSkip) + return kNuErrSkipped; /* [dunno how well this works] */ + if (result == kNuAbort) + return kNuErrAborted; + + return kNuErrNone; +} + + +/* + * =========================================================================== + * NuFunnel object + * =========================================================================== + */ + +/* + * Allocate and initialize a Funnel. + */ +NuError +Nu_FunnelNew(NuArchive* pArchive, NuDataSink* pDataSink, NuValue convertEOL, + NuValue convertEOLTo, NuProgressData* pProgress, NuFunnel** ppFunnel) +{ + NuError err = kNuErrNone; + NuFunnel* pFunnel = nil; + + Assert(ppFunnel != nil); + Assert(pDataSink != nil); + Assert(convertEOL == kNuConvertOff || + convertEOL == kNuConvertOn || + convertEOL == kNuConvertAuto); + + pFunnel = Nu_Calloc(pArchive, sizeof(*pFunnel)); + BailAlloc(pFunnel); + pFunnel->buffer = Nu_Malloc(pArchive, kNuFunnelBufSize); + BailAlloc(pFunnel->buffer); + + pFunnel->pDataSink = pDataSink; + pFunnel->convertEOL = convertEOL; + pFunnel->convertEOLTo = convertEOLTo; + pFunnel->pProgress = pProgress; + +bail: + if (err != kNuErrNone) + Nu_FunnelFree(pArchive, pFunnel); + else + *ppFunnel = pFunnel; + return err; +} + + +/* + * Free a Funnel. + * + * The data should already have been written; it's not the duty of a + * "free" function to flush data out. + */ +NuError +Nu_FunnelFree(NuArchive* pArchive, NuFunnel* pFunnel) +{ + if (pFunnel == nil) + return kNuErrNone; + +#ifdef DEBUG_MSGS + if (pFunnel->bufCount) + Nu_ReportError(NU_BLOB_DEBUG, kNuErrNone, + "freeing non-empty funnel"); +#endif + + Nu_Free(pArchive, pFunnel->buffer); + Nu_Free(pArchive, pFunnel); + + return kNuErrNone; +} + + +#if 0 +/* + * Set the maximum amount of output we're willing to push through the + * funnel. Attempts to write more than this many bytes will fail. This + * allows us to bail out as soon as it's apparent that compression is + * failing and is actually resulting in a larger file. + */ +void +Nu_FunnelSetMaxOutput(NuFunnel* pFunnel, ulong maxBytes) +{ + Assert(pFunnel != nil); + Assert(maxBytes > 0); + + pFunnel->outMax = maxBytes; + if (pFunnel->outCount >= pFunnel->outMax) + pFunnel->outMaxExceeded = true; + else + pFunnel->outMaxExceeded = false; +} +#endif + + +/* + * Table determining what's a binary character and what isn't. It would + * possibly be more compact to generate this from a simple description, + * but I'm hoping static/const data will end up in the code segment and + * save space on the heap. + * + * This corresponds to less-316's ISO-latin1 "8bcccbcc18b95.33b.". This + * may be too loose by itself; we may want to require that the lower-ASCII + * values appear in higher proportions than the upper-ASCII values. + * Otherwise we run the risk of converting a binary file with specific + * properties. + * + * The auto-detect mechanism will never be perfect though, so there's not + * much point in tweaking it to death. + */ +static const char gNuIsBinary[256] = { + 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, /* ^@-^O */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* ^P-^_ */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* - / */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0 - ? */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* @ - O */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* P - _ */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* ` - o */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, /* p - DEL */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0x80 */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0x90 */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xa0 */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xb0 */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xc0 */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xd0 */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xe0 */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xf0 */ +}; + +#define kNuMaxHighASCII 1 /* max #of binary chars per 100 bytes */ +#define kNuMinConvThreshold 40 /* min of 40 chars for auto-detect */ +/* + * Decide, based on the contents of the buffer, whether we should do an + * EOL conversion on the data. + * + * We need to decide if we are looking at text data, and if so, what kind + * of line terminator is in use. + * + * If we don't have enough data to make a determination, don't mess with it. + * + * We try to figure out whether it's CR, LF, or CRLF, so that we can + * skip the CPU-intensive conversion process if it isn't necessary. + */ +static NuValue +Nu_DetermineConversion(NuFunnel* pFunnel, const uchar* buffer, ulong count) +{ + ulong bufCount, numBinary, numLF, numCR; + uchar val; + + if (count < kNuMinConvThreshold) + return kNuConvertOff; + + bufCount = count; + numBinary = numLF = numCR = 0; + while (bufCount--) { + val = *buffer++; + if (gNuIsBinary[val]) + numBinary++; + if (val == kNuCharLF) + numLF++; + if (val == kNuCharCR) + numCR++; + } + + /* if #found is > #allowed, it's a binary file */ + if (count < 100) { + /* use simplified check on files between kNuMinConvThreshold and 100 */ + if (numBinary > kNuMaxHighASCII) + return kNuConvertOff; + } else if (numBinary > (count / 100) * kNuMaxHighASCII) + return kNuConvertOff; + + /* + * If our "convert to" setting is the same as what we're converting + * from, we can turn off the converter and speed things up. + * + * These are simplistic, but this is intended as an optimization. We + * will blow it if the input has lots of CRs and LFs scattered about, + * and they just happen to be in equal amounts, but it's not clear + * to me that an automatic EOL conversion makes sense on that sort + * of file anyway. + */ + if (numLF && !numCR) + pFunnel->convertEOLFrom = kNuEOLLF; + else if (!numLF && numCR) + pFunnel->convertEOLFrom = kNuEOLCR; + else if (numLF && numLF == numCR) + pFunnel->convertEOLFrom = kNuEOLCRLF; + else + pFunnel->convertEOLFrom = kNuEOLUnknown; + + return kNuConvertOn; +} + + +/* + * Write a block of data to the appropriate output device. Test for + * excessive data, and raise "outMaxExceeded" if we overrun. + * + * This is either a Funnel function or a DataSink function, depending on + * your perspective. + */ +static inline void +Nu_FunnelPutBlock(NuFunnel* pFunnel, const uchar* buf, ulong len) +{ + Assert(pFunnel != nil); + Assert(pFunnel->pDataSink != nil); + Assert(buf != nil); + Assert(len > 0); + +#if 0 + if (pFunnel->outMax) { + if (pFunnel->outMaxExceeded) + return; + if (pFunnel->outCount + len > pFunnel->outMax) { + pFunnel->outMaxExceeded = true; + return; + } + } + pFunnel->outCount += len; +#endif + + Nu_DataSinkPutBlock(pFunnel->pDataSink, buf, len); +} + + +/* + * Output the EOL marker requested for this system. + */ +static inline void +Nu_PutEOL(NuFunnel* pFunnel) +{ + uchar ch; + + if (pFunnel->convertEOLTo == kNuEOLCR) { + ch = kNuCharCR; + Nu_FunnelPutBlock(pFunnel, &ch, 1); + } else if (pFunnel->convertEOLTo == kNuEOLLF) { + ch = kNuCharLF; + Nu_FunnelPutBlock(pFunnel, &ch, 1); + } else if (pFunnel->convertEOLTo == kNuEOLCRLF) { + ch = kNuCharCR; + Nu_FunnelPutBlock(pFunnel, &ch, 1); + ch = kNuCharLF; + Nu_FunnelPutBlock(pFunnel, &ch, 1); + } else { + Assert(0); + } +} + +/* + * Write a buffer of data, using the EOL conversion associated with the + * funnel (if any). + * + * When converting to the system's EOL convention, we take anything + * that looks like an EOL mark and convert it. Doesn't matter if it's + * CR, LF, or CRLF; all three get converted to whatever the system uses. + */ +static NuError +Nu_FunnelWriteConvert(NuFunnel* pFunnel, const uchar* buffer, ulong count) +{ + NuError err = kNuErrNone; + ulong progressCount = count; + + /*if (pFunnel->outMaxExceeded) + return kNuErrOutMax;*/ + + if (pFunnel->convertEOL == kNuConvertAuto) { + /* + * This is the first write/flush we've done on this Funnel. + * Check the data we have buffered to decide whether or not + * we want to convert this. + */ + pFunnel->convertEOL = Nu_DetermineConversion(pFunnel, buffer, count); + DBUG(("+++ DetermineConversion --> %ld / %ld\n", pFunnel->convertEOL, + pFunnel->convertEOLFrom)); + + if (pFunnel->convertEOLFrom == pFunnel->convertEOLTo) { + DBUG(("+++ Switching redundant converter off\n")); + pFunnel->convertEOL = kNuConvertOff; + } + /* put it where the progress meter can see it */ + if (pFunnel->pProgress != nil) + pFunnel->pProgress->expand.convertEOL = pFunnel->convertEOL; + } + + if (pFunnel->convertEOL == kNuConvertOff) { + /* write it straight */ + Nu_FunnelPutBlock(pFunnel, buffer, count); + } else { + /* do the LF conversion */ + Boolean lastCR = pFunnel->lastCR; /* local copy */ + uchar uch; + + /* + * We could get a significant speed improvement here by writing + * non-EOL chars as a larger block instead of single bytes. + */ + while (count--) { + if (*buffer == kNuCharCR) { + Nu_PutEOL(pFunnel); + lastCR = true; + } else if (*buffer == kNuCharLF) { + if (!lastCR) + Nu_PutEOL(pFunnel); + lastCR = false; + } else { + uch = *buffer; + Nu_FunnelPutBlock(pFunnel, &uch, 1); + lastCR = false; + } + buffer++; + } + pFunnel->lastCR = lastCR; + + } + + /*if (pFunnel->outMaxExceeded) + err = kNuErrOutMax;*/ + + err = Nu_DataSinkGetError(pFunnel->pDataSink); + + /* update progress counter with pre-LFCR count */ + if (err == kNuErrNone && pFunnel->pProgress != nil) + pFunnel->pProgress->uncompressedProgress += progressCount; + + return err; +} + + +/* + * Flush any data currently in the funnel. + */ +NuError +Nu_FunnelFlush(NuArchive* pArchive, NuFunnel* pFunnel) +{ + NuError err = kNuErrNone; + + if (!pFunnel->bufCount) + goto bail; + + err = Nu_FunnelWriteConvert(pFunnel, pFunnel->buffer, pFunnel->bufCount); + BailError(err); + + pFunnel->bufCount = 0; + err = Nu_FunnelSendProgressUpdate(pArchive, pFunnel); + /* fall through with error */ + +bail: + return err; +} + + +/* + * Write a bunch of bytes into a funnel. They will be held in the buffer + * if they fit, or flushed out the bottom if not. + */ +NuError +Nu_FunnelWrite(NuArchive* pArchive, NuFunnel* pFunnel, const uchar* buffer, + ulong count) +{ + NuError err = kNuErrNone; + + /*pFunnel->inCount += count;*/ + + /* + * If it will fit into the buffer, just copy it in. + */ + if (pFunnel->bufCount + count < kNuFunnelBufSize) { + memcpy(pFunnel->buffer + pFunnel->bufCount, buffer, count); + pFunnel->bufCount += count; + goto bail; + } else { + /* + * Won't fit. We have to flush what we have, and we can either + * blow out what we were just given or put it at the start of + * the buffer. + */ + err = Nu_FunnelFlush(pArchive, pFunnel); + BailError(err); + + if (count >= kNuFunnelBufSize / 4) { + /* it's more than 25% of the buffer, just write it now */ + err = Nu_FunnelWriteConvert(pFunnel, buffer, count); + BailError(err); + } else { + memcpy(pFunnel->buffer, buffer, count); + pFunnel->bufCount = count; + } + goto bail; + } + +bail: + /*if (err == kNuErrNone) + err = Nu_FunnelSendProgressUpdate(pArchive, pFunnel);*/ + return err; +} + + +/* + * Set the Funnel's progress state. + */ +NuError +Nu_FunnelSetProgressState(NuFunnel* pFunnel, NuProgressState state) +{ + Assert(pFunnel != nil); + + if (pFunnel->pProgress == nil) + return kNuErrNone; + + pFunnel->pProgress->state = state; + + return kNuErrNone; +} + + +/* + * Send a progress update to the application, if they're interested. + */ +NuError +Nu_FunnelSendProgressUpdate(NuArchive* pArchive, NuFunnel* pFunnel) +{ + NuProgressData* pProgress; + + Assert(pArchive != nil); + Assert(pFunnel != nil); + + pProgress = pFunnel->pProgress; + if (pProgress == nil) + return kNuErrNone; /* no progress meter attached */ + + /* don't continue if they're not accepting progress messages */ + if (pProgress->progressFunc == nil) + return kNuErrNone; + + /* other than the choice of arguments, it's pretty much the same story */ + return Nu_SendInitialProgress(pArchive, pProgress); +} + + +/* + * Pull the "doExpand" parameter out of the data source. + */ +Boolean +Nu_FunnelGetDoExpand(NuFunnel* pFunnel) +{ + Assert(pFunnel != nil); + Assert(pFunnel->pDataSink != nil); + + return Nu_DataSinkGetDoExpand(pFunnel->pDataSink); +} + + +/* + * =========================================================================== + * NuStraw object + * =========================================================================== + */ + +/* + * Allocate and initialize a Straw. + */ +NuError +Nu_StrawNew(NuArchive* pArchive, NuDataSource* pDataSource, + NuProgressData* pProgress, NuStraw** ppStraw) +{ + NuError err = kNuErrNone; + NuStraw* pStraw = nil; + + Assert(ppStraw != nil); + Assert(pDataSource != nil); + + pStraw = Nu_Calloc(pArchive, sizeof(*pStraw)); + BailAlloc(pStraw); + pStraw->pDataSource = pDataSource; + pStraw->pProgress = pProgress; + pStraw->lastProgress = 0; + pStraw->lastDisplayed = 0; + +bail: + if (err != kNuErrNone) + Nu_StrawFree(pArchive, pStraw); + else + *ppStraw = pStraw; + return err; +} + +/* + * Free a Straw. + */ +NuError +Nu_StrawFree(NuArchive* pArchive, NuStraw* pStraw) +{ + if (pStraw == nil) + return kNuErrNone; + + /* we don't own the data source or progress meter */ + Nu_Free(pArchive, pStraw); + + return kNuErrNone; +} + + +/* + * Set the Funnel's progress state. + */ +NuError +Nu_StrawSetProgressState(NuStraw* pStraw, NuProgressState state) +{ + Assert(pStraw != nil); + Assert(pStraw->pProgress != nil); + + pStraw->pProgress->state = state; + + return kNuErrNone; +} + +/* + * Send a progress update to the application, if they're interested. + */ +NuError +Nu_StrawSendProgressUpdate(NuArchive* pArchive, NuStraw* pStraw) +{ + NuProgressData* pProgress; + + Assert(pArchive != nil); + Assert(pStraw != nil); + + pProgress = pStraw->pProgress; + if (pProgress == nil) + return kNuErrNone; /* no progress meter attached */ + + /* don't continue if they're not accepting progress messages */ + if (pProgress->progressFunc == nil) + return kNuErrNone; + + /* other than the choice of arguments, it's pretty much the same story */ + return Nu_SendInitialProgress(pArchive, pProgress); +} + + +/* + * Read data from a straw. + */ +NuError +Nu_StrawRead(NuArchive* pArchive, NuStraw* pStraw, uchar* buffer, long len) +{ + NuError err; + + Assert(pArchive != nil); + Assert(pStraw != nil); + Assert(buffer != nil); + Assert(len > 0); + + /* + * No buffering going on, so this is straightforward. + */ + + err = Nu_DataSourceGetBlock(pStraw->pDataSource, buffer, len); + BailError(err); + + /* + * Progress updating for adding is a little more complicated than + * for extracting. When extracting, the funnel controls the size + * of the output buffer, and only pushes an update when the output + * buffer fills. Here, we don't know how much will be asked for at + * a time, so we have to pace the updates or we risk flooding the + * application. + * + * We also have another problem: we want to indicate how much data + * has been processed, not how much data is *about* to be processed. + * So we have to set the percentage based on how much was requested + * on the previous call. (This assumes that whatever they asked for + * last time has already been fully processed.) + */ + if (pStraw->pProgress != nil) { + pStraw->pProgress->uncompressedProgress = pStraw->lastProgress; + pStraw->lastProgress += len; + + if (!pStraw->pProgress->uncompressedProgress || + (pStraw->pProgress->uncompressedProgress - pStraw->lastDisplayed + > (kNuFunnelBufSize * 3 / 4))) + { + err = Nu_StrawSendProgressUpdate(pArchive, pStraw); + pStraw->lastDisplayed = pStraw->pProgress->uncompressedProgress; + BailError(err); + } + + } + +bail: + return err; +} + + +/* + * Rewind a straw. This rewinds the underlying data source, and resets + * some progress counters. + */ +NuError +Nu_StrawRewind(NuArchive* pArchive, NuStraw* pStraw) +{ + assert(pStraw != nil); + assert(pStraw->pDataSource != nil); + + pStraw->lastProgress = 0; + pStraw->lastDisplayed = 0; + + return Nu_DataSourceRewind(pStraw->pDataSource); +} + diff --git a/nufxlib-0/INSTALL b/nufxlib-0/INSTALL new file mode 100644 index 0000000..50dbe43 --- /dev/null +++ b/nufxlib-0/INSTALL @@ -0,0 +1,183 @@ +Basic Installation +================== + + These are generic installation instructions. + + The `configure' shell script attempts to guess correct values for +various system-dependent variables used during compilation. It uses +those values to create a `Makefile' in each directory of the package. +It may also create one or more `.h' files containing system-dependent +definitions. Finally, it creates a shell script `config.status' that +you can run in the future to recreate the current configuration, a file +`config.cache' that saves the results of its tests to speed up +reconfiguring, and a file `config.log' containing compiler output +(useful mainly for debugging `configure'). + + If you need to do unusual things to compile the package, please try +to figure out how `configure' could check whether to do them, and mail +diffs or instructions to the address given in the `README' so they can +be considered for the next release. If at some point `config.cache' +contains results you don't want to keep, you may remove or edit it. + + The file `configure.in' is used to create `configure' by a program +called `autoconf'. You only need `configure.in' if you want to change +it or regenerate `configure' using a newer version of `autoconf'. + +The simplest way to compile this package is: + + 1. `cd' to the directory containing the package's source code and type + `./configure' to configure the package for your system. If you're + using `csh' on an old version of System V, you might need to type + `sh ./configure' instead to prevent `csh' from trying to execute + `configure' itself. + + Running `configure' takes awhile. While running, it prints some + messages telling which features it is checking for. + + 2. Type `make' to compile the package. + + 3. Optionally, type `make check' to run any self-tests that come with + the package. + + 4. Type `make install' to install the programs and any data files and + documentation. + + 5. You can remove the program binaries and object files from the + source code directory by typing `make clean'. To also remove the + files that `configure' created (so you can compile the package for + a different kind of computer), type `make distclean'. There is + also a `make maintainer-clean' target, but that is intended mainly + for the package's developers. If you use it, you may have to get + all sorts of other programs in order to regenerate files that came + with the distribution. + +Compilers and Options +===================== + + Some systems require unusual options for compilation or linking that +the `configure' script does not know about. You can give `configure' +initial values for variables by setting them in the environment. Using +a Bourne-compatible shell, you can do that on the command line like +this: + CC=c89 CFLAGS=-O2 LIBS=-lposix ./configure + +Or on systems that have the `env' program, you can do it like this: + env CPPFLAGS=-I/usr/local/include LDFLAGS=-s ./configure + +Compiling For Multiple Architectures +==================================== + + You can compile the package for more than one kind of computer at the +same time, by placing the object files for each architecture in their +own directory. To do this, you must use a version of `make' that +supports the `VPATH' variable, such as GNU `make'. `cd' to the +directory where you want the object files and executables to go and run +the `configure' script. `configure' automatically checks for the +source code in the directory that `configure' is in and in `..'. + + If you have to use a `make' that does not supports the `VPATH' +variable, you have to compile the package for one architecture at a time +in the source code directory. After you have installed the package for +one architecture, use `make distclean' before reconfiguring for another +architecture. + +Installation Names +================== + + By default, `make install' will install the package's files in +`/usr/local/bin', `/usr/local/man', etc. You can specify an +installation prefix other than `/usr/local' by giving `configure' the +option `--prefix=PATH'. + + You can specify separate installation prefixes for +architecture-specific files and architecture-independent files. If you +give `configure' the option `--exec-prefix=PATH', the package will use +PATH as the prefix for installing programs and libraries. +Documentation and other data files will still use the regular prefix. + + In addition, if you use an unusual directory layout you can give +options like `--bindir=PATH' to specify different values for particular +kinds of files. Run `configure --help' for a list of the directories +you can set and what kinds of files go in them. + + If the package supports it, you can cause programs to be installed +with an extra prefix or suffix on their names by giving `configure' the +option `--program-prefix=PREFIX' or `--program-suffix=SUFFIX'. + +Optional Features +================= + + Some packages pay attention to `--enable-FEATURE' options to +`configure', where FEATURE indicates an optional part of the package. +They may also pay attention to `--with-PACKAGE' options, where PACKAGE +is something like `gnu-as' or `x' (for the X Window System). The +`README' should mention any `--enable-' and `--with-' options that the +package recognizes. + + For packages that use the X Window System, `configure' can usually +find the X include and library files automatically, but if it doesn't, +you can use the `configure' options `--x-includes=DIR' and +`--x-libraries=DIR' to specify their locations. + +Specifying the System Type +========================== + + There may be some features `configure' can not figure out +automatically, but needs to determine by the type of host the package +will run on. Usually `configure' can figure that out, but if it prints +a message saying it can not guess the host type, give it the +`--host=TYPE' option. TYPE can either be a short name for the system +type, such as `sun4', or a canonical name with three fields: + CPU-COMPANY-SYSTEM + +See the file `config.sub' for the possible values of each field. If +`config.sub' isn't included in this package, then this package doesn't +need to know the host type. + + If you are building compiler tools for cross-compiling, you can also +use the `--target=TYPE' option to select the type of system they will +produce code for and the `--build=TYPE' option to select the type of +system on which you are compiling the package. + +Sharing Defaults +================ + + If you want to set default values for `configure' scripts to share, +you can create a site shell script called `config.site' that gives +default values for variables like `CC', `cache_file', and `prefix'. +`configure' looks for `PREFIX/share/config.site' if it exists, then +`PREFIX/etc/config.site' if it exists. Or, you can set the +`CONFIG_SITE' environment variable to the location of the site script. +A warning: not all `configure' scripts look for a site script. + +Operation Controls +================== + + `configure' recognizes the following options to control how it +operates. + +`--cache-file=FILE' + Use and save the results of the tests in FILE instead of + `./config.cache'. Set FILE to `/dev/null' to disable caching, for + debugging `configure'. + +`--help' + Print a summary of the options to `configure', and exit. + +`--quiet' +`--silent' +`-q' + Do not print messages saying which checks are being made. To + suppress all normal output, redirect it to `/dev/null' (any error + messages will still be shown). + +`--srcdir=DIR' + Look for the package's source code in directory DIR. Usually + `configure' can determine that directory automatically. + +`--version' + Print the version of Autoconf used to generate the `configure' + script, and exit. + +`configure' also accepts some other, not widely useful, options. + diff --git a/nufxlib-0/Lzw.c b/nufxlib-0/Lzw.c new file mode 100644 index 0000000..fe56a0a --- /dev/null +++ b/nufxlib-0/Lzw.c @@ -0,0 +1,1599 @@ +/* + * NuFX archive manipulation library + * 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 Library General Public License, see the file COPYING.LIB. + * + * ShrinkIt LZW functions. The original code was developed by Kent Dickey + * and Andy Nicholas. + * + * Unisys holds US patent #4,558,302 (filed June 20, 1983 and issued December + * 10, 1985). Patents are good for 17 years from date of issue, so the + * patent remains in effect until the end of 2002. + * + * The Unisys patent is one of many that covers LZW compression, but Unisys + * is the only company actively attacking anyone who uses it. The statement + * Unisys made regarding LZW (and, specifically, GIF and TIFF-LZW) says: + * + * Q: I use LZW in my programs, but not for GIF or TIFF graphics. What should + * I do? + * A: If you are not a business, and the programs are for your own personal + * non-commercial or not-for-profit use, Unisys does not require you to + * obtain a license. If they are used as part of a business and/or you sell + * the programs for commercial or for-profit purposes, then you must contact + * the Welch Patent Licensing Department at Unisys and explain your + * circumstances. They will have a license agreement for your application of + * their LZW algorithm. + * + * According to this, the use of LZW in NufxLib does not require a license. + */ +#include "NufxLibPriv.h" + +/* the LZW algorithms operate on 4K chunks */ +#define kNuLZWBlockSize 4096 + +/* a little padding to avoid mysterious crashes on bad data */ +#define kNuSafetyPadding 64 + +#define kNuLZWClearCode 0x0100 +#define kNuLZWFirstCode 0x0101 + + +/* sometimes we want to get *really* verbose rather late in a large archive */ +#ifdef DEBUG_LZW + static Boolean gNuDebugVerbose = true; + #define DBUG_LZW(x) { if (gNuDebugVerbose) { DBUG(x); } } +#else + #define DBUG_LZW ((void)0) +#endif + + +/* + * =========================================================================== + * Compression + * =========================================================================== + */ + +/* + * We use a hash function borrowed from UNIX compress, which is described + * in the v4.3 sources as: + * + * Algorithm: use open addressing double hashing (no chaining) on the + * prefix code / next character combination. We do a variant of Knuth's + * algorithm D (vol. 3, sec. 6.4) along with G. Knott's relatively-prime + * secondary probe. Here, the modular division first probe is gives way + * to a faster exclusive-or manipulation. + * + * The function used to generate it is: + * + * int c, hashf[256]; + * for (c = 256; --c >= 0; ) { + * hashf[c] = (((c & 0x7) << 7) ^ c) << (maxbits-10); + * } + * + * It is used with: + * + * hash = prefixcode ^ hashf[c]; \* c is char from getchar() *\ + * + * The value for kNuLZWHashSize determines the size of the hash table and + * the % occupancy. We want a fair number of vacancies because we probe + * when we collide. Using 5119 (0x13ff) with 12-bit codes yields 75% + * occupancy. + */ + +#define kNuLZWHashSize 5119 /* must be prime */ +#define kNuLZWEntryUnused 0 /* indicates an unused hash entry */ +#define kNuLZWHashFuncTblSize 256 /* one entry per char value */ +#define kNuLZWDefaultVol 0xfe /* use this as volume number */ +#define kNuLZWHashDelta 0x120 /* used in secondary hashing */ +#define kNuLZWMinCode kNuLZWClearCode /* smallest 12-bit LZW code */ +#define kNuLZWMaxCode 0x0fff /* largest 12-bit LZW code */ +#define kNuLZW2StopCode 0x0ffd /* LZW/2 stops here */ + +/* + * Mask of bits, from 0 to 8. + */ +static const int gBitMask[] = { + 0x00, 0x01, 0x03, 0x07, 0x0f, 0x1f, 0x3f, 0x7f, 0xff +}; + +#define kNuRLEDefaultEscape 0xdb /* ShrinkIt standard */ + +/* + * This holds all of the "big" dynamic state, plus a few things that I + * don't want to pass around. It's allocated once for each instance of + * an open archive, and re-used. + * + * The hash table consists of three parts. We have a choice for some of + * them, "ushort" or "uint". With "ushort" it uses less memory and is + * more likely to fit in a CPU cache, but on some processors you have to + * add instructions to manipulate 16-bit values in a 32-bit word. I'm + * guessing "ushort" is better overall. + */ +typedef struct LZWCompressState { + NuArchive* pArchive; + + ushort entry[kNuLZWHashSize]; /* uint or ushort */ + ushort prefix[kNuLZWMaxCode+1]; /* uint or ushort */ + uchar suffix[kNuLZWMaxCode+1]; + + ushort hashFunc[kNuLZWHashFuncTblSize]; /* uint or ushort */ + + uchar inputBuf[kNuLZWBlockSize]; /* 4K of raw input */ + uchar rleBuf[kNuLZWBlockSize*2 + kNuSafetyPadding]; + uchar lzwBuf[(kNuLZWBlockSize * 3) / 2 + kNuSafetyPadding]; + + ushort chunkCrc; /* CRC for LZW/1 */ + + /* LZW/2 state variables */ + int nextFree; + int codeBits; + int highCode; + Boolean initialClear; +} LZWCompressState; + + +/* + * Allocate some "reusable" state for LZW compression. + */ +static NuError +Nu_AllocLZWCompressState(NuArchive* pArchive) +{ + NuError err; + LZWCompressState* lzwState; + int ic; + + Assert(pArchive != nil); + Assert(pArchive->lzwCompressState == nil); + + /* allocate the general-purpose compression buffer, if needed */ + err = Nu_AllocCompressionBufferIFN(pArchive); + if (err != kNuErrNone) + return err; + + pArchive->lzwCompressState = Nu_Malloc(pArchive, sizeof(LZWCompressState)); + if (pArchive->lzwCompressState == nil) + return kNuErrMalloc; + + /* + * The "hashFunc" table only needs to be set up once. + */ + lzwState = pArchive->lzwCompressState; + for (ic = 256; --ic >= 0; ) + lzwState->hashFunc[ic] = (((ic & 0x7) << 7) ^ ic) << 2; + + return kNuErrNone; +} + + +/* + * Compress a block of input from lzwState->inputBuf to lzwState->rleBuf. + * The size of the output is returned in "*pRLESize" (will be zero if the + * block expanded instead of compressing). + * + * The maximum possible size of the output is 2x the original, which can + * only occur if the input is an alternating sequence of RLE delimiters + * and non-delimiters. It requires 3 bytes to encode a solitary 0xdb, + * so you get (4096 / 2) non-delimiters plus (4096 / 2) * 3 RLE-encoded + * delimiters. We deal with this by using an 8K output buffer, so we + * don't have to watch for overflow in the inner loop. + * + * The RLE format is " ", where count is zero-based + * (i.e. for three bytes we encode "2", allowing us to express 1-256). + */ +static NuError +Nu_CompressBlockRLE(LZWCompressState* lzwState, int* pRLESize) +{ + const uchar* inPtr = lzwState->inputBuf; + const uchar* endPtr = inPtr + kNuLZWBlockSize; + uchar* outPtr = lzwState->rleBuf; + uchar matchChar; + int matchCount; + + while (inPtr < endPtr) { + matchChar = *inPtr; + matchCount = 1; + + /* count up the matching chars */ + while (*++inPtr == matchChar && inPtr < endPtr) + matchCount++; + + if (matchCount > 3) { + if (matchCount > 256) { + /* rare case - really long match */ + while (matchCount > 256) { + *outPtr++ = kNuRLEDefaultEscape; + *outPtr++ = matchChar; + *outPtr++ = 255; + matchCount -= 256; + } + + /* take care of the odd bits -- which might not form a run! */ + if (matchCount > 3) { + *outPtr++ = kNuRLEDefaultEscape; + *outPtr++ = matchChar; + *outPtr++ = matchCount -1; + } else { + while (matchCount--) + *outPtr++ = matchChar; + } + + } else { + /* common case */ + *outPtr++ = kNuRLEDefaultEscape; + *outPtr++ = matchChar; + *outPtr++ = matchCount -1; + } + + } else { + if (matchChar == kNuRLEDefaultEscape) { + /* encode 1-3 0xDBs */ + *outPtr++ = kNuRLEDefaultEscape; + *outPtr++ = kNuRLEDefaultEscape; + *outPtr++ = matchCount -1; + } else { + while (matchCount--) + *outPtr++ = matchChar; + } + } + } + + *pRLESize = outPtr - lzwState->rleBuf; + Assert(*pRLESize > 0 && *pRLESize < sizeof(lzwState->rleBuf)); + + return kNuErrNone; +} + + +/* + * Clear the LZW table. Also resets the LZW/2 state. + */ +static void +Nu_ClearLZWTable(LZWCompressState* lzwState) +{ + Assert(lzwState != nil); + + /*DBUG_LZW(("### clear table\n"));*/ + + /* reset table entries */ + Assert(kNuLZWEntryUnused == 0); /* make sure this is okay */ + memset(lzwState->entry, 0, sizeof(lzwState->entry)); + + /* reset state variables */ + lzwState->nextFree = kNuLZWFirstCode; + lzwState->codeBits = 9; + lzwState->highCode = ~(~0 << lzwState->codeBits); /* a/k/a 0x01ff */ + lzwState->initialClear = false; +} + + +/* + * Write a variable-width LZW code to the output. "prefixCode" has the + * value to write, and "codeBits" is the width. + * + * Data is written in little-endian order (lowest byte first). The + * putcode function in LZC is probably faster, but the format isn't + * compatible with SHK. + * + * The worst conceivable expansion for LZW is 12 bits of output for every + * byte of input. Because we're using variable-width codes and LZW is + * reasonably effective at finding matches, the actual expansion will + * certainly be less. Throwing the extra 2K onto the end of the buffer + * saves us from having to check for a buffer overflow here. + * + * On exit, "*pOutBuf" will point PAST the last byte we wrote (even if + * it's a partial byte), and "*pAtBit" will contain the bit offset. + * + * (Turning this into a macro might speed things up.) + */ +static inline void +Nu_LZWPutCode(uchar** pOutBuf, ulong prefixCode, int codeBits, int* pAtBit) +{ + int atBit = *pAtBit; + uchar* outBuf = *pOutBuf; + + /*DBUG_LZW(("### PUT: prefixCode=0x%04lx, codeBits=%d, atBit=%d\n", + prefixCode, codeBits, atBit));*/ + + Assert(atBit >= 0 && atBit < sizeof(gBitMask)); + + if (atBit) { + /* align the prefix code with the existing byte */ + prefixCode <<= atBit; + + /* merge it with the buffer contents (if necessary) and write lo bits */ + outBuf--; + *outBuf = (uchar)((*outBuf & gBitMask[atBit]) | prefixCode); + outBuf++; + } else { + /* nothing to merge with; write lo byte at next posn and advance */ + *outBuf++ = (uchar)prefixCode; + } + + /* codes are at least 9 bits, so we know we have to write one more */ + *outBuf++ = (uchar)(prefixCode >> 8); + + /* in some cases, we may have to write yet another */ + atBit += codeBits; + if (atBit > 16) + *outBuf++ = (uchar)(prefixCode >> 16); + + *pAtBit = atBit & 0x07; + *pOutBuf = outBuf; +} + + +/* + * Compress a block of data with LZW, from "inputBuf" to lzwState->lzwBuf. + * + * LZW/1 is just like LZW/2, except that for the former the table is + * always cleared before this function is called. Because of this, the + * table never fills completely, so none of the table-overflow code + * ever happens. + * + * This function is patterned after the LZC compress function, rather + * than the NuLib LZW code, because the NuLib code was abysmal (a rather + * straight translation from assembly). This function differs from LZC + * in a few areas in order to make the output match GS/ShrinkIt. + * + * There is a minor bug here: if a table clear is emitted when there is + * only one character left in the input, nothing will be added to the + * hash table (as there is nothing to add) but "nextFree" will be + * advanced. This mimics GSHK's behavior, and accounts for the "resetFix" + * logic in the expansion functions. Code 0x0101 is essentially lost + * in this situation. + */ +static NuError +Nu_CompressLZWBlock(LZWCompressState* lzwState, const uchar* inputBuf, + int inputCount, int* pOutputCount) +{ + int nextFree, ic, atBit, codeBits; + int hash, hashDelta; + int prefixCode, code, highCode; + const uchar* inputEnd = inputBuf + inputCount; + /* local copies of lzwState members, for speed */ + const ushort* pHashFunc = lzwState->hashFunc; + ushort* pEntry = lzwState->entry; + ushort* pPrefix = lzwState->prefix; + uchar* pSuffix = lzwState->suffix; + uchar* outBuf = lzwState->lzwBuf; + + Assert(lzwState != nil); + Assert(inputBuf != nil); + Assert(inputCount > 0 && inputCount <= kNuLZWBlockSize); + /* make sure nobody has been messing with the types */ + Assert(sizeof(pHashFunc[0]) == sizeof(lzwState->hashFunc[0])); + Assert(sizeof(pEntry[0]) == sizeof(lzwState->entry[0])); + Assert(sizeof(pPrefix[0]) == sizeof(lzwState->prefix[0])); + Assert(sizeof(pSuffix[0]) == sizeof(lzwState->suffix[0])); + + /*DBUG_LZW(("### START LZW (nextFree=0x%04x)\n", lzwState->nextFree));*/ + + atBit = 0; + + if (lzwState->initialClear) { + /*DBUG_LZW(("### initialClear set\n"));*/ + codeBits = lzwState->codeBits; + Nu_LZWPutCode(&outBuf, kNuLZWClearCode, codeBits, &atBit); + Nu_ClearLZWTable(lzwState); + } + + table_cleared: + /* recover our state (or get newly-cleared state) */ + nextFree = lzwState->nextFree; + codeBits = lzwState->codeBits; + highCode = lzwState->highCode; + + prefixCode = *inputBuf++; + + /*DBUG_LZW(("### fchar=0x%02x\n", prefixCode));*/ + + while (inputBuf < inputEnd) { + ic = *inputBuf++; + /*DBUG_LZW(("### char=0x%02x\n", ic));*/ + + hash = prefixCode ^ pHashFunc[ic]; + code = pEntry[hash]; + + if (code != kNuLZWEntryUnused) { + /* something is here, either our prefix or a hash collision */ + if (pSuffix[code] != ic || pPrefix[code] != prefixCode) { + /* we've collided; do the secondary probe */ + hashDelta = (kNuLZWHashDelta - ic) << 2; + do { + /* rehash and keep looking */ + Assert(code >= kNuLZWMinCode && code <= kNuLZWMaxCode); + if (hash >= hashDelta) + hash -= hashDelta; + else + hash += kNuLZWHashSize - hashDelta; + Assert(hash >= 0 && hash < kNuLZWHashSize); + + if ((code = pEntry[hash]) == kNuLZWEntryUnused) + goto new_code; + } while (pSuffix[code] != ic || pPrefix[code] != prefixCode); + } + + /* else we found a matching string, and can keep searching */ + prefixCode = code; + + } else { + /* found an empty entry, add the prefix+suffix to the table */ + new_code: + Nu_LZWPutCode(&outBuf, prefixCode, codeBits, &atBit); + Assert(outBuf < lzwState->lzwBuf + sizeof(lzwState->lzwBuf)); + /*DBUG_LZW(("### outBuf now at +%d\n",outBuf - lzwState->lzwBuf));*/ + + code = nextFree; + Assert(hash < kNuLZWHashSize); + Assert(code >= kNuLZWMinCode); + Assert(code <= kNuLZWMaxCode); + + /* + * GSHK accepts 0x0ffd, and then sends the table clear + * immediately. We could improve on GSHK's compression slightly + * by using the entire table, but I want to generate the exact + * same output as GSHK. (The decoder believes the table clear + * is entry 0xffe, so we've got one more coming, and possibly + * two if we tweak getcode slightly.) + * + * Experiments show that switching to 0xffe increases the size + * of files that don't compress well, and decreases the size + * of files that do. In both cases, the difference in size + * is very small. + */ + Assert(code <= kNuLZW2StopCode); + /*if (code <= kNuLZW2StopCode) {*/ + /*DBUG_LZW(("### added new code 0x%04x prefix=0x%04x ch=0x%02x\n", + code, prefixCode, ic));*/ + + pEntry[hash] = code; + pPrefix[code] = prefixCode; + pSuffix[code] = ic; + + /* + * Check and see if it's time to increase the code size (note + * we flip earlier than LZC by one here). + */ + if (code >= highCode) { + highCode += code +1; + codeBits++; + } + + nextFree++; + + /*}*/ + + prefixCode = ic; + + /* if the table is full, clear it (only for LZW/2) */ + if (code == kNuLZW2StopCode) { + /* output last code */ + Nu_LZWPutCode(&outBuf, prefixCode, codeBits, &atBit); + + if (inputBuf < inputEnd) { + /* still have data, keep going */ + Nu_LZWPutCode(&outBuf, kNuLZWClearCode, codeBits, &atBit); + Nu_ClearLZWTable(lzwState); + goto table_cleared; + } else { + /* no more input, hold table clear for next block */ + DBUG(("--- RARE: block-end clear\n")); + lzwState->initialClear = true; + goto table_clear_finish; + } + } + + Assert(nextFree <= kNuLZW2StopCode); + } + } + + /* + * Output the last code. Since there's no following character, we don't + * need to add an entry to the table... whatever we've found is already + * in there. + */ + Nu_LZWPutCode(&outBuf, prefixCode, codeBits, &atBit); + + /* + * Update the counters so LZW/2 has continuity. + */ + Assert(nextFree <= kNuLZW2StopCode); + if (nextFree >= highCode) { + highCode += nextFree +1; + codeBits++; + } + nextFree++; /* make room for the code we just wrote */ + + if (nextFree > kNuLZW2StopCode) { + /* + * The code we just wrote, which was part of a longer string already + * in the tree, took the last entry in the table. We need to clear + * the table, but we can't do it in this block. We will have to + * emit a table clear as the very first thing in the next block. + */ + DBUG(("--- RARE: block-end inter clear\n")); + lzwState->initialClear = true; + } + table_clear_finish: + + /* save state for next pass through */ + lzwState->nextFree = nextFree; + lzwState->codeBits = codeBits; + lzwState->highCode = highCode; + + Assert(inputBuf == inputEnd); + + *pOutputCount = outBuf - lzwState->lzwBuf; + + /* + if (*pOutputCount < inputCount) { + DBUG_LZW(("### compressed from %d to %d\n", inputCount, *pOutputCount)); + } else { + DBUG_LZW(("### NO compression (%d to %d)\n", inputCount,*pOutputCount)); + } + */ + + return kNuErrNone; +} + +/* + * Compress ShrinkIt-style "LZW/1" and "LZW/2". + * + * "*pThreadCrc" should already be set to its initial value. On exit it + * will contain the CRC of the uncompressed data. + * + * On exit, the output file will be positioned past the last byte written. + */ +static NuError +Nu_CompressLZW(NuArchive* pArchive, NuStraw* pStraw, FILE* fp, + ulong srcLen, ulong* pDstLen, ushort* pThreadCrc, Boolean isType2) +{ + NuError err = kNuErrNone; + LZWCompressState* lzwState; + long initialOffset; + const uchar* lzwInputBuf; + uint blockSize, rleSize, lzwSize; + long compressedLen; + Boolean keepLzw; + + Assert(pArchive != nil); + Assert(pStraw != nil); + Assert(fp != nil); + Assert(srcLen > 0); + Assert(pDstLen != nil); + Assert(pThreadCrc != nil); + Assert(isType2 == true || isType2 == false); + + /* + * Do some initialization and set-up. + */ + if (pArchive->lzwCompressState == nil) { + err = Nu_AllocLZWCompressState(pArchive); + BailError(err); + } + Assert(pArchive->lzwCompressState != nil); + Assert(pArchive->compBuf != nil); + + lzwState = pArchive->lzwCompressState; + lzwState->pArchive = pArchive; + compressedLen = 0; + + /* + * And now for something ugly: for LZW/1 we have to compute the CRC + * twice. Old versions of ShrinkIt used LZW/1 and put the CRC in + * the compressed block while newer versions used LZW/2 and put the + * CRC in the thread header. We're using LZW/1 with the newer record + * format, so we need two CRCs. For some odd reason Andy N. decided + * to use 0xffff as the initial value for the thread one, so we can't + * just store the same thing in two places. + * + * Of course, this also means that an LZW/2 chunk stored in an old + * pre-v3 record wouldn't have a CRC at all... + * + * LZW/1 is included here for completeness. I can't think of a reason + * why you'd want to use it, really. + */ + lzwState->chunkCrc = kNuInitialChunkCRC; /* 0x0000 */ + + /* + * An LZW/1 file starts off with a CRC of the data, which means we + * have to compress the whole thing, then seek back afterward and + * write the value. This annoyance went away in LZW/2. + */ + err = Nu_FTell(fp, &initialOffset); + BailError(err); + + if (!isType2) { + putc(0, fp); /* leave space for CRC */ + putc(0, fp); + compressedLen += 2; + } + putc(kNuLZWDefaultVol, fp); + putc(kNuRLEDefaultEscape, fp); + compressedLen += 2; + + if (isType2) + Nu_ClearLZWTable(lzwState); + + while (srcLen) { + /* + * Fill up the input buffer. + */ + blockSize = (srcLen > kNuLZWBlockSize) ? kNuLZWBlockSize : srcLen; + + err = Nu_StrawRead(pArchive, pStraw, lzwState->inputBuf, blockSize); + if (err != kNuErrNone) { + Nu_ReportError(NU_BLOB, err, "compression read failed"); + goto bail; + } + + /* + * ShrinkIt was originally just going to be a 5.25" disk compressor, + * so the compression functions were organized around 4K blocks (the + * size of one track on a 5.25" disk). The block passed into the + * RLE function is always 4K, so we zero out any extra space. + */ + if (blockSize < kNuLZWBlockSize) { + memset(lzwState->inputBuf + blockSize, 0, + kNuLZWBlockSize - blockSize); + } + + /* + * Compute the CRC. For LZW/1 this is on the entire 4K block, for + * LZW/2 this is on just the "real" data. + */ + if (isType2) { + *pThreadCrc = Nu_CalcCRC16(*pThreadCrc, + lzwState->inputBuf, blockSize); + } else { + *pThreadCrc = Nu_CalcCRC16(*pThreadCrc, + lzwState->inputBuf, kNuLZWBlockSize); + lzwState->chunkCrc = Nu_CalcCRC16(lzwState->chunkCrc, + lzwState->inputBuf, kNuLZWBlockSize); + } + + /* + * Try to compress with RLE, from inputBuf to rleBuf. + */ + err = Nu_CompressBlockRLE(lzwState, (int*) &rleSize); + BailError(err); + + if (rleSize < kNuLZWBlockSize) { + lzwInputBuf = lzwState->rleBuf; + } else { + lzwInputBuf = lzwState->inputBuf; + rleSize = kNuLZWBlockSize; + } + + /* + * Compress with LZW, into lzwBuf. + */ + if (!isType2) + Nu_ClearLZWTable(lzwState); + err = Nu_CompressLZWBlock(lzwState, lzwInputBuf, rleSize, + (int*) &lzwSize); + BailError(err); + + /* decide if we want to keep it, bearing in mind the LZW/2 header */ + if (pArchive->valMimicSHK) { + /* GSHK doesn't factor in header -- and *sometimes* uses "<=" !! */ + keepLzw = (lzwSize < rleSize); + } else { + if (isType2) + keepLzw = (lzwSize +2 < rleSize); + else + keepLzw = (lzwSize < rleSize); + } + + /* + * Write the compressed (or not) chunk. + */ + if (keepLzw) { + /* + * LZW succeeded. + */ + if (isType2) + rleSize |= 0x8000; /* for LZW/2, set "LZW used" flag */ + + putc(rleSize & 0xff, fp); /* size after RLE */ + putc(rleSize >> 8, fp); + compressedLen += 2; + + if (isType2) { + /* write compressed LZW len (+4 for header bytes) */ + putc((lzwSize+4) & 0xff, fp); + putc((lzwSize+4) >> 8, fp); + compressedLen += 2; + } else { + /* set LZW/1 "LZW used" flag */ + putc(1, fp); + compressedLen++; + } + + /* write data from LZW buffer */ + err = Nu_FWrite(fp, lzwState->lzwBuf, lzwSize); + BailError(err); + compressedLen += lzwSize; + } else { + /* + * LZW failed. + */ + putc(rleSize & 0xff, fp); /* size after RLE */ + putc(rleSize >> 8, fp); + compressedLen += 2; + + if (isType2) { + /* clear LZW/2 table; we can't use it next time */ + Nu_ClearLZWTable(lzwState); + } else { + /* set LZW/1 "LZW not used" flag */ + putc(0, fp); + compressedLen++; + } + + /* write data from RLE or plain-input buffer */ + err = Nu_FWrite(fp, lzwInputBuf, rleSize); + BailError(err); + compressedLen += rleSize; + } + + + /* + * Update the counter and continue. + */ + srcLen -= blockSize; + } + + /* + * For LZW/1, go back and write the CRC. + */ + if (!isType2) { + long curOffset; + + err = Nu_FTell(fp, &curOffset); + BailError(err); + err = Nu_FSeek(fp, initialOffset, SEEK_SET); + BailError(err); + putc(lzwState->chunkCrc & 0xff, fp); + putc(lzwState->chunkCrc >> 8, fp); + err = Nu_FSeek(fp, curOffset, SEEK_SET); + BailError(err); + } + + /* P8SHK and GSHK add an extra byte to LZW-compressed threads */ + if (pArchive->valMimicSHK) { + putc(0, fp); + compressedLen++; + } + + *pDstLen = compressedLen; + +bail: + return err; +} + +/* + * Compress ShrinkIt-style "LZW/1". + */ +NuError +Nu_CompressLZW1(NuArchive* pArchive, NuStraw* pStraw, FILE* fp, + ulong srcLen, ulong* pDstLen, ushort* pCrc) +{ + return Nu_CompressLZW(pArchive, pStraw, fp, srcLen, pDstLen, pCrc, false); +} + +/* + * Compress ShrinkIt-style "LZW/2". + */ +NuError +Nu_CompressLZW2(NuArchive* pArchive, NuStraw* pStraw, FILE* fp, + ulong srcLen, ulong* pDstLen, ushort* pCrc) +{ + return Nu_CompressLZW(pArchive, pStraw, fp, srcLen, pDstLen, pCrc, true); +} + + +/* + * =========================================================================== + * Expansion + * =========================================================================== + */ + +/* if we don't have at least this much data, we try to read more */ +/* (the "+3" is for the chunk header bytes) */ +#define kNuLZWDesiredChunk (kNuLZWBlockSize + 3) + +/* + * Static tables useful for bit manipulation. + */ +static const uint gMaskTable[17] = { + 0x0000, 0x01ff, 0x03ff, 0x03ff, 0x07ff, 0x07ff, 0x07ff, 0x07ff, + 0x0fff, 0x0fff, 0x0fff, 0x0fff, 0x0fff, 0x0fff, 0x0fff, 0x0fff, + 0x0fff +}; +/* convert high byte of "entry" into a bit width */ +static const uint gBitWidth[17] = { + 8,9,10,10,11,11,11,11,12,12,12,12,12,12,12,12,12 +}; + + +/* entry in the trie */ +typedef struct TableEntry { + uchar ch; + uint prefix; +} TableEntry; + +/* + * This holds all of the "big" dynamic state, plus a few things that I + * don't want to pass around. It's allocated once for each instance of + * an open archive, and re-used. + */ +typedef struct LZWExpandState { + NuArchive* pArchive; + + TableEntry trie[4096-256]; /* holds from 9 bits to 12 bits */ + uchar stack[kNuLZWBlockSize]; + + uint entry; /* 16-bit index into table */ + uint oldcode; /* carryover state for LZW/2 */ + uint incode; /* carryover state for LZW/2 */ + uint finalc; /* carryover state for LZW/2 */ + Boolean resetFix; /* work around an LZW/2 bug */ + + ushort chunkCrc; /* CRC we calculate for LZW/1 */ + ushort fileCrc; /* CRC stored with file */ + + uchar diskVol; /* disk volume # */ + uchar rleEscape; /* RLE escape char, usually 0xdb */ + + ulong dataInBuffer; /* #of bytes in compBuf */ + uchar* dataPtr; /* current data offset */ + + uchar lzwOutBuf[kNuLZWBlockSize + kNuSafetyPadding]; + uchar rleOutBuf[kNuLZWBlockSize + kNuSafetyPadding]; +} LZWExpandState; + + +/* + * Allocate some "reusable" state for LZW expansion. + */ +static NuError +Nu_AllocLZWExpandState(NuArchive* pArchive) +{ + NuError err; + + Assert(pArchive != nil); + Assert(pArchive->lzwExpandState == nil); + + /* allocate the general-purpose compression buffer, if needed */ + err = Nu_AllocCompressionBufferIFN(pArchive); + if (err != kNuErrNone) + return err; + + pArchive->lzwExpandState = Nu_Malloc(pArchive, sizeof(LZWExpandState)); + if (pArchive->lzwExpandState == nil) + return kNuErrMalloc; + return kNuErrNone; +} + + +#ifdef NDEBUG +# define Nu_LZWPush(uch) ( *stackPtr++ = (uch) ) +# define Nu_LZWPop() ( *(--stackPtr) ) +# define Nu_LZWStackEmpty() ( stackPtr == lzwState->stack ) + +#else +# define Nu_LZWPush(uch) \ + ( Nu_LZWPushCheck(uch, lzwState, stackPtr), *stackPtr++ = (uch) ) +# define Nu_LZWPop() \ + ( Nu_LZWPopCheck(lzwState, stackPtr), *(--stackPtr) ) +# define Nu_LZWStackEmpty() ( stackPtr == lzwState->stack ) + +static inline void +Nu_LZWPushCheck(uchar uch, const LZWExpandState* lzwState,const uchar* stackPtr) +{ + if (stackPtr >= lzwState->stack + sizeof(lzwState->stack)) { + Nu_ReportError(lzwState->NU_BLOB, kNuErrBadData, "stack overflow"); + abort(); + } +} + +static inline void +Nu_LZWPopCheck(const LZWExpandState* lzwState, const uchar* stackPtr) +{ + if (stackPtr == lzwState->stack) { + Nu_ReportError(lzwState->NU_BLOB, kNuErrBadData, "stack underflow"); + abort(); + } +} + +#endif + +/* + * Get the next LZW code from the input, advancing pointers as needed. + * + * This would be faster as a macro and less ugly with pass-by-reference. + * Resorting to globals is unacceptable. Might be less ugly if we clumped + * some stuff into a struct. Should be good enough as-is. + * + * Returns an integer up to 12 bits long. + * + * (Turning this into a macro might speed things up.) + */ +static inline uint +Nu_LZWGetCode(const uchar** pInBuf, uint entry, int* pAtBit, uint* pLastByte) +{ + uint numBits, startBit, lastBit; + ulong value; + + Assert(sizeof(uint) >= 2); + + numBits = (entry +1) >> 8; /* bit-width of next code */ + startBit = *pAtBit; + lastBit = startBit + gBitWidth[numBits]; + + /* + * We need one or two bytes from the input. These have to be shifted + * around and merged with the bits we already have (if any). + */ + if (!startBit) + value = *(*pInBuf)++; + else + value = *pLastByte; + + if (lastBit > 16) { + /* need two more bytes */ + value |= *(*pInBuf)++ << 8; + *pLastByte = *(*pInBuf)++; + value |= (ulong) *pLastByte << 16; + } else { + /* only need one more byte */ + *pLastByte = *(*pInBuf)++; + value |= *pLastByte << 8; + } + + *pAtBit = lastBit & 0x07; + + /*printf("| EX: value=$%06lx mask=$%04x return=$%03lx\n", + value,gMaskTable[numBits], (value >> startBit) & gMaskTable[numBits]);*/ + + /*DBUG_LZW(("### getcode 0x%04lx\n", + (value >> startBit) & gMaskTable[numBits]));*/ + + /* I believe ANSI allows shifting by zero bits, so don't test "!startBit" */ + return (value >> startBit) & gMaskTable[numBits]; +} + + +/* + * Expand an LZW/1 chunk. + * + * Reads from lzwState->dataPtr, writes to lzwState->lzwOutBuf. + */ +static NuError +Nu_ExpandLZW1(LZWExpandState* lzwState, uint expectedLen) +{ + NuError err = kNuErrNone; + TableEntry* tablePtr; + int atBit; + uint entry, oldcode, incode, ptr; + uint lastByte, finalc; + const uchar* inbuf; + uchar* outbuf; + uchar* outbufend; + uchar* stackPtr; + + Assert(lzwState != nil); + Assert(expectedLen > 0 && expectedLen <= kNuLZWBlockSize); + + inbuf = lzwState->dataPtr; + outbuf = lzwState->lzwOutBuf; + outbufend = outbuf + expectedLen; + tablePtr = lzwState->trie - 256; /* don't store 256 empties */ + stackPtr = lzwState->stack; + + atBit = 0; + lastByte = 0; + + entry = kNuLZWFirstCode; /* 0x101 */ + finalc = oldcode = incode = Nu_LZWGetCode(&inbuf, entry, &atBit, &lastByte); + *outbuf++ = incode; + Assert(incode <= 0xff); + if (incode > 0xff) { + err = kNuErrBadData; + Nu_ReportError(lzwState->NU_BLOB, err, "invalid initial LZW symbol"); + goto bail; + } + + while (outbuf < outbufend) { + incode = ptr = Nu_LZWGetCode(&inbuf, entry, &atBit, &lastByte); + + /* handle KwKwK case */ + if (ptr >= entry) { + Nu_LZWPush((uchar)finalc); + ptr = oldcode; + } + + /* fill the stack by chasing up the trie */ + while (ptr > 0xff) { + Nu_LZWPush(tablePtr[ptr].ch); + ptr = tablePtr[ptr].prefix; + Assert(ptr < 4096); + } + + /* done chasing up, now dump the stack, starting with ptr */ + finalc = ptr; + *outbuf++ = ptr; + /*printf("PUT 0x%02x\n", *(outbuf-1));*/ + while (!Nu_LZWStackEmpty()) { + *outbuf++ = Nu_LZWPop(); + /*printf("POP/PUT 0x%02x\n", *(outbuf-1));*/ + } + + /* add the new prefix to the trie -- last string plus new char */ + Assert(finalc <= 0xff); + tablePtr[entry].ch = finalc; + tablePtr[entry].prefix = oldcode; + entry++; + oldcode = incode; + } + +bail: + if (outbuf != outbufend) { + err = kNuErrBadData; + Nu_ReportError(lzwState->NU_BLOB, err, "LZW expansion failed"); + return err; + } + + /* adjust input buffer */ + lzwState->dataInBuffer -= (inbuf - lzwState->dataPtr); + Assert(lzwState->dataInBuffer < 32767*65536); + lzwState->dataPtr = (uchar*)inbuf; + + return err; +} + +/* + * Expand an LZW/2 chunk. Main difference from LZW/1 is that the state + * is carried over from the previous block in most cases, and the table + * is cleared explicitly. + * + * Reads from lzwState->dataPtr, writes to lzwState->lzwOutBuf. + */ +static NuError +Nu_ExpandLZW2(LZWExpandState* lzwState, uint expectedLen, + uint expectedInputUsed) +{ + NuError err = kNuErrNone; + TableEntry* tablePtr; + int atBit; + uint entry, oldcode, incode, ptr; + uint lastByte, finalc; + const uchar* inbuf; + const uchar* inbufend; + uchar* outbuf; + uchar* outbufend; + uchar* stackPtr; + + /*DBUG_LZW(("### LZW/2 block start (compIn=%d, rleOut=%d, entry=0x%04x)\n", + expectedInputUsed, expectedLen, lzwState->entry));*/ + Assert(lzwState != nil); + Assert(expectedLen > 0 && expectedLen <= kNuLZWBlockSize); + + inbuf = lzwState->dataPtr; + inbufend = lzwState->dataPtr + expectedInputUsed; + outbuf = lzwState->lzwOutBuf; + outbufend = outbuf + expectedLen; + entry = lzwState->entry; + tablePtr = lzwState->trie - 256; /* don't store 256 empties */ + stackPtr = lzwState->stack; + + atBit = 0; + lastByte = 0; + + /* + * If the table isn't empty, initialize from the saved state and + * jump straight into the main loop. + * + * There's a funny situation that arises when a table clear is the + * second-to-last code in the previous chunk. After we see the + * table clear, we get the next code and use it to initialize "oldcode" + * and "incode" -- but we don't advance "entry" yet. The way that + * ShrinkIt originally worked, the next time we came through we'd + * see what we thought was an empty table and we'd reinitialize. So + * we use "resetFix" to keep track of this situation. + */ + if (entry != kNuLZWFirstCode || lzwState->resetFix) { + /* table not empty */ + oldcode = lzwState->oldcode; + incode = lzwState->incode; + finalc = lzwState->finalc; + lzwState->resetFix = false; + goto main_loop; + } + +clear_table: + /* table is either empty or was just explicitly cleared; reset */ + entry = kNuLZWFirstCode; /* 0x0101 */ + if (outbuf == outbufend) { + /* block must've ended on a table clear */ + DBUG(("--- RARE: ending clear\n")); + /* reset values, mostly to quiet gcc's "used before init" warnings */ + oldcode = incode = finalc = 0; + goto main_loop; /* the while condition will fall through */ + } + finalc = oldcode = incode = Nu_LZWGetCode(&inbuf, entry, &atBit, &lastByte); + *outbuf++ = incode; + /*printf("PUT 0x%02x\n", *(outbuf-1));*/ + if (incode > 0xff) { + err = kNuErrBadData; + Nu_ReportError(lzwState->NU_BLOB, err, "invalid initial LZW symbol"); + goto bail; + } + + if (outbuf == outbufend) { + /* if we're out of data, raise the "reset fix" flag */ + DBUG(("--- RARE: resetFix!\n")); + lzwState->resetFix = true; + /* fall through; the while condition will let us slip past */ + } + +main_loop: + while (outbuf < outbufend) { + incode = ptr = Nu_LZWGetCode(&inbuf, entry, &atBit, &lastByte); + /*DBUG_LZW(("### read incode=0x%04x\n", incode));*/ + if (incode == kNuLZWClearCode) /* table clear - 0x0100 */ + goto clear_table; + + /* handle KwKwK case */ + if (ptr >= entry) { + Nu_LZWPush((uchar)finalc); + ptr = oldcode; + } + + /* fill the stack by chasing up the trie */ + while (ptr > 0xff) { + Nu_LZWPush(tablePtr[ptr].ch); + ptr = tablePtr[ptr].prefix; + Assert(ptr < 4096); + } + + /* done chasing up, now dump the stack, starting with ptr */ + finalc = ptr; + *outbuf++ = ptr; + /*printf("PUT 0x%02x\n", *(outbuf-1));*/ + while (!Nu_LZWStackEmpty()) { + *outbuf++ = Nu_LZWPop(); + /*printf("POP/PUT 0x%02x\n", *(outbuf-1));*/ + } + + /* add the new prefix to the trie -- last string plus new char */ + /*DBUG_LZW(("### entry 0x%04x gets prefix=0x%04x and ch=0x%02x\n", + entry, oldcode, finalc));*/ + Assert(finalc <= 0xff); + tablePtr[entry].ch = finalc; + tablePtr[entry].prefix = oldcode; + entry++; + oldcode = incode; + } + +bail: + /*DBUG_LZW(("### end of block\n"));*/ + Assert(inbuf == inbufend); + Assert(outbuf == outbufend); + + /* adjust input buffer */ + lzwState->dataInBuffer -= (inbuf - lzwState->dataPtr); + Assert(lzwState->dataInBuffer < 32767*65536); + lzwState->dataPtr = (uchar*)inbuf; + + /* save off local copies of stuff */ + lzwState->entry = entry; + lzwState->oldcode = oldcode; + lzwState->incode = incode; + lzwState->finalc = finalc; + + return err; +} + + +/* + * Expands a chunk of RLEd data into 4K of output. + */ +static NuError +Nu_ExpandRLE(LZWExpandState* lzwState, const uchar* inbuf, + uint expectedInputUsed) +{ + NuError err = kNuErrNone; + uchar *outbuf; + uchar *outbufend; + const uchar *inbufend; + uchar uch, rleEscape; + int count; + + outbuf = lzwState->rleOutBuf; + outbufend = outbuf + kNuLZWBlockSize; + inbufend = inbuf + expectedInputUsed; + rleEscape = lzwState->rleEscape; + + while (outbuf < outbufend) { + uch = *inbuf++; + if (uch == rleEscape) { + uch = *inbuf++; + count = *inbuf++; + while (count-- >= 0) + *outbuf++ = uch; + } else { + *outbuf++ = uch; + } + } + + if (outbuf != outbufend) { + err = kNuErrBadData; + Nu_ReportError(lzwState->NU_BLOB, err, + "RLE output glitch (off by %d)", (int)(outbufend-outbuf)); + goto bail; + } + if (inbuf != inbufend) { + err = kNuErrBadData; + Nu_ReportError(lzwState->NU_BLOB, err, + "RLE input glitch (off by %d)", (int)(inbufend-inbuf)); + goto bail; + } + +bail: + return err; +} + + +/* + * Utility function to get a byte from the input buffer. + */ +static inline uchar +Nu_GetHeaderByte(LZWExpandState* lzwState) +{ + lzwState->dataInBuffer--; + Assert(lzwState->dataInBuffer > 0); + return *lzwState->dataPtr++; +} + +/* + * Expand ShrinkIt-style "LZW/1" and "LZW/2". + * + * This manages the input data buffer, passing chunks of compressed data + * into the appropriate expansion function. + * + * Pass in nil for "pThreadCrc" if no thread CRC is desired. Otherwise, + * "*pThreadCrc" should already be set to its initial value. On exit it + * will contain the CRC of the uncompressed data. + */ +NuError +Nu_ExpandLZW(NuArchive* pArchive, const NuRecord* pRecord, + const NuThread* pThread, FILE* infp, NuFunnel* pFunnel, ushort* pThreadCrc) +{ + NuError err = kNuErrNone; + Boolean isType2; + LZWExpandState* lzwState; + ulong compRemaining, uncompRemaining, minSize; + + Assert(pArchive != nil); + Assert(pThread != nil); + Assert(infp != nil); + Assert(pFunnel != nil); + + /* + * Do some initialization and set-up. + */ + if (pArchive->lzwExpandState == nil) { + err = Nu_AllocLZWExpandState(pArchive); + BailError(err); + } + Assert(pArchive->lzwExpandState != nil); + Assert(pArchive->compBuf != nil); + + lzwState = pArchive->lzwExpandState; + lzwState->pArchive = pArchive; + + if (pThread->thThreadFormat == kNuThreadFormatLZW1) { + isType2 = false; + minSize = 7; /* crc-lo,crc-hi,vol,rle-delim,len-lo,len-hi,lzw-used */ + lzwState->chunkCrc = kNuInitialChunkCRC; /* 0x0000 */ + } else if (pThread->thThreadFormat == kNuThreadFormatLZW2) { + isType2 = true; + minSize = 4; /* vol,rle-delim,len-lo,len-hi */ + } else { + err = kNuErrBadFormat; + goto bail; + } + + uncompRemaining = pThread->actualThreadEOF; + compRemaining = pThread->thCompThreadEOF; + if (compRemaining < minSize) { + err = kNuErrBadData; + Nu_ReportError(NU_BLOB, err, "thread too short to be valid LZW"); + goto bail; + } + if (compRemaining && !uncompRemaining) { + err = kNuErrBadData; + Nu_ReportError(NU_BLOB, err, + "compressed data but no uncompressed data??"); + goto bail; + } + + /* + * Read the LZW header out of the data stream. + */ + if (!isType2) { + lzwState->fileCrc = getc(infp); + lzwState->fileCrc |= getc(infp) << 8; + compRemaining -= 2; + } + lzwState->diskVol = getc(infp); /* disk volume #; not really used */ + lzwState->rleEscape = getc(infp); /* RLE escape char for this thread */ + compRemaining -= 2; + + lzwState->dataInBuffer = 0; + lzwState->dataPtr = nil; + + /* reset pointers */ + lzwState->entry = kNuLZWFirstCode; /* 0x0101 */ + lzwState->resetFix = false; + + /*DBUG_LZW(("### LZW%d block, vol=0x%02x, rleEsc=0x%02x\n", + isType2 +1, lzwState->diskVol, lzwState->rleEscape));*/ + + /* + * Read large blocks of the source file into compBuf, taking care not + * to read past the end of the thread data. + * + * The motivation for doing it this way rather than just reading the + * next compressed chunk are (1) compBuf is considerably larger than + * stdio BUFSIZ on most systems, and (2) for LZW/1 we don't know the + * size of the compressed data anyway. + * + * We need to ensure that we have at least one full compressed chunk + * in the buffer. Since the compressor will refuse to store the + * compressed data if it grows, we know that we need 4K plus the + * chunk header. + * + * Once we have what looks like a full chunk, invoke the LZW decoder. + */ + while (uncompRemaining) { + Boolean rleUsed; + Boolean lzwUsed; + ulong getSize; + uint rleLen; /* length after RLE; 4096 if no RLE */ + uint lzwLen = 0; /* type 2 only */ + uint writeLen, inCount; + const uchar* writeBuf; + + /* if we're low, and there's more data available, read more */ + if (lzwState->dataInBuffer < kNuLZWDesiredChunk && compRemaining) { + /* + * First thing we do is slide the old data to the start of + * the buffer. + */ + if (lzwState->dataInBuffer) { + Assert(lzwState->dataPtr != nil); + Assert(pArchive->compBuf != lzwState->dataPtr); + memmove(pArchive->compBuf, lzwState->dataPtr, + lzwState->dataInBuffer); + } + lzwState->dataPtr = pArchive->compBuf; + + /* + * Next we read as much as we can. + */ + if (kNuGenCompBufSize - lzwState->dataInBuffer < compRemaining) + getSize = kNuGenCompBufSize - lzwState->dataInBuffer; + else + getSize = compRemaining; + + /*printf("+++ READING %ld\n", getSize);*/ + err = Nu_FRead(infp, lzwState->dataPtr + lzwState->dataInBuffer, + getSize); + if (err != kNuErrNone) + { + Nu_ReportError(NU_BLOB, err, + "failed reading compressed data (%ld bytes)", getSize); + goto bail; + } + lzwState->dataInBuffer += getSize; + compRemaining -= getSize; + + Assert(compRemaining < 32767*65536); + Assert(lzwState->dataInBuffer <= kNuGenCompBufSize); + } + Assert(lzwState->dataInBuffer); + + /* + * Read the LZW block header. + */ + if (isType2) { + rleLen = Nu_GetHeaderByte(lzwState); + rleLen |= Nu_GetHeaderByte(lzwState) << 8; + lzwUsed = rleLen & 0x8000 ? true : false; + rleLen &= 0x1fff; + rleUsed = (rleLen != kNuLZWBlockSize); + + if (lzwUsed) { + lzwLen = Nu_GetHeaderByte(lzwState); + lzwLen |= Nu_GetHeaderByte(lzwState) << 8; + lzwLen -= 4; /* don't include header bytes */ + } + } else { + rleLen = Nu_GetHeaderByte(lzwState); + rleLen |= Nu_GetHeaderByte(lzwState) << 8; + lzwUsed = Nu_GetHeaderByte(lzwState); + if (lzwUsed != 0 && lzwUsed != 1) { + err = kNuErrBadData; + Nu_ReportError(NU_BLOB, err, "garbled LZW header"); + goto bail; + } + rleUsed = (rleLen != kNuLZWBlockSize); + } + + /*DBUG_LZW(("### CHUNK rleLen=%d(%d) lzwLen=%d(%d) uncompRem=%ld\n", + rleLen, rleUsed, lzwLen, lzwUsed, uncompRemaining));*/ + + if (uncompRemaining <= kNuLZWBlockSize) + writeLen = uncompRemaining; /* last block */ + else + writeLen = kNuLZWBlockSize; + + #ifndef NDEBUG + writeBuf = nil; + #endif + + /* + * Decode the chunk, and point "writeBuf" at the uncompressed data. + * + * LZW always expands from the read buffer into lzwState->lzwOutBuf. + * RLE expands from a specific buffer to lzwState->rleOutBuf. + */ + if (lzwUsed) { + if (!isType2) { + err = Nu_ExpandLZW1(lzwState, rleLen); + } else { + Assert(lzwState->dataInBuffer >= lzwLen); + err = Nu_ExpandLZW2(lzwState, rleLen, lzwLen); + } + + BailError(err); + + if (rleUsed) { + err = Nu_ExpandRLE(lzwState, lzwState->lzwOutBuf, rleLen); + BailError(err); + writeBuf = lzwState->rleOutBuf; + } else { + writeBuf = lzwState->lzwOutBuf; + } + + } else { + if (rleUsed) { + err = Nu_ExpandRLE(lzwState, lzwState->dataPtr, rleLen); + BailError(err); + writeBuf = lzwState->rleOutBuf; + inCount = rleLen; + } else { + writeBuf = lzwState->dataPtr; + inCount = writeLen; + } + + /* + * Advance the input buffer data pointers to consume the input. + * The LZW expansion functions do this for us, but we're not + * using LZW. + */ + lzwState->dataPtr += inCount; + lzwState->dataInBuffer -= inCount; + Assert(lzwState->dataInBuffer < 32767*65536); + + /* no LZW used, reset pointers */ + lzwState->entry = kNuLZWFirstCode; /* 0x0101 */ + lzwState->resetFix = false; + } + + Assert(writeBuf != nil); + + /* + * Compute the CRC of the uncompressed data, and write it. For + * LZW/1, the CRC of the last block includes the zeros that pad + * it out to 4096 bytes. + * + * See commentary in the compression code for why we have to + * compute two CRCs for LZW/1. + */ + if (isType2) { + if (pThreadCrc != nil) { + *pThreadCrc = Nu_CalcCRC16(*pThreadCrc, writeBuf, writeLen); + } + } else { + if (pThreadCrc != nil) { + *pThreadCrc = Nu_CalcCRC16(*pThreadCrc, writeBuf, + kNuLZWBlockSize); + } + lzwState->chunkCrc = Nu_CalcCRC16(lzwState->chunkCrc, + writeBuf, kNuLZWBlockSize); + } + + /* write the data, possibly doing an EOL conversion */ + err = Nu_FunnelWrite(pArchive, pFunnel, writeBuf, writeLen); + if (err != kNuErrNone) { + Nu_ReportError(NU_BLOB, err, "unable to write output"); + goto bail; + } + + uncompRemaining -= writeLen; + Assert(uncompRemaining < 32767*65536); + } + + /* + * It appears that ShrinkIt appends an extra byte after the last + * LZW block. The byte is included in the compThreadEOF, but isn't + * consumed by the LZW expansion routine, so it's usually harmless. + * + * It is *possible* for extra bytes to be here legitimately, but very + * unlikely. The very last block is always padded out to 4K with + * zeros. If you found a situation where that last block failed + * to compress with RLE and LZW (perhaps the last block filled up + * all but the last 2 or 3 bytes with uncompressible data), but + * earlier data made the overall file compressible, you would have + * a few stray bytes in the archive. + * + * This is a little easier to do if the last block has lots of single + * 0xdb characters in it, since that requires RLE to escape them. + * + * Whatever the case, issue a warning if it looks like there's too + * many of them. + */ + if (lzwState->dataInBuffer > 1) { + DBUG(("--- Found %ld bytes following compressed data (compRem=%ld)\n", + lzwState->dataInBuffer, compRemaining)); + if (lzwState->dataInBuffer > 32) { + Nu_ReportError(NU_BLOB, kNuErrNone, "(Warning) lots of fluff (%ld)", + lzwState->dataInBuffer); + } + } + + /* + * We might be okay with stray bytes in the thread, but we're definitely + * not okay with anything identified as compressed data being unused. + */ + if (compRemaining) { + err = kNuErrBadData; + Nu_ReportError(NU_BLOB, err, + "not all compressed data was used (%ld/%ld)", + compRemaining, lzwState->dataInBuffer); + goto bail; + } + + /* + * ShrinkIt used to put the CRC in the stream and not in the thread + * header. For LZW/1, we check the CRC here; for LZW/2, we hope it's + * in the thread header. (As noted in the compression code, it's + * possible to end up with two CRCs or no CRCs.) + */ + if (!isType2 && !pArchive->valIgnoreCRC) { + if (lzwState->chunkCrc != lzwState->fileCrc) { + if (!Nu_ShouldIgnoreBadCRC(pArchive, pRecord, kNuErrBadDataCRC)) { + err = kNuErrBadDataCRC; + Nu_ReportError(NU_BLOB, err, + "expected 0x%04x, got 0x%04x (LZW/1)", + lzwState->fileCrc, lzwState->chunkCrc); + (void) Nu_FunnelFlush(pArchive, pFunnel); + goto bail; + } + } else { + DBUG(("--- LZW/1 CRCs match (0x%04x)\n", lzwState->chunkCrc)); + } + } + +bail: + if (err == kNuErrNone) + err = Nu_FunnelFlush(pArchive, pFunnel); + + return err; +} + diff --git a/nufxlib-0/Makefile.in b/nufxlib-0/Makefile.in new file mode 100644 index 0000000..a6f8f29 --- /dev/null +++ b/nufxlib-0/Makefile.in @@ -0,0 +1,110 @@ +# +# Makefile for nufxlib stuff (should work with non-GNU "make"). +# +# You can use: +# make (builds library and sample applications) +# make shared (builds shared library if you're using GNU ld or similar) +# + +# NufxLib install location. +prefix = @prefix@ +exec_prefix = @exec_prefix@ +includedir = @includedir@ +libdir = @libdir@ +srcdir = @srcdir@ + +SHELL = @SHELL@ +INSTALL = @INSTALL@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_DATA = @INSTALL_DATA@ +CC = @CC@ +AR = ar rcv +#OPT = @CFLAGS@ -DNDEBUG +OPT = @CFLAGS@ +#OPT = @CFLAGS@ -DDEBUG_MSGS +#OPT = @CFLAGS@ -DDEBUG_VERBOSE +GCC_FLAGS = -Wall -Wwrite-strings -Wstrict-prototypes -Wpointer-arith -Wshadow +CFLAGS = @BUILD_FLAGS@ -I. @DEFS@ + +SRCS = Archive.c ArchiveIO.c Compress.c Crc16.c Debug.c Deferred.c \ + Entry.c Expand.c FileIO.c Funnel.c Lzw.c MiscStuff.c MiscUtils.c \ + Record.c SourceSink.c Thread.c Value.c Version.c +OBJS = Archive.o ArchiveIO.o Compress.o Crc16.o Debug.o Deferred.o \ + Entry.o Expand.o FileIO.o Funnel.o Lzw.o MiscStuff.o MiscUtils.o \ + Record.o SourceSink.o Thread.o Value.o Version.o + +STATIC_PRODUCT = libnufx.a +SHARED_PRODUCT = libnufx.so +PRODUCT = $(STATIC_PRODUCT) + + +# +# Build stuff +# + +all: $(PRODUCT) samples + @true + +install: $(STATIC_PRODUCT) + $(srcdir)/mkinstalldirs $(libdir) + $(INSTALL_DATA) $(STATIC_PRODUCT) $(libdir) + $(srcdir)/mkinstalldirs $(includedir) + $(INSTALL_DATA) NufxLib.h $(includedir) + +install-shared: $(SHARED_PRODUCT) + $(srcdir)/mkinstalldirs $(libdir) + $(INSTALL_DATA) $(SHARED_PRODUCT) $(libdir) + $(srcdir)/mkinstalldirs $(includedir) + $(INSTALL_DATA) NufxLib.h $(includedir) + +samples:: + @echo "Building samples..." + @(cd samples; set +e; unset CFLAGS OBJS; set -e; \ + @SET_MAKE@ LIB_PRODUCT="../$(PRODUCT)" $(MAKE)) + +shared:: + PRODUCT="$(SHARED_PRODUCT)" $(MAKE) -e + +$(STATIC_PRODUCT): $(OBJS) + -rm -f $(SHARED_PRODUCT) + $(AR) $@ $(OBJS) + @RANLIB@ $@ + +$(SHARED_PRODUCT): $(OBJS) + -rm -f $(STATIC_PRODUCT) + $(CC) @SHARE_FLAGS@ -o $@ $(OBJS) + +# the build date is approximate, the build flags are accurate +Version.c: Version.c.in Makefile + (sed -e "s/BUILT/`date`/" -e "s/OPTFLAGS/$(OPT)/" < Version.c.in > Version.c) + +clean: + (cd samples; make clean) + -rm -f *.o core + -rm -f $(SHARED_PRODUCT) $(STATIC_PRODUCT) + +# build tags; assumes fancy GNU tag generation +tags:: + @ctags -R --totals * + @#ctags *.[ch] + +distclean: clean + (cd samples; make distclean) + -rm -f Version.c + -rm -f Makefile Makefile.bak + -rm -f config.log config.cache config.status config.h + -rm -f tags + +baktar: + @tar cvf nufxlib.tar *.txt COPYING-LIB INSTALL configure *.in Makefile \ + Makefile.msc install-sh config.guess config.sub mkinstalldirs *.[ch] \ + samples/*.txt samples/Makefile* samples/*.[ch] + @gzip -9 nufxlib.tar + @mv -i nufxlib.tar.gz /home/fadden/BAK/ + +depend: + makedepend -- $(CFLAGS) -I/usr/local/include -- $(SRCS) + @(cd samples; unset CFLAGS OBJS; @SET_MAKE@ $(MAKE) depend) + +# DO NOT DELETE THIS LINE -- make depend depends on it. + diff --git a/nufxlib-0/Makefile.msc b/nufxlib-0/Makefile.msc new file mode 100644 index 0000000..4333f91 --- /dev/null +++ b/nufxlib-0/Makefile.msc @@ -0,0 +1,75 @@ +# +# Makefile for Microsoft C compilers. Tested against Visual C++ 6.0. +# Not pretty but it seems to work. +# +# Run with "nmake /f Makefile.msc". +# +# To build without debugging info, use "nmake nodebug=1". +# + +# Windows magic +TARGETOS = BOTH +!include + +# object files +OBJS1 = Archive.obj ArchiveIO.obj Compress.obj Crc16.obj Debug.obj Deferred.obj +OBJS2 = Entry.obj Expand.obj FileIO.obj Funnel.obj Lzw.obj MiscStuff.obj +OBJS3 = MiscUtils.obj Record.obj SourceSink.obj Thread.obj Value.obj Version.obj +OBJS = $(OBJS1) $(OBJS2) $(OBJS3) + +!ifdef NODEBUG +#OPT = $(cdebug) /D NDEBUG /ML +OPT = $(cdebug) /ML +!else +OPT = $(cdebug) /MLd +!endif + +BUILD_FLAGS = /W3 /GX /D "WIN32" + +# how to compile sources +.c.obj: + @$(cc) $(OPT) $(BUILD_FLAGS) $(cflags) $(cvars) -o $@ $< + + +PRODUCT = nufxlib.lib + +all: $(PRODUCT) samples + +samples:: + @echo - + @echo - Change to the samples directory and run $(MAKE) there. + @echo - + +# this isn't great, but it'll have to do +Version.c: Version.c.in + copy Version.c.in Version.c + @echo - + @echo - NOTE: you must update Version.c by hand (use flags=$(OPT)) + @echo - + +nufxlib.lib: $(OBJS) + $(link) -lib /out:$(PRODUCT) $(OBJS) /nologo + +clean:: + del *.obj + del $(PRODUCT) + +Archive.obj: Archive.c NufxLibPriv.h NufxLib.h MiscStuff.h SysDefs.h +ArchiveIO.obj: ArchiveIO.c NufxLibPriv.h NufxLib.h MiscStuff.h SysDefs.h +Compress.obj: Compress.c NufxLibPriv.h NufxLib.h MiscStuff.h SysDefs.h +Crc16.obj: Crc16.c NufxLibPriv.h NufxLib.h MiscStuff.h SysDefs.h +Debug.obj: Debug.c NufxLibPriv.h NufxLib.h MiscStuff.h SysDefs.h +Deferred.obj: Deferred.c NufxLibPriv.h NufxLib.h MiscStuff.h SysDefs.h +Entry.obj: Entry.c NufxLibPriv.h NufxLib.h MiscStuff.h SysDefs.h +Expand.obj: Expand.c NufxLibPriv.h NufxLib.h MiscStuff.h SysDefs.h +FileIO.obj: FileIO.c NufxLibPriv.h NufxLib.h MiscStuff.h SysDefs.h +Funnel.obj: Funnel.c NufxLibPriv.h NufxLib.h MiscStuff.h SysDefs.h +Lzw.obj: Lzw.c NufxLibPriv.h NufxLib.h MiscStuff.h SysDefs.h +MiscStuff.obj: MiscStuff.c NufxLibPriv.h NufxLib.h MiscStuff.h SysDefs.h +MiscUtils.obj: MiscUtils.c NufxLibPriv.h NufxLib.h MiscStuff.h SysDefs.h +Record.obj: Record.c NufxLibPriv.h NufxLib.h MiscStuff.h SysDefs.h +SourceSink.obj: SourceSink.c NufxLibPriv.h NufxLib.h MiscStuff.h SysDefs.h +Thread.obj: Thread.c NufxLibPriv.h NufxLib.h MiscStuff.h SysDefs.h +Value.obj: Value.c NufxLibPriv.h NufxLib.h MiscStuff.h SysDefs.h +Version.obj: Version.c NufxLibPriv.h NufxLib.h MiscStuff.h SysDefs.h + diff --git a/nufxlib-0/MiscStuff.c b/nufxlib-0/MiscStuff.c new file mode 100644 index 0000000..a14af3f --- /dev/null +++ b/nufxlib-0/MiscStuff.c @@ -0,0 +1,120 @@ +/* + * 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 Library General Public License, see the file COPYING.LIB. + * + * Misc stuff (shared between nufxlib and nulib2). This is a collection + * of standard functions that aren't available in libc on this system. + */ +#include "SysDefs.h" +#include "MiscStuff.h" +#include + + +#ifndef HAVE_STRERROR +/* + * Return a pointer to the appropriate string in the system table, or NULL + * if the value is out of bounds. + */ +const char* +Nu_strerror(int errnum) +{ + extern int sys_nerr; + extern char *sys_errlist[]; + + if (errnum < 0 || errnum > sys_nerr) + return NULL; + + return sys_errlist[errnum]; +} +#endif + +#ifndef HAVE_MEMMOVE +/* + * Move a block of memory. Unlike memcpy, this is expected to work + * correctly with overlapping blocks. + * + * This is a straightforward implementation. A much faster implementation, + * from BSD, is available in the PGP 2.6.2 distribution, but this should + * suffice for those few systems that don't have memmove. + */ +void* +Nu_memmove(void* dst, const void* src, size_t n) +{ + void* retval = dst; + char* srcp = (char*)src; + char* dstp = (char*)dst; + + /* you can normally get away with this if n==0 */ + assert(dst != NULL); + assert(src != NULL); + + if (dstp == srcp || !n) { + /* nothing to do */ + } else if (dstp > srcp) { + /* start from the end */ + (char*)dstp += n-1; + (char*)srcp += n-1; + while (n--) + *dstp-- = *srcp--; + } else { + /* start from the front */ + while (n--) + *dstp++ = *srcp++; + } + + return retval; +} +#endif + +#ifndef HAVE_STRTOUL +/* + * Perform strtol, but on an unsigned long. + * + * On systems that have strtol but don't have strtoul, the strtol + * function doesn't clamp the return value, making it similar in + * function to strtoul. The comparison is not exact, however, + * because strtoul is expected to lots of fancy things (like set + * errno to ERANGE). + * + * For our purposes here, strtol does all we need it to. Someday + * we should replace this with a "real" version. + */ +unsigned long +Nu_strtoul(const char *nptr, char **endptr, int base) +{ + return strtol(nptr, endptr, base); +} +#endif + +#ifndef HAVE_STRCASECMP +/* + * Compare two strings, case-insensitive. + */ +int +Nu_strcasecmp(const char *str1, const char *str2) +{ + while (*str1 && *str2 && toupper(*str1) == toupper(*str2)) + str1++, str2++; + return (toupper(*str1) - toupper(*str2)); +} + +#endif + +#ifndef HAVE_STRNCASECMP +/* + * Compare two strings, case-insensitive, stopping after "n" chars. + */ +int +Nu_strncasecmp(const char *str1, const char *str2, size_t n) +{ + while (n && *str1 && *str2 && toupper(*str1) == toupper(*str2)) + str1++, str2++, n--; + + if (n) + return (toupper(*str1) - toupper(*str2)); + else + return 0; /* no mismatch in first n chars */ +} +#endif + diff --git a/nufxlib-0/MiscStuff.h b/nufxlib-0/MiscStuff.h new file mode 100644 index 0000000..1f35ea0 --- /dev/null +++ b/nufxlib-0/MiscStuff.h @@ -0,0 +1,108 @@ +/* + * 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 Library General Public License, see the file COPYING.LIB. + * + * Misc stuff (shared between nufxlib and nulib2). This is a collection + * of miscellaneous types and macros that I find generally useful. + */ +#ifndef __MiscStuff__ +#define __MiscStuff__ + +#include "SysDefs.h" + +/* + * Use our versions of functions if they don't exist locally. + */ +#ifndef HAVE_STRERROR +#define strerror Nu_strerror +const char* Nu_strerror(int errnum); +#endif +#ifndef HAVE_MEMMOVE +#define memmove Nu_memmove +void* Nu_memmove(void *dest, const void *src, size_t n); +#endif +#ifndef HAVE_STRTOUL +#define strtoul Nu_strtoul +unsigned long Nu_strtoul(const char *nptr, char **endptr, int base); +#endif +#ifndef HAVE_STRCASECMP +#define strcasecmp Nu_strcasecmp +int Nu_strcasecmp(const char *s1, const char *s2); +#endif +#ifndef HAVE_STRNCASECMP +#define strncasecmp Nu_strncasecmp +int Nu_strncasecmp(const char *s1, const char *s2, size_t n); +#endif + + +/* + * Misc types. + */ + +#include + +#define nil NULL /* I can't seem to stop typing 'nil' now */ + +typedef uchar Boolean; +#define false (0) +#define true (!false) + + +/* + * Handy macros. + */ + +/* compute #of elements in a static array */ +#define NELEM(x) (sizeof(x) / sizeof((x)[0])) + +/* convert single hex digit char to number */ +#define HexDigit(x) ( !isxdigit((int)(x)) ? -1 : \ + (x) <= '9' ? (x) - '0' : toupper(x) +10 - 'A' ) + +/* convert number from 0-15 to hex digit */ +#define HexConv(x) ( ((uint)(x)) <= 15 ? \ + ( (x) <= 9 ? (x) + '0' : (x) -10 + 'A') : -1 ) + + +/* + * Debug stuff. + */ + +/* + * Redefine this if you want assertions to do something other than default. + * Changing the definition of assert is tough, because assert.h redefines + * it every time it's included. On a Solaris 2.7 system I was using, gcc + * pulled assert.h in with some of the system headers, and their definition + * resulted in corrupted core dumps. + */ +#define Assert assert + +#if defined(DEBUG_VERBOSE) + /* quick debug printf macro */ + #define DBUG(args) printf args +#else + #define DBUG(args) ((void)0) +#endif + + +#if defined(NDEBUG) + #define DebugFill(addr, len) ((void)0) + + #define DebugAbort() ((void)0) + +#else + /* when debugging, fill Malloc blocks with junk, unless we're using Purify */ + #if !defined(PURIFY) + #define DebugFill(addr, len) memset(addr, 0xa3, len) + #else + #define DebugFill(addr, len) ((void)0) + #endif + + #define DebugAbort() abort() +#endif + +#define kInvalidFill (0xa3) +#define kInvalidPtr ((void*)0xa3a3a3a3) + +#endif /*__MiscStuff__*/ diff --git a/nufxlib-0/MiscUtils.c b/nufxlib-0/MiscUtils.c new file mode 100644 index 0000000..00c8d3f --- /dev/null +++ b/nufxlib-0/MiscUtils.c @@ -0,0 +1,358 @@ +/* + * NuFX archive manipulation library + * 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 Library General Public License, see the file COPYING.LIB. + * + * Miscellaneous NufxLib utility functions. + */ +#define __MiscUtils_c__ +#include "NufxLibPriv.h" + +/* + * Big fat hairy global. Unfortunately this is unavoidable. + */ +NuCallback gNuGlobalErrorMessageHandler = nil; + + +const char* kNufxLibName = "nufxlib"; + + +/* + * strerror() equivalent for NufxLib errors. + */ +const char* +Nu_StrError(NuError err) +{ + /* + * BUG: this should be set up as per-thread storage in an MT environment. + * I would be more inclined to worry about this if I was expecting + * it to be used. So long as valid values are passed in, and the + * switch statement is kept up to date, we should never have cause + * to return this. + * + * An easier solution, should this present a problem for someone, would + * be to have the function return nil or "unknown error" when the + * error value isn't recognized. + */ + static char defaultMsg[32]; + + switch (err) { + case kNuErrNone: + return "(no error)"; + + case kNuErrGeneric: + return "NufxLib generic error"; + case kNuErrInternal: + return "NufxLib internal error"; + case kNuErrUsage: + return "NufxLib usage error"; + case kNuErrSyntax: + return "NufxLib syntax error"; + case kNuErrMalloc: + return "NufxLib malloc error"; + case kNuErrInvalidArg: + return "Invalid arguments to NufxLib"; + case kNuErrBadStruct: + return "Bad NuArchive structure passed to NufxLib"; + case kNuErrBusy: + return "Attempted invalid reentrant call"; + + case kNuErrSkipped: + return "Skipped by user"; + case kNuErrAborted: + return "Processing aborted"; + case kNuErrRename: + return "User wants to rename file"; + + case kNuErrFile: + return "NufxLib trouble with a file"; + case kNuErrFileOpen: + return "NufxLib unable to open file"; + case kNuErrFileClose: + return "NufxLib unable to close file"; + case kNuErrFileRead: + return "NufxLib unable to read file"; + case kNuErrFileWrite: + return "NufxLib unable to write file"; + case kNuErrFileSeek: + return "NufxLib unable to seek file"; + case kNuErrFileExists: + return "File already exists"; + case kNuErrFileNotFound: + return "No such file or directory"; + case kNuErrFileStat: + return "Couldn't get file info"; + case kNuErrFileNotReadable: + return "Read access denied"; + + case kNuErrDirExists: + return "Directory already exists"; + case kNuErrNotDir: + return "Not a directory"; + case kNuErrNotRegularFile: + return "Not a regular file"; + case kNuErrDirCreate: + return "Unable to create directory"; + case kNuErrOpenDir: + return "Unable to open directory"; + case kNuErrReadDir: + return "Unable to read directory"; + case kNuErrFileSetDate: + return "Unable to set file date"; + case kNuErrFileSetAccess: + return "Unable to set file access"; + + case kNuErrNotNuFX: + return "Input is not a NuFX archive"; + case kNuErrBadMHVersion: + return "Unrecognized Master Header version"; + case kNuErrRecHdrNotFound: + return "Next record not found"; + case kNuErrNoRecords: + return "No records in archive"; + case kNuErrBadRecord: + return "Bad data in record"; + case kNuErrBadMHCRC: + return "Bad Master Header CRC"; + case kNuErrBadRHCRC: + return "Bad Record header CRC"; + case kNuErrBadThreadCRC: + return "Bad Thread header CRC"; + case kNuErrBadDataCRC: + return "Data CRC mismatch"; + + case kNuErrBadFormat: + return "Thread compression format unsupported"; + case kNuErrBadData: + return "Bad data found"; + case kNuErrBufferOverrun: + return "Buffer overrun"; + case kNuErrBufferUnderrun: + return "Buffer underrun"; + case kNuErrOutMax: + return "Output limit exceeded"; + + case kNuErrNotFound: + return "Not found"; + case kNuErrRecordNotFound: + return "Record not found"; + case kNuErrRecIdxNotFound: + return "RecordIdx not found"; + case kNuErrThreadIdxNotFound: + return "ThreadIdx not found"; + case kNuErrThreadIDNotFound: + return "ThreadID not found"; + case kNuErrRecNameNotFound: + return "Record name not found"; + case kNuErrRecordExists: + return "Record already exists"; + + case kNuErrAllDeleted: + return "Tried to delete all files"; + case kNuErrArchiveRO: + return "Archive is in read-only mode"; + case kNuErrModRecChange: + return "Attempt to alter a modified record"; + case kNuErrModThreadChange: + return "Attempt to alter a modified thread"; + case kNuErrThreadAdd: + return "Can't add conflicting threadID"; + case kNuErrNotPreSized: + return "Operation only permitted on pre-sized threads"; + case kNuErrPreSizeOverflow: + return "Data exceeds pre-sized thread size"; + case kNuErrInvalidFilename: + return "Invalid filename"; + + case kNuErrLeadingFssep: + return "Storage name started with fssep char"; + case kNuErrNotNewer: + return "New item wasn't newer than existing"; + case kNuErrDuplicateNotFound: + return "Can only update an existing item"; + case kNuErrDamaged: + return "Original archive may have been damaged"; + + default: + sprintf(defaultMsg, "(error=%d)", err); + return defaultMsg; + } +} + + +#define kNuHeftyBufSize 256 /* all error messages should fit in this */ +#define kNuExtraGoodies 8 /* leave room for "\0" and other trivial chars*/ + +/* + * Similar to perror(), but takes the error as an argument, and knows + * about NufxLib errors as well as system errors. + * + * Depending on the compiler, "file", "line", and "function" may be nil/zero. + * + * Calling here with "pArchive"==nil is allowed, but should only be done + * if the archive is inaccessible (perhaps because it failed to open). We + * can't invoke the error message callback if the pointer is nil. + */ +void +Nu_ReportError(NuArchive* pArchive, const char* file, int line, + const char* function, Boolean isDebug, NuError err, const char* format, ...) +{ + NuErrorMessage errorMessage; + const char* msg; + va_list args; + char buf[kNuHeftyBufSize]; + int count; + #if defined(HAVE_SNPRINTF) || defined(SPRINTF_RETURNS_INT) + int cc; + #endif + + Assert(format != nil); + + + va_start(args, format); + + #if defined(HAVE_VSNPRINTF) && defined(VSNPRINTF_DECLARED) + count = vsnprintf(buf, sizeof(buf)-kNuExtraGoodies, format, args); + #else + #ifdef SPRINTF_RETURNS_INT + count = vsprintf(buf, format, args); + #else + vsprintf(buf, format, args); + count = strlen(buf); + #endif + #endif + + va_end(args); + + Assert(count > 0); + if (count < 0) + goto bail; + + /* print the error code data, if any */ + if (err != kNuErrNone) { + /* we know we have room for ": ", because of kNuExtraGoodies */ + strcpy(buf+count, ": "); + count += 2; + + msg = nil; + if (err >= 0) + msg = strerror(err); + if (msg == nil) + msg = Nu_StrError(err); + + #if defined(HAVE_SNPRINTF) && defined(SNPRINTF_DECLARED) + if (msg == nil) + cc = snprintf(buf+count, sizeof(buf) - count, + "(unknown err=%d)", err); + else + cc = snprintf(buf+count, sizeof(buf) - count, "%s", msg); + #else + #ifdef SPRINTF_RETURNS_INT + if (msg == nil) + cc = sprintf(buf+count, "(unknown err=%d)", err); + else + cc = sprintf(buf+count, "%s", msg); + Assert(cc > 0); + count += cc; + #else + if (msg == nil) + sprintf(buf+count, "(unknown err=%d)", err); + else + sprintf(buf+count, "%s", msg); + count += strlen(buf + count); + #endif + #endif + + } + + #if !defined(HAVE_SNPRINTF) || !defined(HAVE_VSNPRINTF) || \ + !defined(SNPRINTF_DELCARED) || !defined(VSNPRINTF_DECLARED) + /* couldn't do it right, so check for overflow */ + Assert(count <= kNuHeftyBufSize); + #endif + + if ((pArchive != nil && pArchive->messageHandlerFunc == nil) || + (pArchive == nil && gNuGlobalErrorMessageHandler == nil)) + { + if (isDebug) { + fprintf(stderr, "%s: [%s:%d %s] %s\n", kNufxLibName, + file, line, function, buf); + } else { + fprintf(stderr, "%s: ERROR: %s\n", kNufxLibName, buf); + } + } else { + errorMessage.message = buf; + errorMessage.err = err; + errorMessage.isDebug = isDebug; + errorMessage.file = file; + errorMessage.line = line; + errorMessage.function = function; + + if (pArchive == nil) + (void) (*gNuGlobalErrorMessageHandler)(pArchive, &errorMessage); + else + (void) (*pArchive->messageHandlerFunc)(pArchive, &errorMessage); + } + +bail: + return; +} + + +/* + * Memory allocation wrappers. + * + * Under gcc these would be macros, but not all compilers can handle that. + * + * [ It should be possible to use mmalloc instead of malloc. Just tuck the + * mmalloc descriptor into the NuArchive struct. ] + */ + +#ifndef USE_DMALLOC +void* +Nu_Malloc(NuArchive* pArchive, size_t size) +{ + void* _result; + + Assert(size > 0); + _result = malloc(size); + if (_result == nil) { + Nu_ReportError(NU_BLOB, kNuErrMalloc, "malloc(%u) failed", (uint) size); + DebugAbort(); /* leave a core dump if we're built for it */ + } + DebugFill(_result, size); + return _result; +} + +void* +Nu_Calloc(NuArchive* pArchive, size_t size) +{ + void* _cresult = Nu_Malloc(pArchive, size); + memset(_cresult, 0, size); + return _cresult; +} + +void* +Nu_Realloc(NuArchive* pArchive, void* ptr, size_t size) +{ + void* _result; + + Assert(ptr != nil); /* disallow this usage */ + Assert(size > 0); /* disallow this usage */ + _result = realloc(ptr, size); + if (_result == nil) { + Nu_ReportError(NU_BLOB, kNuErrMalloc, "realloc(%u) failed",(uint) size); + DebugAbort(); /* leave a core dump if we're built for it */ + } + return _result; +} + +void +Nu_Free(NuArchive* pArchive, void* ptr) +{ + if (ptr != nil) + free(ptr); +} +#endif + diff --git a/nufxlib-0/NOTES.txt b/nufxlib-0/NOTES.txt new file mode 100644 index 0000000..6fe25f8 --- /dev/null +++ b/nufxlib-0/NOTES.txt @@ -0,0 +1,206 @@ +NufxLib NOTES +Last revised: 2000/01/23 + + +The interface is documented in "nufxlibapi.html", available from the +www.nulib.com web site. This discusses some of the internal design that +may be of interest. + +Some familiarity with the NuFX file format is assumed. + + +Read-Write Data Structures +========================== + +For both read-only and read-write files (but not streaming read-only files), +the archive is represented internally as a linked list of Records, each +of which has an array of Threads attached. No attempt is made to +optimize searches by filename, so use of the "replace existing entry when +filenames match" option should be restricted to situations where it is +necessary. Otherwise, O(N^2) behavior can result. + +Modifications, such as deletions, changes to filename threads, and +additions of new records, are queued up in a separate list until a NuFlush +call is issued. The list works much the same way as the temporary file: +when the operation completes, the "new" list becomes the "original" list. +If the operation is aborted, the "new" list is scrubbed, and the "original" +list remains unmodified. + +Just as it is inefficient to write data to the temp file when it's not +necessary to do so, it is inefficient to allocate a complete copy of the +records from the original list if none are changed. As a result, there are +actually two "new" lists, one with a copy of the original record list, and +one with new additions. The "copy" list starts out uninitialized, and +remains that way until one of the entries from the original list is +modified. When that happens, the entire original list is duplicated, and +the changes are made directly to members of the "copy" list. (This is +important for really large archives, like a by-file archive with the +entire contents of a hard drive, where the record index could be several +megabytes in size.) + +It would be more *memory* efficient to simply maintain a list of what +has changed. However, we can't disturb the "original" list in any way or +we lose the ability to roll back quickly if the operation is aborted. +Consequently, we need to create a new list of records that reflects +the state of the new archive, so that when we rename the temp file over +the original, we can simply "rename" the new record list over the original. +Since we're going to need the new list eventually, we might as well create +it as soon as it is needed, and deal with memory allocation failures up +front rather than during the update process. (Some items, such as the +record's file offset in the archive, have to be updated even for records +that aren't themselves changing... which means we potentially need to +modify all existing record structures, so we need a complete copy of the +record list regardless of how little or how much has changed.) + +This also ties into the "modify original archive file directly if possible" +option, which avoids the need for creating and renaming a temp file. If +the only changes are updates to pre-sized records (e.g. renaming a file +inside the archive, or updating a comment), or adding new records onto the +end, there is little risk and possibly a huge efficiency gain in just +modifying the archive in place. If none of the operations caused the +"copy" list to be initialized, then clearly there's no need to write to a +temp file. (It's not actually this simple, because updates to pre-sized +threads are annotated in the "copy" list.) + +One of the goals was to be able to execute a sequence of operations like: + + - open original archive + - read original archive + - modify archive + - flush (success) + - modify archive + - flush (failure, rollback) + - modify archive + - flush (success) + - close archive + +The archive is opened at the start and held open across many operations. +There is never a need to re-read the entire archive. We could avoid the +need to allocate two complete Record lists by requiring that the archive be +re-scanned after changes are aborted; if we did that, we could just modify +the original record list in place, and let the changes become "permanent" +after a successful write. In many ways, though, its cleaner to have two +lists. + +Archives with several thousand entries should be sufficiently rare, and +virtual memory should be sufficiently plentiful, that this won't be a +problem for anyone. Scanning repeatedly through a 15MB archive stored on a +CD-ROM is likely to be very annoying though, so the design makes every +attempt to avoid repeated scans of the archive. And in any event, this +only applies to archive updates. The memory requirements for simple file +extraction are minimal. + +In summary: + + "orig" list has original set of records, and is not disturbed until + the changes are committed. + "copy" list is created on first add/update/delete operation, and + initially contains a complete copy of "orig". + "new" list contains all new additions to the archive, including + new additions that replace existing entries (the existing entry + is deleted from "copy" and then added to "new"). + + +Each Record in the list has a "thread modification" list attached to it. +Any changes to the record header or additions to the thread mod list are +made in the "copy" set; the "original" set remains untouched. The thread +mod list can have the following items in it: + + - delete thread (NuThreadIdx) + - add thread (type, otherSize, format, +contents) + - update pre-sized thread (NuThreadIdx, +contents) + +Contents are specified with a NuDataSource, which allows the application +to indicate that the data is already compressed. This is useful for +copying parts of records between archives without having to expand and +recompress the data. + +Some interactions and concepts that are important to understand: + + When a file is added, the file type information will be placed in the + "new" Record immediately (subject to some restrictions: adding a data + fork always causes the type info to be updated, adding a rsrc fork only + updates the type info if a data fork is not already present). + + Deleting a record results in the Record being removed from the "copy" + list immediately. Future modify operations on that NuRecordIdx will + fail. Future read operations will work just fine until the next + NuFlush is issued, because read operations use the "original" list. + + Deleting all threads from a record results in the record being + deleted, but not until the NuFlush call is issued. It is possible to + delete all the existing threads and then add new ones. + + It is *not* allowed to delete a modified thread, modify a deleted thread, + or delete a record that has been modified. This limitation was added to + keep the system simple. Note this does not mean you can't delete a data + fork and add a data fork; doing so results in operations on two threads + with different NuThreadIdx values. What you can't do is update the + filename thread and then delete it, or vice-versa. (If anyone can think + of a reason why you'd want to rename a file and then delete it with the + same NuFlush call, I'll figure out a way to support it.) + + Updating a filename thread is intercepted, and causes the Record's + filename cache to be updated as well. Adding a filename thread for + records where the filename is stored in the record itself cause the + "in-record" filename to be zeroed. Adding a filename thread to a + record that already has one isn't allowed; nufxlib restricts you to + a single filename thread per record. + + Some actions on an archive are allowed but strongly discouraged. For + example, deleting a filename thread but leaving the data threads behind + is a valid thing to do, but leaves most archivers in a state of mild + confusion. Deleting the data threads but leaving the filename thread is + similarly perplexing. + + You can't call "update thread" on a thread that doesn't yet exist, + even if an "add thread" call has been made. You can, however, call + "add thread" on a newly created Record. + +When a new record is created because of a "create record" call, a filename +thread is created automatically. It is not necessary to explicitly add the +filename. + +Failures encountered while committing changes to a record cause all +operations on that record to be rolled back. If, during a NuFlush, a +file add fails, the user is given the option of aborting the entire +operation or skipping the file in question (and perhaps retrying or other +options as well). Aborting the flush causes a complete rollback. If only +the thread mod operation is canceled, then all thread mods for that record +are ignored. The temp file (or archive file) will have its file pointer +reset to the original start of the record, and if the record already +existed in the original archive, the full original record will be copied +over. This may seem drastic, but it helps ensure that you don't end up +with a record in a partially created state. + +If a failure occurs during an "update in place", it isn't possible to +roll back all changes. If the failure was due to a bug in NufxLib, it +is possible that the archive could be unrecoverably damaged. NufxLib +tries to identify such situations, and will leave the archive open in +read-only mode after rolling back any new file additions. + + +Updating Filenames +================== + +Updating filenames is a small nightmare, because the filename can be +either in the record header or in a filename thread. It's possible, +but illogical, to have a single record with a filename in the record +header and two or more filenames in threads. + +NufxLib will not automatically "fix" broken records, but it will prevent +applications from creating situations that should not exist. + + When reading an archive, NufxLib will use the filename from the + first filename thread found. If no filename threads are found, the + filename from the record header will be used. + + If you add a filename thread to a record that has a filename in the + record header, the header name will be removed. + + If you update a filename thread in a record that has a filename in + the record header, the header name will be left untouched. + + Adding a filename thread is only allowed if no filename thread exists, + or all existing filename threads have been deleted. + diff --git a/nufxlib-0/NufxLib.h b/nufxlib-0/NufxLib.h new file mode 100644 index 0000000..502957a --- /dev/null +++ b/nufxlib-0/NufxLib.h @@ -0,0 +1,733 @@ +/* + * NuFX archive manipulation library + * 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 Library General Public License, see the file COPYING.LIB. + * + * External interface (types, defines, and function prototypes). + */ +#ifndef __NufxLib__ +#define __NufxLib__ + +#include + + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * =========================================================================== + * Types + * =========================================================================== + */ + +/* + * Error values returned from functions. + * + * These are negative so that they don't conflict with system-defined + * errors (like ENOENT). A NuError can hold either. + */ +typedef enum NuError { + kNuErrNone = 0, + + kNuErrGeneric = -1, + kNuErrInternal = -2, + kNuErrUsage = -3, + kNuErrSyntax = -4, + kNuErrMalloc = -5, + kNuErrInvalidArg = -6, + kNuErrBadStruct = -7, + kNuErrUnexpectedNil = -8, + kNuErrBusy = -9, + + kNuErrSkipped = -10, /* processing skipped by request */ + kNuErrAborted = -11, /* processing aborted by request */ + kNuErrRename = -12, /* user wants to rename before extracting */ + + kNuErrFile = -20, + kNuErrFileOpen = -21, + kNuErrFileClose = -22, + kNuErrFileRead = -23, + kNuErrFileWrite = -24, + kNuErrFileSeek = -25, + kNuErrFileExists = -26, /* existed when it shouldn't */ + kNuErrFileNotFound = -27, /* didn't exist when it should have */ + kNuErrFileStat = -28, /* some sort of GetFileInfo failure */ + kNuErrFileNotReadable = -29, /* bad access permissions */ + + kNuErrDirExists = -30, /* dir exists, don't need to create it */ + kNuErrNotDir = -31, /* expected a dir, got a regular file */ + kNuErrNotRegularFile = -32, /* expected regular file, got weirdness */ + kNuErrDirCreate = -33, /* unable to create a directory */ + kNuErrOpenDir = -34, /* error opening directory */ + kNuErrReadDir = -35, /* error reading directory */ + kNuErrFileSetDate = -36, /* unable to set file date */ + kNuErrFileSetAccess = -37, /* unable to set file access permissions */ + + kNuErrNotNuFX = -40, /* 'NuFile' missing; not a NuFX archive? */ + kNuErrBadMHVersion = -41, /* bad master header version */ + kNuErrRecHdrNotFound = -42, /* 'NuFX' missing; corrupted archive? */ + kNuErrNoRecords = -43, /* archive doesn't have any records */ + kNuErrBadRecord = -44, /* something about the record looked bad */ + kNuErrBadMHCRC = -45, /* bad master header CRC */ + kNuErrBadRHCRC = -46, /* bad record header CRC */ + kNuErrBadThreadCRC = -47, /* bad thread header CRC */ + kNuErrBadDataCRC = -48, /* bad CRC detected in the data */ + + kNuErrBadFormat = -50, /* compression type not supported */ + kNuErrBadData = -51, /* expansion func didn't like input */ + kNuErrBufferOverrun = -52, /* overflowed a user buffer */ + kNuErrBufferUnderrun = -53, /* underflowed a user buffer */ + kNuErrOutMax = -54, /* output limit exceeded */ + + kNuErrNotFound = -60, /* (generic) search unsuccessful */ + kNuErrRecordNotFound = -61, /* search for specific record failed */ + kNuErrRecIdxNotFound = -62, /* search by NuRecordIdx failed */ + kNuErrThreadIdxNotFound = -63, /* search by NuThreadIdx failed */ + kNuErrThreadIDNotFound = -64, /* search by NuThreadID failed */ + kNuErrRecNameNotFound = -65, /* search by storageName failed */ + kNuErrRecordExists = -66, /* found existing record with same name */ + + kNuErrAllDeleted = -70, /* attempt to delete everything */ + kNuErrArchiveRO = -71, /* archive is open in read-only mode */ + kNuErrModRecChange = -72, /* tried to change modified record */ + kNuErrModThreadChange = -73, /* tried to change modified thread */ + kNuErrThreadAdd = -74, /* adding that thread creates a conflict */ + kNuErrNotPreSized = -75, /* tried to update a non-pre-sized thread */ + kNuErrPreSizeOverflow = -76, /* too much data */ + kNuErrInvalidFilename = -77, /* invalid filename */ + + kNuErrLeadingFssep = -80, /* names in archives must not start w/sep */ + kNuErrNotNewer = -81, /* item same age or older than existing */ + kNuErrDuplicateNotFound = -82, /* "must overwrite" was set, but item DNE */ + kNuErrDamaged = -83 /* original archive may have been damaged */ +} NuError; + +/* + * Return values from callback functions. + */ +typedef enum NuResult { + kNuOK = 0, + kNuSkip = 1, + kNuAbort = 2, + /*kNuAbortAll = 3,*/ + kNuRetry = 4, + kNuIgnore = 5, + kNuRename = 6, + kNuOverwrite = 7 +} NuResult; + +/* + * NuRecordIdxs are assigned to records in an archive. You may assume that + * the values are unique, but that is all. + */ +typedef unsigned long NuRecordIdx; + +/* + * NuThreadIdxs are assigned to threads within a record. Again, you may + * assume that the values are unique within a record, but that is all. + */ +typedef unsigned long NuThreadIdx; + +/* + * Thread ID, a combination of thread_class and thread_kind. Standard + * values have explicit identifiers. + */ +typedef unsigned long NuThreadID; +#define NuMakeThreadID(class, kind) /* construct a NuThreadID */ \ + ((unsigned long)class << 16 | (unsigned long)kind) +#define NuGetThreadID(pThread) /* pull NuThreadID out of NuThread */ \ + (NuMakeThreadID(pThread->thThreadClass, pThread->thThreadKind)) +#define NuThreadIDGetClass(threadID) /* get threadClass from NuThreadID */ \ + ((unsigned short) ((unsigned long)(threadID) >> 16)) +#define NuThreadIDGetKind(threadID) /* get threadKind from NuThreadID */ \ + ((unsigned short) ((threadID) & 0xffff)) +#define kNuThreadClassMessage 0x0000 +#define kNuThreadClassControl 0x0001 +#define kNuThreadClassData 0x0002 +#define kNuThreadClassFilename 0x0003 +#define kNuThreadIDOldComment NuMakeThreadID(kNuThreadClassMessage, 0x0000) +#define kNuThreadIDComment NuMakeThreadID(kNuThreadClassMessage, 0x0001) +#define kNuThreadIDIcon NuMakeThreadID(kNuThreadClassMessage, 0x0002) +#define kNuThreadIDMkdir NuMakeThreadID(kNuThreadClassControl, 0x0000) +#define kNuThreadIDDataFork NuMakeThreadID(kNuThreadClassData, 0x0000) +#define kNuThreadIDDiskImage NuMakeThreadID(kNuThreadClassData, 0x0001) +#define kNuThreadIDRsrcFork NuMakeThreadID(kNuThreadClassData, 0x0002) +#define kNuThreadIDFilename NuMakeThreadID(kNuThreadClassFilename, 0x0000) +#define kNuThreadIDWildcard NuMakeThreadID(0xffff, 0xffff) + +/* enumerate the possible values for thThreadFormat */ +typedef enum NuThreadFormat { + kNuThreadFormatUncompressed = 0x0000, + kNuThreadFormatHuffmanSQ = 0x0001, + kNuThreadFormatLZW1 = 0x0002, + kNuThreadFormatLZW2 = 0x0003, + kNuThreadFormatLZC12 = 0x0004, + kNuThreadFormatLZC16 = 0x0005 +} NuThreadFormat; + + +/* extract the filesystem separator char from the "file_sys_info" field */ +#define NuGetSepFromSysInfo(sysInfo) \ + ((char) ((sysInfo) & 0xff)) +/* return a file_sys_info with a replaced filesystem separator */ +#define NuSetSepInSysInfo(sysInfo, newSep) \ + ((ushort) (((sysInfo) & 0xff00) | ((newSep) & 0xff)) ) + +/* GS/OS-defined file system identifiers; sadly, UNIX is not among them */ +typedef enum NuFileSysID { + kNuFileSysUnknown = 0, /* NuFX spec says use this */ + kNuFileSysProDOS = 1, + kNuFileSysDOS33 = 2, + kNuFileSysDOS32 = 3, + kNuFileSysPascal = 4, + kNuFileSysMacHFS = 5, + kNuFileSysMacMFS = 6, + kNuFileSysLisa = 7, + kNuFileSysCPM = 8, + kNuFileSysCharFST = 9, + kNuFileSysMSDOS = 10, + kNuFileSysHighSierra = 11, + kNuFileSysISO9660 = 12, + kNuFileSysAppleShare = 13 +} NuFileSysID; + +/* simplified definition of storage types */ +typedef enum NuStorageType { + kNuStorageUnknown = 0, + kNuStorageSeedling = 1, /* <= 512 bytes */ + kNuStorageSapling = 2, /* < 128KB */ + kNuStorageTree = 3, /* < 16MB */ + kNuStorageExtended = 5, /* forked (any size) */ + kNuStorageDirectory = 0x0d +} NuStorageType; + +/* flags for NuOpenRW */ +enum { + kNuOpenCreat = 0x0001, + kNuOpenExcl = 0x0002 +}; + + +/* + * The actual NuArchive structure is opaque, and should only be visible + * to the library. We define it here as an ambiguous struct. + */ +typedef struct NuArchive NuArchive; + +/* + * Generic callback prototype. + */ +typedef NuResult (*NuCallback)(NuArchive* pArchive, void* args); + +/* + * Parameters that affect archive operations. + */ +typedef enum NuValueID { + kNuValueInvalid = 0, + kNuValueIgnoreCRC = 1, + kNuValueDataCompression = 2, + kNuValueDiscardWrapper = 3, + kNuValueEOL = 4, + kNuValueConvertExtractedEOL = 5, + kNuValueOnlyUpdateOlder = 6, + kNuValueAllowDuplicates = 7, + kNuValueHandleExisting = 8, + kNuValueModifyOrig = 9, + kNuValueMimicSHK = 10 +} NuValueID; +typedef unsigned long NuValue; + +/* + * Enumerated values for things you pass in a NuValue. + */ +enum NuValueValue { + /* for the truly retentive */ + kNuValueFalse = 0, + kNuValueTrue = 1, + + /* for kNuValueDataCompression */ + kNuCompressNone = 10, + kNuCompressSQ = 11, + kNuCompressLZW1 = 12, + kNuCompressLZW2 = 13, + kNuCompressLZC12 = 14, + kNuCompressLZC16 = 15, + + /* for kNuValueEOL */ + kNuEOLUnknown = 50, + kNuEOLCR = 51, + kNuEOLLF = 52, + kNuEOLCRLF = 53, + + /* for kNuValueConvertExtractedEOL */ + kNuConvertOff = 60, + kNuConvertOn = 61, + kNuConvertAuto = 62, + + /* for kNuValueHandleExisting */ + kNuMaybeOverwrite = 90, + kNuNeverOverwrite = 91, + kNuAlwaysOverwrite = 93, + kNuMustOverwrite = 94 +}; + + +/* + * Pull out archive attributes. + */ +typedef enum NuAttrID { + kNuAttrInvalid = 0, + kNuAttrArchiveType = 1, + kNuAttrNumRecords = 2 +} NuAttrID; +typedef unsigned long NuAttr; + +/* + * Archive types. + */ +typedef enum NuArchiveType { + kNuArchiveUnknown, /* .??? */ + kNuArchiveNuFX, /* .SHK (sometimes .SDK) */ + kNuArchiveNuFXInBNY, /* .BXY */ + kNuArchiveNuFXSelfEx, /* .SEA */ + kNuArchiveNuFXSelfExInBNY, /* .BSE */ + + kNuArchiveBNY /* .BNY, .BQY - not supported */ +} NuArchiveType; + + +/* + * Some common values for "locked" and "unlocked". Under ProDOS each bit + * can be set independently, so don't use these defines to *interpret* + * what you see. They're reasonable things to *set* the access field to. + */ +#define kNuAccessLocked 0x21 +#define kNuAccessUnlocked 0xe3 + + +/* + * NuFlush result flags. + */ +#define kNuFlushSucceeded (1L) +#define kNuFlushAborted (1L << 1) +#define kNuFlushCorrupted (1L << 2) +#define kNuFlushReadOnly (1L << 3) +#define kNuFlushInaccessible (1L << 4) + + +/* + * =========================================================================== + * NuFX archive defintions + * =========================================================================== + */ + +typedef struct NuThreadMod NuThreadMod; /* dummy def for internal struct */ +typedef union NuDataSource NuDataSource; /* dummy def for internal struct */ +typedef union NuDataSink NuDataSink; /* dummy def for internal struct */ + +/* + * NuFX Date/Time structure; same as TimeRec from IIgs "misctool.h". + */ +typedef struct NuDateTime { + unsigned char second; + unsigned char minute; + unsigned char hour; + unsigned char year; + unsigned char day; + unsigned char month; + unsigned char extra; + unsigned char weekDay; +} NuDateTime; + +/* + * NuFX "thread" definition. + */ +typedef struct NuThread { + /* from the archive */ + unsigned short thThreadClass; + NuThreadFormat thThreadFormat; + unsigned short thThreadKind; + unsigned short thThreadCRC; /* comp or uncomp data; see rec vers */ + unsigned long thThreadEOF; + unsigned long thCompThreadEOF; + + /* extra goodies */ + NuThreadIdx threadIdx; + unsigned long actualThreadEOF; /* disk images might be off */ + long fileOffset; /* fseek offset to data in file */ + + /* internal use only */ + unsigned short used; /* mark as uninteresting */ +} NuThread; + +/* + * NuFX "record" definition. + */ +#define kNufxIDLen 4 /* len of 'NuFX' with funky MSBs */ +#define kNuReasonableAttribCount 256 +#define kNuReasonableFilenameLen 1024 +#define kNuReasonableTotalThreads 16 +#define kNuMaxRecordVersion 3 /* max we can handle */ +#define kNuOurRecordVersion 3 /* what we write */ +typedef struct NuRecord { + /* version 0+ */ + unsigned char recNufxID[kNufxIDLen]; + unsigned short recHeaderCRC; + unsigned short recAttribCount; + unsigned short recVersionNumber; + unsigned long recTotalThreads; + NuFileSysID recFileSysID; + unsigned short recFileSysInfo; + unsigned long recAccess; + unsigned long recFileType; + unsigned long recExtraType; + unsigned short recStorageType; /* NuStorage*,file_sys_block_size */ + NuDateTime recCreateWhen; + NuDateTime recModWhen; + NuDateTime recArchiveWhen; + + /* option lists only in version 1+ */ + unsigned short recOptionSize; + unsigned char* recOptionList; /* NULL if v0 or recOptionSize==0 */ + + /* data specified by recAttribCount, not accounted for by option list */ + long extraCount; + unsigned char* extraBytes; + + unsigned short recFilenameLength; /* usually zero */ + char* recFilename; /* doubles as disk volume_name */ + + /* extra goodies; "dirtyHeader" does not apply to anything below */ + NuRecordIdx recordIdx; /* session-unique record index */ + char* threadFilename; /* extracted from filename thread */ + char* newFilename; /* memorized during "add file" call */ + const char* filename; /* points at recFilen or threadFilen */ + unsigned long recHeaderLength; /* size of rec hdr, incl thread hdrs */ + unsigned long totalCompLength; /* total len of data in archive file */ + + long fileOffset; /* file offset of record header */ + + /* use provided interface to access this */ + struct NuThread* pThreads; /* ptr to thread array */ + + /* private -- things the application shouldn't look at */ + struct NuRecord* pNext; /* used internally */ + NuThreadMod* pThreadMods; /* used internally */ + short dirtyHeader; /* set in "copy" when hdr fields uptd */ + short dropRecFilename; /* if set, we're dropping this name */ +} NuRecord; + +/* + * NuFX "master header" definition. + * + * The "mhReserved2" entry doesn't appear in my copy of the $e0/8002 File + * Type Note, but as best as I can recall the MH block must be 48 bytes. + */ +#define kNufileIDLen 6 /* length of 'NuFile' with funky MSBs */ +#define kNufileMasterReserved1Len 8 +#define kNufileMasterReserved2Len 6 +#define kNuMaxMHVersion 2 /* max we can handle */ +#define kNuOurMHVersion 2 /* what we write */ +typedef struct NuMasterHeader { + unsigned char mhNufileID[kNufileIDLen]; + unsigned short mhMasterCRC; + unsigned long mhTotalRecords; + NuDateTime mhArchiveCreateWhen; + NuDateTime mhArchiveModWhen; + unsigned short mhMasterVersion; + unsigned char mhReserved1[kNufileMasterReserved1Len]; + unsigned long mhMasterEOF; + unsigned char mhReserved2[kNufileMasterReserved2Len]; + + /* private -- internal use only */ + short isValid; +} NuMasterHeader; + + +/* + * =========================================================================== + * Misc declarations + * =========================================================================== + */ + +/* + * Record attributes that can be changed with NuSetRecordAttr. This is + * a small subset of the full record. + */ +typedef struct NuRecordAttr { + NuFileSysID fileSysID; + /*unsigned short fileSysInfo;*/ + unsigned long access; + unsigned long fileType; + unsigned long extraType; + NuDateTime createWhen; + NuDateTime modWhen; + NuDateTime archiveWhen; +} NuRecordAttr; + +/* + * Some additional details about a file. + */ +typedef struct NuFileDetails { + /* used during AddFile call */ + NuThreadID threadID; /* data, rsrc, disk img? */ + + /* these go straight into the NuRecord */ + const char* storageName; + NuFileSysID fileSysID; + unsigned short fileSysInfo; + unsigned long access; + unsigned long fileType; + unsigned long extraType; + unsigned short storageType; /* use Unknown, or disk block size */ + NuDateTime createWhen; + NuDateTime modWhen; + NuDateTime archiveWhen; +} NuFileDetails; + + +/* + * Passed into the SelectionFilter callback. + */ +typedef struct NuSelectionProposal { + const NuRecord* pRecord; + const NuThread* pThread; +} NuSelectionProposal; + +/* + * Passed into the OuputPathnameFilter callback. + */ +typedef struct NuPathnameProposal { + const char* pathname; + char filenameSeparator; + const NuRecord* pRecord; + const NuThread* pThread; + + const char* newPathname; + unsigned char newFilenameSeparator; + /*NuThreadID newStorage;*/ + NuDataSink* newDataSink; +} NuPathnameProposal; + + +/* used by error handler and progress updater to indicate what we're doing */ +typedef enum NuOperation { + kNuOpUnknown = 0, + kNuOpAdd, + kNuOpExtract, + kNuOpTest, + kNuOpDelete, /* not used for progress updates */ + kNuOpContents /* not used for progress updates */ +} NuOperation; + +/* state of progress when adding or extracting */ +typedef enum NuProgressState { + kNuProgressPreparing, /* not started yet */ + kNuProgressOpening, /* opening files */ + + kNuProgressCompressing, /* compressing data */ + kNuProgressStoring, /* storing (no compression) data */ + kNuProgressExpanding, /* expanding data */ + kNuProgressCopying, /* copying data (in or out) */ + + kNuProgressDone, /* all done, success */ + kNuProgressSkipped, /* all done, we skipped this one */ + kNuProgressFailed /* all done, failure */ +} NuProgressState; + +/* + * Passed into the ProgressUpdater callback. + * + * [ Thought for the day: add an optional flag that causes us to only + * call the progressFunc when the "percentComplete" changes by more + * than a specified amount. ] + */ +typedef struct NuProgressData { + /* what are we doing */ + NuOperation operation; + /* what specifically are we doing */ + NuProgressState state; + /* how far along are we */ + short percentComplete; /* 0-100 */ + + /* original pathname (in archive for expand, on disk for compress) */ + const char* origPathname; + /* processed pathname (PathnameFilter for expand, in-record for compress) */ + const char* pathname; + /* basename of "pathname" */ + const char* filename; + /* pointer to the record we're expanding from */ + const NuRecord* pRecord; + + unsigned long uncompressedLength; /* size of uncompressed data */ + unsigned long uncompressedProgress; /* #of bytes in/out */ + + struct { + NuThreadFormat threadFormat; /* compression being applied */ + } compress; + + struct { + unsigned long totalCompressedLength; /* all "data" threads */ + unsigned long totalUncompressedLength; + + /*unsigned long compressedLength; * size of compressed data */ + /*unsigned long compressedProgress; * #of compressed bytes in/out*/ + const NuThread* pThread; /* thread we're working on */ + NuValue convertEOL; /* set if LF/CR conv is on */ + } expand; + + /* pay no attention */ + NuCallback progressFunc; +} NuProgressData; + +/* + * Passed into the ErrorHandler callback. + */ +typedef struct NuErrorStatus { + NuOperation operation; /* were we adding, extracting, ?? */ + NuError err; /* library error code */ + int sysErr; /* system error code, if applicable */ + const char* message; /* (optional) message to user */ + const NuRecord* pRecord; /* relevant record, if any */ + const char* pathname; /* relevant pathname, if any */ + char filenameSeparator; /* fssep for this path, if any */ + /*char origArchiveTouched;*/ + + char canAbort; /* give option to abort */ + /*char canAbortAll;*/ /* abort + discard all recent changes */ + char canRetry; /* give option to retry same op */ + char canIgnore; /* give option to ignore error */ + char canSkip; /* give option to skip this file/rec */ + char canRename; /* give option to rename file */ + char canOverwrite; /* give option to overwrite file */ +} NuErrorStatus; + +/* + * Error message callback gets one of these. + */ +typedef struct NuErrorMessage { + const char* message; /* the message itself */ + NuError err; /* relevant error code (may be none) */ + short isDebug; /* set for debug-only messages */ + + /* these identify where the message originated if lib built w/debug set */ + const char* file; /* source file */ + int line; /* line number */ + const char* function; /* function name (might be nil) */ +} NuErrorMessage; + + +/* + * =========================================================================== + * Function prototypes + * =========================================================================== + */ + +/* streaming and non-streaming read-only interfaces */ +NuError NuStreamOpenRO(FILE* infp, NuArchive** ppArchive); +NuError NuContents(NuArchive* pArchive, NuCallback contentFunc); +NuError NuExtract(NuArchive* pArchive); +NuError NuTest(NuArchive* pArchive); + +/* strictly non-streaming read-only interfaces */ +NuError NuOpenRO(const char* archivePathname, NuArchive** ppArchive); +NuError NuExtractRecord(NuArchive* pArchive, NuRecordIdx recordIdx); +NuError NuExtractThread(NuArchive* pArchive, NuThreadIdx threadIdx, + NuDataSink* pDataSink); +NuError NuGetRecord(NuArchive* pArchive, NuRecordIdx recordIdx, + const NuRecord** ppRecord); +NuError NuGetRecordIdxByName(NuArchive* pArchive, const char* name, + NuRecordIdx* pRecordIdx); +NuError NuGetRecordIdxByPosition(NuArchive* pArchive, unsigned long position, + NuRecordIdx* pRecordIdx); + +/* read/write interfaces */ +NuError NuOpenRW(const char* archivePathname, const char* tempPathname, + unsigned long flags, NuArchive** ppArchive); +NuError NuFlush(NuArchive* pArchive, long* pStatusFlags); +NuError NuAddRecord(NuArchive* pArchive, const NuFileDetails* pFileDetails, + NuRecordIdx* pRecordIdx); +NuError NuAddThread(NuArchive* pArchive, NuRecordIdx recordIdx, + NuThreadIdx threadID, NuDataSource* pDataSource, + NuThreadIdx* pThreadIdx); +NuError NuAddFile(NuArchive* pArchive, const char* pathname, + const NuFileDetails* pFileDetails, short fromRsrcFork, + NuRecordIdx* pRecordIdx); +NuError NuRename(NuArchive* pArchive, NuRecordIdx recordIdx, + const char* pathname, char fssep); +NuError NuSetRecordAttr(NuArchive* pArchive, NuRecordIdx recordIdx, + const NuRecordAttr* pRecordAttr); +NuError NuUpdatePresizedThread(NuArchive* pArchive, NuThreadIdx threadIdx, + NuDataSource* pDataSource, long* pMaxLen); +NuError NuDelete(NuArchive* pArchive); +NuError NuDeleteRecord(NuArchive* pArchive, NuRecordIdx recordIdx); +NuError NuDeleteThread(NuArchive* pArchive, NuRecordIdx threadIdx); + +/* general interfaces */ +NuError NuClose(NuArchive* pArchive); +NuError NuAbort(NuArchive* pArchive); +NuError NuGetMasterHeader(NuArchive* pArchive, + const NuMasterHeader** ppMasterHeader); +NuError NuGetExtraData(NuArchive* pArchive, void** ppData); +NuError NuSetExtraData(NuArchive* pArchive, void* pData); +NuError NuGetValue(NuArchive* pArchive, NuValueID ident, NuValue* pValue); +NuError NuSetValue(NuArchive* pArchive, NuValueID ident, NuValue value); +NuError NuGetAttr(NuArchive* pArchive, NuAttrID ident, NuAttr* pAttr); +NuError NuGetVersion(long* pMajorVersion, long* pMinorVersion, + long* pBugVersion, const char** ppBuildDate, + const char** ppBuildFlags); +const char* NuStrError(NuError err); +NuError NuDebugDumpArchive(NuArchive* pArchive); + +/* sources and sinks */ +NuError NuCreateDataSourceForFile(NuThreadFormat threadFormat, short doClose, + unsigned long otherLen, const char* pathname, short isFromRsrcFork, + NuDataSource** ppDataSource); +NuError NuCreateDataSourceForFP(NuThreadFormat threadFormat, short doClose, + unsigned long otherLen, FILE* fp, long offset, long length, + NuDataSource** ppDataSource); +NuError NuCreateDataSourceForBuffer(NuThreadFormat threadFormat, short doClose, + unsigned long otherLen, const unsigned char* buffer, long offset, + long length, NuDataSource** ppDataSource); +NuError NuFreeDataSource(NuDataSource* pDataSource); +NuError NuDataSourceSetRawCrc(NuDataSource* pDataSource, unsigned short crc); +NuError NuCreateDataSinkForFile(short doExpand, NuValue convertEOL, + const char* pathname, char fssep, NuDataSink** ppDataSink); +NuError NuCreateDataSinkForFP(short doExpand, NuValue convertEOL, + FILE* fp, NuDataSink** ppDataSink); +NuError NuCreateDataSinkForBuffer(short doExpand, NuValue convertEOL, + unsigned char* buffer, unsigned long bufLen, + NuDataSink** ppDataSink); +NuError NuFreeDataSink(NuDataSink* pDataSink); +NuError NuDataSinkGetOutCount(NuDataSink* pDataSink, unsigned long* pOutCount); + +/* miscellaneous non-archive operations */ +void NuRecordCopyAttr(NuRecordAttr* pRecordAttr, const NuRecord* pRecord); +NuError NuRecordCopyThreads(const NuRecord* pRecord, NuThread** ppThreads); +unsigned long NuRecordGetNumThreads(const NuRecord* pRecord); +const NuThread* NuThreadGetByIdx(const NuThread* pThread, long idx); +short NuIsPresizedThreadID(NuThreadID threadID); + +#define NuGetThread(pRecord, idx) ( (const NuThread*) \ + ((unsigned long) (idx) < (unsigned long) (pRecord)->recTotalThreads ? \ + &(pRecord)->pThreads[(idx)] : NULL) \ + ) + + +/* callback setters */ +NuError NuSetSelectionFilter(NuArchive* pArchive, NuCallback filterFunc); +NuError NuSetOutputPathnameFilter(NuArchive* pArchive, NuCallback filterFunc); +NuError NuSetProgressUpdater(NuArchive* pArchive, NuCallback updateFunc); +NuError NuSetErrorHandler(NuArchive* pArchive, NuCallback errorFunc); +NuError NuSetErrorMessageHandler(NuArchive* pArchive, + NuCallback messageHandlerFunc); +NuError NuSetGlobalErrorMessageHandler(NuCallback messageHandlerFunc); + + +#ifdef __cplusplus +} +#endif + +#endif /*__NufxLib__*/ diff --git a/nufxlib-0/NufxLibPriv.h b/nufxlib-0/NufxLibPriv.h new file mode 100644 index 0000000..72edeb5 --- /dev/null +++ b/nufxlib-0/NufxLibPriv.h @@ -0,0 +1,827 @@ +/* + * NuFX archive manipulation library + * 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 Library General Public License, see the file COPYING.LIB. + * + * Global internal declarations and definitions. + */ +#ifndef __NufxLibPriv__ +#define __NufxLibPriv__ + +/* include files that everybody needs */ +#include "SysDefs.h" +#include "NufxLib.h" +#include "MiscStuff.h" + +#ifdef USE_DMALLOC +/* enable with something like "dmalloc -l logfile -i 100 medium" */ +# include "dmalloc.h" +#endif + + +/* + * =========================================================================== + * NuArchive definition + * =========================================================================== + */ + +/* + * Archives can be opened in streaming read-only, non-streaming read-only, + * and non-streaming read-write mode. + */ +typedef enum NuOpenMode { + kNuOpenUnknown, + kNuOpenStreamingRO, + kNuOpenRO, + kNuOpenRW +} NuOpenMode; +#define Nu_IsStreaming(pArchive) ((pArchive)->openMode == kNuOpenStreamingRO) +#define Nu_IsReadOnly(pArchive) ((pArchive)->openMode == kNuOpenStreamingRO ||\ + (pArchive)->openMode == kNuOpenRO) + +#ifdef FOPEN_WANTS_B +# define kNuFileOpenReadOnly "rb" +# define kNuFileOpenReadWrite "r+b" +# define kNuFileOpenWriteTrunc "wb" +# define kNuFileOpenReadWriteCreat "w+b" +#else +# define kNuFileOpenReadOnly "r" +# define kNuFileOpenReadWrite "r+" +# define kNuFileOpenWriteTrunc "w" +# define kNuFileOpenReadWriteCreat "w+" +#endif + + +/* + * Some NuFX and Binary II definitions. + */ +#define kNuMasterHeaderSize 48 /* size of fixed-length master header */ +#define kNuRecordHeaderBaseSize 58 /* size of rec hdr up to variable stuff */ +#define kNuThreadHeaderSize 16 /* size of fixed-length thread header */ +#define kNuDefaultFilenameThreadSize 32 /* default size of filename thred */ +#define kNuDefaultCommentSize 200 /* size of GSHK-mimic comments */ +#define kNuBinary2BlockSize 128 /* size of bxy header and padding */ +#define kNuSEAOffset 0x2ee5 /* fixed(??) offset to data in SEA */ + +#define kNuInitialChunkCRC 0x0000 /* start for CRC in LZW/1 chunk */ +#define kNuInitialThreadCRC 0xffff /* start for CRC in v3 thread header */ + +/* size of general-purpose compression buffer */ +#define kNuGenCompBufSize 32768 + +#define kNuCharLF 0x0a +#define kNuCharCR 0x0d + + +/* + * A list of records. Generally we use one of these for read-only + * archives, and two for read-write. + * + * The "loaded" flag is set when we've made some use of the record set. + * Relying on "numRecords" won't always work; for example, if the "copy" + * record set was initialized from "orig", and then had all of its records + * deleted, you couldn't look at "numRecords" and decide whether it was + * appropriate to use "orig" or not. + */ +typedef struct NuRecordSet { + Boolean loaded; + ulong numRecords; + NuRecord* nuRecordHead; + NuRecord* nuRecordTail; +} NuRecordSet; + +/* + * Archive state. + */ +struct NuArchive { + ulong structMagic; + Boolean busy; + + NuOpenMode openMode; + Boolean newlyCreated; + char* archivePathname; /* pathname or "(stream)" */ + FILE* archiveFp; + NuArchiveType archiveType; + long headerOffset; /* adjustment for BXY/SEA/BSE */ + + char* tmpPathname; /* temp file, for writes */ + FILE* tmpFp; + + /* used during initial processing; helps avoid ftell() calls */ + long currentOffset; + + /* setting this changes Extract into Test */ + Boolean testMode; + + /* clumsy way of remembering name used for other fork in forked file */ + const char* lastFileCreated; + /* clumsy way to avoid trying to create the same subdir several times */ + const char* lastDirCreated; + + /* master header from the archive */ + NuMasterHeader masterHeader; /* original */ + NuMasterHeader newMasterHeader; /* working copy during update */ + + /* list of records from archive, plus some extra state */ + NuRecordIdx recordIdxSeed; /* where the NuRecordIdxs start */ + NuRecordIdx nextRecordIdx; /* next record gets this value */ + Boolean haveToc; /* set if we have full TOC */ + NuRecordSet origRecordSet; /* records from archive */ + NuRecordSet copyRecordSet; /* copy of orig, for write ops */ + NuRecordSet newRecordSet; /* newly-added records */ + + /* state for compression functions */ + uchar* compBuf; /* large general-purpose buffer */ + void* lzwCompressState; /* state for LZW/1 and LZW/2 */ + void* lzwExpandState; /* state for LZW/1 and LZW/2 */ + + /* options and attributes that the user can set */ + /* (these can be changed by a callback, so don't cache them internally) */ + void* extraData; /* application-defined pointer */ + + NuValue valAllowDuplicates; /* allow dups when adding? */ + NuValue valConvertExtractedEOL; /* convert EOL during extract? */ + NuValue valDataCompression; /* how to compress when adding? */ + NuValue valDiscardWrapper; /* remove BNY or SEA header? */ + NuValue valEOL; /* EOL value to convert to */ + NuValue valHandleExisting; /* how to deal with existing files*/ + NuValue valIgnoreCRC; /* don't compute or test CRCs */ + NuValue valMimicSHK; /* mimic some ShrinkIt quirks */ + NuValue valModifyOrig; /* modify original arc in place? */ + NuValue valOnlyUpdateOlder; /* modify original arc in place? */ + + /* callback functions */ + NuCallback selectionFilterFunc; + NuCallback outputPathnameFunc; + NuCallback progressUpdaterFunc; + NuCallback errorHandlerFunc; + NuCallback messageHandlerFunc; +}; + +#define kNuArchiveStructMagic 0xc0edbabe + + +#define kNuDefaultRecordName "UNKNOWN" + + +/* + * =========================================================================== + * ThreadMod definition + * =========================================================================== + */ + +/* operations we can perform on threads in a record */ +typedef enum ThreadModKind { + kNuThreadModUnknown = 0, + + kNuThreadModAdd, + kNuThreadModUpdate, + kNuThreadModDelete +} ThreadModKind; + +/* + * We attach a list of these to records we plan to modify. Care is taken + * to ensure that they don't conflict, e.g. you can't update a thread + * right after you delete it, nor delete one you have modified. + */ +struct NuThreadMod { + union { + ThreadModKind kind; + + struct { + ThreadModKind kind; + Boolean used; + } generic; + + struct { + ThreadModKind kind; + Boolean used; + Boolean isPresized; + NuThreadIdx threadIdx; + NuThreadID threadID; + NuThreadFormat threadFormat; + NuDataSource* pDataSource; + } add; + + struct { + ThreadModKind kind; + Boolean used; + NuThreadIdx threadIdx; + NuDataSource* pDataSource; + } update; + + struct { + ThreadModKind kind; + Boolean used; + NuThreadIdx threadIdx; + NuThreadID threadID; /* used for watching filename threads */ + } delete; + } entry; + + struct NuThreadMod* pNext; +}; + + +/* + * =========================================================================== + * NuFunnel/NuStraw definition + * =========================================================================== + */ + +#define kNuFunnelBufSize 16384 + +/* + * File funnel definition. This is used for writing output to files + * (so we can do things like pipe compressed output through an LF->CR + * converter) and archive files (so we can halt compression when the + * output size exceeds the uncompressed original). [ for various reasons, + * I'm not using this on the archive anymore. ] + * + * Funnels are unidirectional. You write data into them with a + * function call; the top-level action (which is usually compressing or + * expanding data) reads from the input and crams things into the pipe. + * We could fully abstract the concept, and write the compression + * functions so that they operate as a Funnel filter, but it's much + * easier to write block-oriented compression than stream-oriented (and + * more to the point, the ShrinkIt LZW functions are very much + * block-oriented). + */ +typedef struct NuFunnel { + /* data storage */ + uchar* buffer; /* kNuFunnelBufSize worth of storage */ + long bufCount; /* #of bytes in buffer */ + + /* EOL conversion; if "auto", on first flush we convert to "on" or "off" */ + NuValue convertEOL; /* on/off/auto */ + NuValue convertEOLTo; /* EOL to switch to */ + NuValue convertEOLFrom; /* EOL terminator we think we found */ + Boolean lastCR; /* was last char a CR? */ + +#if 0 + ulong inCount; /* total #of bytes in the top */ + ulong outCount; /* total #of bytes out the bottom */ + + ulong outMax; /* flag an err when outCount exceeds this */ + Boolean outMaxExceeded; /* in fact, it's this flag */ +#endif + + /* update this when stuff happens */ + NuProgressData* pProgress; + + /* data goeth out here */ + NuDataSink* pDataSink; +} NuFunnel; + + +/* + * File straw definition. This is used for slurping up input data. + * + * Mostly this is an encapsulation of an input source and a progress + * updater, useful for reading uncompressed data and feeding it to a + * compressor. It doesn't make sense to show a thermometer based on + * compressed output, since we don't know how big the eventual result + * will be, so we want to do it for the input. + */ +typedef struct NuStraw { + /* update this when stuff happens */ + NuProgressData* pProgress; + + /* data cometh in here */ + NuDataSource* pDataSource; + + /* progress update fields */ + ulong lastProgress; + ulong lastDisplayed; +} NuStraw; + +/*NuError Nu_CopyStreamToStream(FILE* outfp, FILE* infp, ulong count);*/ + + +/* + * =========================================================================== + * Data source and sink abstractions + * =========================================================================== + */ + +/* + * DataSource is used when adding data to an archive. + */ + +typedef enum NuDataSourceType { + kNuDataSourceUnknown = 0, + kNuDataSourceFromFile, + kNuDataSourceFromFP, + kNuDataSourceFromBuffer +} NuDataSourceType; + +typedef struct NuDataSourceCommon { + NuDataSourceType sourceType; + NuThreadFormat threadFormat; /* is it already compressed? */ + ushort rawCrc; /* crc for already-compressed data*/ + Boolean doClose; /* close on completion? */ + ulong dataLen; /* length of data (var for buf) */ + ulong otherLen; /* uncomp len or preset buf size */ +} NuDataSourceCommon; + +union NuDataSource { + NuDataSourceType sourceType; + + NuDataSourceCommon common; + + struct { + NuDataSourceCommon common; + char* pathname; + Boolean fromRsrcFork; + + /* temp storage; only valid when processing in library */ + FILE* fp; + } fromFile; + + struct { + NuDataSourceCommon common; + FILE* fp; + long offset; /* starting offset */ + } fromFP; + + struct { + NuDataSourceCommon common; + const uchar* buffer; /* non-const if doClose=true */ + long offset; /* starting offset */ + + long curOffset; /* current offset */ + long curDataLen; /* remaining data */ + } fromBuffer; +}; + + +/* + * DataSink is used when extracting data from an archive. + */ + +typedef enum NuDataSinkType { + kNuDataSinkUnknown = 0, + kNuDataSinkToFile, + kNuDataSinkToFP, + kNuDataSinkToBuffer, + kNuDataSinkToVoid +} NuDataSinkType; + +typedef struct NuDataSinkCommon { + NuDataSinkType sinkType; + Boolean doExpand; /* expand file? */ + NuValue convertEOL; /* convert EOL? (req "expand") */ + ulong outCount; +} NuDataSinkCommon; + +union NuDataSink { + NuDataSinkType sinkType; + + NuDataSinkCommon common; + + struct { + NuDataSinkCommon common; + char* pathname; /* file to open */ + char fssep; + + /* temp storage; must be nil except when processing in library */ + FILE* fp; + } toFile; + + struct { + NuDataSinkCommon common; + FILE* fp; + } toFP; + + struct { + NuDataSinkCommon common; + uchar* buffer; + ulong bufLen; /* max amount of data "buffer" holds */ + NuError stickyErr; + } toBuffer; +}; + + +/* + * =========================================================================== + * Function prototypes + * =========================================================================== + */ + +/* + * This is a little unpleasant. This blob of stuff gets stuffed in as + * the first arguments to Nu_ReportError, so we don't have to type them + * in every time we use the function. It would've been much easier to + * use a gcc-style varargs macro, but not everybody uses gcc. + */ +#ifdef DEBUG_MSGS + #ifdef HAS__FUNCTION__ + #define _FUNCTION_ __FUNCTION__ + #else + #define _FUNCTION_ "" + #endif + + #define NU_BLOB pArchive, __FILE__, __LINE__, _FUNCTION_, false + #define NU_BLOB_DEBUG pArchive, __FILE__, __LINE__, _FUNCTION_, true + #define NU_NILBLOB NULL, __FILE__, __LINE__, _FUNCTION_, false + #define DebugShowError(err) \ + Nu_ReportError(pArchive, __FILE__, __LINE__, _FUNCTION_, \ + true, err, "(DEBUG)"); +#else + #define NU_BLOB pArchive, "", 0, "", false + #define NU_BLOB_DEBUG pArchive, "", 0, "", true + #define NU_NILBLOB NULL, "", 0, "", false + #define DebugShowError(err) ((void)0) +#endif + +/* + * The BailError macro serves two purposes. First, it's a convenient + * way to avoid typing, "if (err != kNuErrNone) goto bail;". Second, + * when the library is built with debugging enabled, it vitually gives + * us a stack trace of exiting functions. This makes it easier to debug + * problems sent in as screen dumps via e-mail. + */ +#define BailError(err) { \ + if ((err) != kNuErrNone) { \ + /* [should this be debug-only, or all the time?] */ \ + DebugShowError(err); \ + goto bail; \ + } \ + } +#define BailErrorQuiet(err) { \ + if ((err) != kNuErrNone) \ + goto bail; \ + } +#define BailNil(val) { \ + if ((val) == nil) { \ + err = kNuErrUnexpectedNil; \ + BailError(err); \ + } \ + } +#define BailAlloc(val) { \ + if ((val) == nil) { \ + err = kNuErrMalloc; \ + BailError(err); \ + } \ + } + + +/* + * Internal function prototypes and inline functions. + */ + +/* Archive.c */ +void Nu_MasterHeaderCopy(NuArchive* pArchive, NuMasterHeader* pDstHeader, + const NuMasterHeader* pSrcHeader); +NuError Nu_GetMasterHeader(NuArchive* pArchive, + const NuMasterHeader** ppMasterHeader); +NuRecordIdx Nu_GetNextRecordIdx(NuArchive* pArchive); +NuThreadIdx Nu_GetNextThreadIdx(NuArchive* pArchive); +NuError Nu_CopyWrapperToTemp(NuArchive* pArchive); +NuError Nu_UpdateWrapper(NuArchive* pArchive, FILE* fp); +NuError Nu_AdjustWrapperPadding(NuArchive* pArchive, FILE* fp); +NuError Nu_AllocCompressionBufferIFN(NuArchive* pArchive); +NuError Nu_StreamOpenRO(FILE* infp, NuArchive** ppArchive); +NuError Nu_OpenRO(const char* filename, NuArchive** ppArchive); +NuError Nu_OpenRW(const char* archivePathname, const char* tempPathname, + ulong flags, NuArchive** ppArchive); +NuError Nu_WriteMasterHeader(NuArchive* pArchive, FILE* fp, + NuMasterHeader* pMasterHeader); +NuError Nu_Close(NuArchive* pArchive); +NuError Nu_Abort(NuArchive* pArchive); +NuError Nu_RenameTempToArchive(NuArchive* pArchive); +NuError Nu_DeleteArchiveFile(NuArchive* pArchive); + +/* ArchiveIO.c */ +uchar Nu_ReadOneC(NuArchive* pArchive, FILE* fp, ushort* pCrc); +uchar Nu_ReadOne(NuArchive* pArchive, FILE* fp); +void Nu_WriteOneC(NuArchive* pArchive, FILE* fp, uchar val, ushort* pCrc); +void Nu_WriteOne(NuArchive* pArchive, FILE* fp, uchar val); +ushort Nu_ReadTwoC(NuArchive* pArchive, FILE* fp, ushort* pCrc); +ushort Nu_ReadTwo(NuArchive* pArchive, FILE* fp); +void Nu_WriteTwoC(NuArchive* pArchive, FILE* fp, ushort val, ushort* pCrc); +void Nu_WriteTwo(NuArchive* pArchive, FILE* fp, ushort val); +ulong Nu_ReadFourC(NuArchive* pArchive, FILE* fp, ushort* pCrc); +ulong Nu_ReadFour(NuArchive* pArchive, FILE* fp); +void Nu_WriteFourC(NuArchive* pArchive, FILE* fp, ulong val, ushort* pCrc); +void Nu_WriteFour(NuArchive* pArchive, FILE* fp, ulong val); +NuDateTime Nu_ReadDateTimeC(NuArchive* pArchive, FILE* fp, ushort* pCrc); +NuDateTime Nu_ReadDateTime(NuArchive* pArchive, FILE* fp, ushort* pCrc); +void Nu_WriteDateTimeC(NuArchive* pArchive, FILE* fp, NuDateTime dateTime, + ushort* pCrc); +void Nu_WriteDateTime(NuArchive* pArchive, FILE* fp, NuDateTime dateTime); +void Nu_ReadBytesC(NuArchive* pArchive, FILE* fp, void* vbuffer, long count, + ushort* pCrc); +void Nu_ReadBytes(NuArchive* pArchive, FILE* fp, void* vbuffer, long count); +void Nu_WriteBytesC(NuArchive* pArchive, FILE* fp, const void* vbuffer, + long count, ushort* pCrc); +void Nu_WriteBytes(NuArchive* pArchive, FILE* fp, const void* vbuffer, + long count); +NuError Nu_HeaderIOFailed(NuArchive* pArchive, FILE* fp); +NuError Nu_SeekArchive(NuArchive* pArchive, FILE* fp, long offset, + int ptrname); +NuError Nu_RewindArchive(NuArchive* pArchive); + +/* Compress.c */ +NuError Nu_CompressToArchive(NuArchive* pArchive, NuDataSource* pDataSource, + NuThreadID threadID, NuThreadFormat sourceFormat, + NuThreadFormat targetFormat, NuProgressData* progressData, FILE* dstFp, + NuThread* pThread); +NuError Nu_CopyPresizedToArchive(NuArchive* pArchive, + NuDataSource* pDataSource, NuThreadID threadID, FILE* dstFp, + NuThread* pThread, char** ppSavedCopy); + +/* Crc16.c */ +extern const ushort gNuCrc16Table[256]; +ushort Nu_CalcCRC16(ushort seed, const uchar* ptr, int count); +#ifdef __Crc16_c__ /* just doing "static inline" warns def-but-not-used */ + #define CRC_INLINE /**/ +#else + #define CRC_INLINE extern inline +#endif +#if defined(inline) && !defined(__Crc16_c__) /* somebody ovrd inline def? */ +ushort Nu_UpdateCRC16(uchar val, ushort crc); +#else +CRC_INLINE ushort +Nu_UpdateCRC16(uchar val, ushort crc) +{ + return (gNuCrc16Table[((crc >> 8) & 0xFF) ^ val] ^ (crc << 8)) & 0xFFFF; +} +#endif + +/* Debug.c */ +#if defined(DEBUG_MSGS) || !defined(NDEBUG) +void Nu_DebugDumpAll(NuArchive* pArchive); +void Nu_DebugDumpThread(const NuThread* pThread); +#endif + +/* Deferred.c */ +NuError Nu_ThreadModAdd_New(NuArchive* pArchive, NuThreadID threadID, + NuThreadFormat threadFormat, NuDataSource* pDataSource, + NuThreadMod** ppThreadMod); +NuError Nu_ThreadModUpdate_New(NuArchive* pArchive, NuThreadIdx threadIdx, + NuDataSource* pDataSource, NuThreadMod** ppThreadMod); +NuError Nu_ThreadModDelete_New(NuArchive* pArchive, NuThreadIdx threadIdx, + NuThreadID threadID, NuThreadMod** ppThreadMod); +void Nu_ThreadModFree(NuArchive* pArchive, NuThreadMod* pThreadMod); +NuError Nu_ThreadModAdd_FindByThreadID(const NuRecord* pRecord, + NuThreadID threadID, NuThreadMod** ppThreadMod); +void Nu_FreeThreadMods(NuArchive* pArchive, NuRecord* pRecord); +NuThreadMod* Nu_ThreadMod_FindByThreadIdx(const NuRecord* pRecord, + NuThreadIdx threadIdx); +NuError Nu_Flush(NuArchive* pArchive, long* pStatusFlags); + +/* Expand.c */ +NuError Nu_ExpandStream(NuArchive* pArchive, const NuRecord* pRecord, + const NuThread* pThread, FILE* infp, NuFunnel* pFunnel); + +/* FileIO.c */ +void Nu_SetCurrentDateTime(NuDateTime* pDateTime); +Boolean Nu_IsOlder(const NuDateTime* pWhen1, const NuDateTime* pWhen2); +NuError Nu_OpenOutputFile(NuArchive* pArchive, const NuRecord* pRecord, + const NuThread* pThread, const char* newPathname, char newFssep, + FILE** pFp); +NuError Nu_CloseOutputFile(NuArchive* pArchive, const NuRecord* pRecord, + FILE* fp, const char* pathname); +NuError Nu_OpenInputFile(NuArchive* pArchive, const char* pathname, + Boolean openRsrc, FILE** pFp); +NuError Nu_DeleteFile(const char* pathname); +NuError Nu_RenameFile(const char* fromPath, const char* toPath); +NuError Nu_FTell(FILE* fp, long* pOffset); +NuError Nu_FSeek(FILE* fp, long offset, int ptrname); +NuError Nu_FRead(FILE* fp, void* buf, size_t nbyte); +NuError Nu_FWrite(FILE* fp, const void* buf, size_t nbyte); +NuError Nu_CopyFileSection(NuArchive* pArchive, FILE* dstFp, FILE* srcFp, + long length); +NuError Nu_GetFileLength(NuArchive* pArchive, FILE* fp, long* pLength); +NuError Nu_TruncateOpenFile(FILE* fp, long length); + +/* Funnel.c */ +NuError Nu_ProgressDataInit_Compress(NuArchive* pArchive, + NuProgressData* pProgressData, const NuRecord* pRecord, + const char* origPathname); +NuError Nu_ProgressDataInit_Expand(NuArchive* pArchive, + NuProgressData* pProgressData, const NuRecord* pRecord, + const char* newPathname, char newFssep, NuValue convertEOL); +NuError Nu_SendInitialProgress(NuArchive* pArchive, + const NuProgressData* pProgress); + +NuError Nu_FunnelNew(NuArchive* pArchive, NuDataSink* pDataSink, + NuValue convertEOL, NuValue convertEOLTo, NuProgressData* pProgress, + NuFunnel** ppFunnel); +NuError Nu_FunnelFree(NuArchive* pArchive, NuFunnel* pFunnel); +/*void Nu_FunnelSetMaxOutput(NuFunnel* pFunnel, ulong maxBytes);*/ +NuError Nu_FunnelWrite(NuArchive* pArchive, NuFunnel* pFunnel, + const uchar* buffer, ulong count); +NuError Nu_FunnelFlush(NuArchive* pArchive, NuFunnel* pFunnel); +NuError Nu_ProgressDataCompressPrep(NuArchive* pArchive, NuStraw* pStraw, + NuThreadFormat threadFormat, ulong sourceLen); +NuError Nu_ProgressDataExpandPrep(NuArchive* pArchive, NuFunnel* pFunnel, + const NuThread* pThread); +NuError Nu_FunnelSetProgressState(NuFunnel* pFunnel, NuProgressState state); +NuError Nu_FunnelSendProgressUpdate(NuArchive* pArchive, NuFunnel* pFunnel); +Boolean Nu_FunnelGetDoExpand(NuFunnel* pFunnel); + +NuError Nu_StrawNew(NuArchive* pArchive, NuDataSource* pDataSource, + NuProgressData* pProgress, NuStraw** ppStraw); +NuError Nu_StrawFree(NuArchive* pArchive, NuStraw* pStraw); +NuError Nu_StrawSetProgressState(NuStraw* pStraw, NuProgressState state); +NuError Nu_StrawSendProgressUpdate(NuArchive* pArchive, NuStraw* pStraw); +NuError Nu_StrawRead(NuArchive* pArchive, NuStraw* pStraw, uchar* buffer, + long len); +NuError Nu_StrawRewind(NuArchive* pArchive, NuStraw* pStraw); + +/* Lzw.c */ +NuError Nu_CompressLZW1(NuArchive* pArchive, NuStraw* pStraw, FILE* fp, + ulong srcLen, ulong* pDstLen, ushort* pCrc); +NuError Nu_CompressLZW2(NuArchive* pArchive, NuStraw* pStraw, FILE* fp, + ulong srcLen, ulong* pDstLen, ushort* pCrc); +NuError Nu_ExpandLZW(NuArchive* pArchive, const NuRecord* pRecord, + const NuThread* pThread, FILE* infp, NuFunnel* pFunnel, ushort* pCrc); + +/* MiscUtils.c */ +extern const char* kNufxLibName; +extern NuCallback gNuGlobalErrorMessageHandler; +const char* Nu_StrError(NuError err); +void Nu_ReportError(NuArchive* pArchive, const char* file, int line, + const char* function, Boolean isDebug, NuError err, const char* format, ...) + #if defined(__GNUC__) + __attribute__ ((format(printf, 7, 8))) + #endif + ; +#ifdef USE_DMALLOC /* want file and line numbers for calls */ +# define Nu_Malloc(archive, size) malloc(size) +# define Nu_Calloc(archive, size) calloc(1, size) +# define Nu_Realloc(archive, ptr, size) realloc(ptr, size) +# define Nu_Free(archive, ptr) (ptr != nil ? free(ptr) : (void)0) +#else +void* Nu_Malloc(NuArchive* pArchive, size_t size); +void* Nu_Calloc(NuArchive* pArchive, size_t size); +void* Nu_Realloc(NuArchive* pArchive, void* ptr, size_t size); +void Nu_Free(NuArchive* pArchive, void* ptr); +#endif + +/* Record.c */ +void Nu_RecordAddThreadMod(NuRecord* pRecord, NuThreadMod* pThreadMod); +Boolean Nu_RecordIsEmpty(NuArchive* pArchive, const NuRecord* pRecord); +Boolean Nu_RecordSet_GetLoaded(const NuRecordSet* pRecordSet); +ulong Nu_RecordSet_GetNumRecords(const NuRecordSet* pRecordSet); +void Nu_RecordSet_SetNumRecords(NuRecordSet* pRecordSet, ulong val); +void Nu_RecordSet_IncNumRecords(NuRecordSet* pRecordSet); +NuRecord* Nu_RecordSet_GetListHead(const NuRecordSet* pRecordSet); +NuRecord** Nu_RecordSet_GetListHeadPtr(NuRecordSet* pRecordSet); +NuRecord* Nu_RecordSet_GetListTail(const NuRecordSet* pRecordSet); +Boolean Nu_RecordSet_IsEmpty(const NuRecordSet* pRecordSet); +NuError Nu_RecordSet_FreeAllRecords(NuArchive* pArchive, + NuRecordSet* pRecordSet); +NuError Nu_RecordSet_DeleteRecordPtr(NuArchive* pArchive, + NuRecordSet* pRecordSet, NuRecord** ppRecord); +NuError Nu_RecordSet_DeleteRecord(NuArchive* pArchive, NuRecordSet* pRecordSet, + NuRecord* pRecord); +NuError Nu_RecordSet_Clone(NuArchive* pArchive, NuRecordSet* pDstSet, + const NuRecordSet* pSrcSet); +NuError Nu_RecordSet_MoveAllRecords(NuArchive* pArchive, NuRecordSet* pDstSet, + NuRecordSet* pSrcSet); +NuError Nu_RecordSet_FindByIdx(const NuRecordSet* pRecordSet, NuRecordIdx rec, + NuRecord** ppRecord); +NuError Nu_RecordSet_FindByThreadIdx(NuRecordSet* pRecordSet, + NuThreadIdx threadIdx, NuRecord** ppRecord, NuThread** ppThread); +NuError Nu_RecordSet_ReplaceRecord(NuArchive* pArchive, NuRecordSet* pBadSet, + NuRecord* pBadRecord, NuRecordSet* pGoodSet, NuRecord** ppGoodRecord); +Boolean Nu_ShouldIgnoreBadCRC(NuArchive* pArchive, const NuRecord* pRecord, + NuError err); +NuError Nu_WriteRecordHeader(NuArchive* pArchive, NuRecord* pRecord, FILE* fp); +NuError Nu_GetTOCIfNeeded(NuArchive* pArchive); +NuError Nu_StreamContents(NuArchive* pArchive, NuCallback contentFunc); +NuError Nu_StreamExtract(NuArchive* pArchive); +NuError Nu_StreamTest(NuArchive* pArchive); +NuError Nu_Contents(NuArchive* pArchive, NuCallback contentFunc); +NuError Nu_Extract(NuArchive* pArchive); +NuError Nu_ExtractRecord(NuArchive* pArchive, NuRecordIdx recIdx); +NuError Nu_Test(NuArchive* pArchive); +NuError Nu_GetRecord(NuArchive* pArchive, NuRecordIdx recordIdx, + const NuRecord** ppRecord); +NuError Nu_GetRecordIdxByName(NuArchive* pArchive, const char* name, + NuRecordIdx* pRecordIdx); +NuError Nu_GetRecordIdxByPosition(NuArchive* pArchive, ulong position, + NuRecordIdx* pRecordIdx); +NuError Nu_FindRecordForWriteByIdx(NuArchive* pArchive, NuRecordIdx recIdx, + NuRecord** ppFoundRecord); +NuError Nu_AddFile(NuArchive* pArchive, const char* pathname, + const NuFileDetails* pFileDetails, Boolean fromRsrcFork, + NuRecordIdx* pRecordIdx); +NuError Nu_AddRecord(NuArchive* pArchive, const NuFileDetails* pFileDetails, + NuRecordIdx* pRecordIdx, NuRecord** ppRecord); +NuError Nu_Rename(NuArchive* pArchive, NuRecordIdx recIdx, + const char* pathname, char fssep); +NuError Nu_SetRecordAttr(NuArchive* pArchive, NuRecordIdx recordIdx, + const NuRecordAttr* pRecordAttr); +NuError Nu_Delete(NuArchive* pArchive); +NuError Nu_DeleteRecord(NuArchive* pArchive, NuRecordIdx rec); + +/* SourceSink.c */ +NuError Nu_DataSourceFile_New(NuThreadFormat threadFormat, Boolean doClose, + ulong otherLen, const char* pathname, Boolean isFromRsrcFork, + NuDataSource** ppDataSource); +NuError Nu_DataSourceFP_New(NuThreadFormat threadFormat, Boolean doClose, + ulong otherLen, FILE* fp, long offset, long length, + NuDataSource** ppDataSource); +NuError Nu_DataSourceBuffer_New(NuThreadFormat threadFormat, + Boolean doClose, ulong otherLen, const uchar* buffer, long offset, + long length, NuDataSource** ppDataSource); +NuDataSource* Nu_DataSourceCopy(NuDataSource* pDataSource); +NuError Nu_DataSourceFree(NuDataSource* pDataSource); +NuDataSourceType Nu_DataSourceGetType(const NuDataSource* pDataSource); +NuThreadFormat Nu_DataSourceGetThreadFormat(const NuDataSource* pDataSource); +ulong Nu_DataSourceGetDataLen(const NuDataSource* pDataSource); +ulong Nu_DataSourceGetOtherLen(const NuDataSource* pDataSource); +void Nu_DataSourceSetOtherLen(NuDataSource* pDataSource, long otherLen); +ushort Nu_DataSourceGetRawCrc(const NuDataSource* pDataSource); +void Nu_DataSourceSetRawCrc(NuDataSource* pDataSource, ushort crc); +NuError Nu_DataSourcePrepareInput(NuArchive* pArchive, + NuDataSource* pDataSource); +void Nu_DataSourceUnPrepareInput(NuArchive* pArchive, + NuDataSource* pDataSource); +const char* Nu_DataSourceFile_GetPathname(NuDataSource* pDataSource); +NuError Nu_DataSourceGetBlock(NuDataSource* pDataSource, uchar* buf, ulong len); +NuError Nu_DataSourceRewind(NuDataSource* pDataSource); +NuError Nu_DataSinkFile_New(Boolean doExpand, NuValue convertEOL, + const char* pathname, char fssep, NuDataSink** ppDataSink); +NuError Nu_DataSinkFP_New(Boolean doExpand, NuValue convertEOL, FILE* fp, + NuDataSink** ppDataSink); +NuError Nu_DataSinkBuffer_New(Boolean doExpand, NuValue convertEOL, + uchar* buffer, ulong bufLen, NuDataSink** ppDataSink); +NuError Nu_DataSinkVoid_New(Boolean doExpand, NuValue convertEOL, + NuDataSink** ppDataSink); +NuError Nu_DataSinkFree(NuDataSink* pDataSink); +NuDataSinkType Nu_DataSinkGetType(const NuDataSink* pDataSink); +Boolean Nu_DataSinkGetDoExpand(const NuDataSink* pDataSink); +NuValue Nu_DataSinkGetConvertEOL(const NuDataSink* pDataSink); +ulong Nu_DataSinkGetOutCount(const NuDataSink* pDataSink); +const char* Nu_DataSinkFile_GetPathname(const NuDataSink* pDataSink); +char Nu_DataSinkFile_GetFssep(const NuDataSink* pDataSink); +FILE* Nu_DataSinkFile_GetFP(const NuDataSink* pDataSink); +void Nu_DataSinkFile_SetFP(NuDataSink* pDataSink, FILE* fp); +void Nu_DataSinkFile_Close(NuDataSink* pDataSink); +NuError Nu_DataSinkPutBlock(NuDataSink* pDataSink, const uchar* buf, ulong len); +NuError Nu_DataSinkGetError(NuDataSink* pDataSink); + +/* Thread.c */ +#ifdef __Thread_c__ + #define THREAD_INLINE /**/ +#else + #define THREAD_INLINE extern inline +#endif +#if defined(inline) && !defined(__Thread_c__) /* somebody ovrd inline def? */ +NuThread* Nu_GetThread(const NuRecord* pRecord, int idx); +#else +THREAD_INLINE NuThread* +Nu_GetThread(const NuRecord* pRecord, int idx) +{ + if (idx >= (int)pRecord->recTotalThreads) + return nil; + else + return &pRecord->pThreads[idx]; +} +#endif +void Nu_StripHiIfAllSet(char* str); +Boolean Nu_IsPresizedThreadID(NuThreadID threadID); +Boolean Nu_IsCompressibleThreadID(NuThreadID threadID); +Boolean Nu_ThreadHasCRC(long recordVersion, NuThreadID threadID); +NuError Nu_FindThreadByIdx(const NuRecord* pRecord, NuThreadIdx thread, + NuThread** ppThread); +NuError Nu_FindThreadByID(const NuRecord* pRecord, NuThreadID threadID, + NuThread** ppThread); +void Nu_CopyThreadContents(NuThread* pDstThread, const NuThread* pSrcThread); +NuError Nu_ReadThreadHeaders(NuArchive* pArchive, NuRecord* pRecord, + ushort* pCrc); +NuError Nu_WriteThreadHeaders(NuArchive* pArchive, NuRecord* pRecord, FILE* fp, + ushort* pCrc); +NuError Nu_ComputeThreadData(NuArchive* pArchive, NuRecord* pRecord); +NuError Nu_ScanThreads(NuArchive* pArchive, NuRecord* pRecord,long numThreads); +NuError Nu_ExtractThreadBulk(NuArchive* pArchive, const NuRecord* pRecord, + const NuThread* pThread); +NuError Nu_SkipThread(NuArchive* pArchive, const NuRecord* pRecord, + const NuThread* pThread); +NuError Nu_ExtractThread(NuArchive* pArchive, NuThreadIdx threadIdx, + NuDataSink* pDataSink); +NuError Nu_OkayToAddThread(NuArchive* pArchive, const NuRecord* pRecord, + NuThreadID threadID); +NuError Nu_AddThread(NuArchive* pArchive, NuRecordIdx rec, NuThreadID threadID, + NuDataSource* pDataSource, NuThreadIdx* pThreadIdx); +NuError Nu_UpdatePresizedThread(NuArchive* pArchive, NuThreadIdx threadIdx, + NuDataSource* pDataSource, long* pMaxLen); +NuError Nu_DeleteThread(NuArchive* pArchive, NuThreadIdx threadIdx); + +/* Value.c */ +NuError Nu_GetValue(NuArchive* pArchive, NuValueID ident, NuValue* pValue); +NuError Nu_SetValue(NuArchive* pArchive, NuValueID ident, NuValue value); +NuError Nu_GetAttr(NuArchive* pArchive, NuAttrID ident, NuAttr* pAttr); +NuThreadFormat Nu_ConvertCompressValToFormat(NuArchive* pArchive, + NuValue compValue); + +/* Version.c */ +NuError Nu_GetVersion(long* pMajorVersion, long* pMinorVersion, + long* pBugVersion, const char** ppBuildDate, const char** ppBuildFlags); + +#endif /*__NufxLibPriv__*/ diff --git a/nufxlib-0/README.txt b/nufxlib-0/README.txt new file mode 100644 index 0000000..8cba4c8 --- /dev/null +++ b/nufxlib-0/README.txt @@ -0,0 +1,92 @@ +NufxLib README, updated 2000/05/18 +http://www.nulib.com/ + +See "COPYING-LIB" for distribution restrictions. + + +UNIX +==== + +Run the "configure" script. Read through "INSTALL" if you haven't used +one of these before, especially if you want to use a specific compiler +or a particular set of compiler flags. + +Run "make depend" if you have makedepend, and then type "make". This will +build the library and all of the programs in the "samples" directory. +There are some useful programs in "samples", described in a README.txt +file there. In particular, you should run samples/test-basic to verify +that things are more or less working. + +If you want to install the library and header file into standard system +locations (usually /usr/local), run "make install". To learn how to +specify different locations, read the INSTALL document. + +There are some flags in "OPT" you may want to use. The "autoconf" default +for @CFLAGS@ is "-g -O2". + +-DNDEBUG + Disable assert() calls and extra tests. This will speed things up, + but errors won't get caught until later on, making the root cause + harder to locate. + +-DDEBUG_MSGS + Enable debug messages. This increases the size of the executable, + but shouldn't affect performance. When errors occur, more output is + produced. The "debug dump" feature is enabled by this flag. + +-DDEBUG_VERBOSE + (Implicitly sets DEBUG_MSGS.) Spray lots of debugging output. + +If you want to do benchmarks, use "-O2 -DNDEBUG". For pre-v1.0 sources, +setting -DNDEBUG is otherwise discouraged. The recommended configuration +is "-g -O2 -DDEBUG_MSGS", so that verbose debug output is available when +errors occur. + +The flags are stuffed into Version.c, so the application program can +examine and display the flags that were used to build the library. + + +BeOS +==== + +This works just like the UNIX version, but certain defaults have been +changed. Running configure without arguments under BeOS is equivalent to: + + ./configure --prefix=/boot --includedir='${prefix}/develop/headers' + --libdir='${exec_prefix}/home/config/lib' --mandir='/tmp' + --bindir='${exec_prefix}/home/config/bin' + +If you're using BeOS/PPC, it will also do: + + CC=cc CFLAGS='-proc 603 -opt full' + + +Win32 +===== + +If you're using an environment that supports "configure" scripts, such as +DJGPP, follow the UNIX instructions. + +NufxLib has been tested with Microsoft Visual C++ 6.0. To build NufxLib, +start up a DOS shell and run vcvars32.bat to set your environment. Run: + nmake -f makefile.msc +to build with debugging info, or + nmake -f makefile.msc nodebug=1 +to build optimized. + +Once the library has been built, "cd samples" and run the same command there. +When it finishes, run "test-basic.exe". + + +Other Notes +=========== + +All of the source code was formatted with four-space hard tabs. + +If you want to use the library in a multithreaded application, you should +define "USE_REENTRANT_CALLS" to tell it to use reentrant versions of +certain library calls. This defines _REENTRANT, which causes Solaris to +add the appropriate goodies. (Seems to me you'd always want this on, but +for some reason Solaris makes you take an extra step, so I'm not going to +define it by default.) + diff --git a/nufxlib-0/Record.c b/nufxlib-0/Record.c new file mode 100644 index 0000000..dbb6292 --- /dev/null +++ b/nufxlib-0/Record.c @@ -0,0 +1,2744 @@ +/* + * NuFX archive manipulation library + * 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 Library General Public License, see the file COPYING.LIB. + * + * Record-level operations. + */ +#include "NufxLibPriv.h" + + +/* + * Local constants. + */ +static const uchar kNufxID[kNufxIDLen] = { 0x4e, 0xf5, 0x46, 0xd8 }; + + +/* + * =========================================================================== + * Simple NuRecord stuff + * =========================================================================== + */ + +/* + * Initialize the contents of a NuRecord. The goal here is to init the + * things that a Nu_FreeRecordContents call will check, so that we don't + * end up trying to free garbage. No need to memset() the whole thing. + */ +static NuError +Nu_InitRecordContents(NuArchive* pArchive, NuRecord* pRecord) +{ + Assert(pRecord != nil); + + DebugFill(pRecord, sizeof(*pRecord)); + + pRecord->recOptionList = nil; + pRecord->extraBytes = nil; + pRecord->recFilename = nil; + pRecord->threadFilename = nil; + pRecord->newFilename = nil; + pRecord->pThreads = nil; + pRecord->pNext = nil; + pRecord->pThreadMods = nil; + pRecord->dirtyHeader = false; + pRecord->dropRecFilename = false; + + return kNuErrNone; +} + +/* + * Allocate and initialize a new NuRecord struct. + */ +static NuError +Nu_RecordNew(NuArchive* pArchive, NuRecord** ppRecord) +{ + Assert(ppRecord != nil); + + *ppRecord = Nu_Malloc(pArchive, sizeof(**ppRecord)); + if (*ppRecord == nil) + return kNuErrMalloc; + + return Nu_InitRecordContents(pArchive, *ppRecord); +} + +/* + * Free anything allocated within a record. Doesn't try to free the record + * itself. + */ +static NuError +Nu_FreeRecordContents(NuArchive* pArchive, NuRecord* pRecord) +{ + Assert(pRecord != nil); + + Nu_Free(pArchive, pRecord->recOptionList); + Nu_Free(pArchive, pRecord->extraBytes); + Nu_Free(pArchive, pRecord->recFilename); + Nu_Free(pArchive, pRecord->threadFilename); + Nu_Free(pArchive, pRecord->newFilename); + Nu_Free(pArchive, pRecord->pThreads); + /* don't Free(pRecord->pNext)! */ + Nu_FreeThreadMods(pArchive, pRecord); + + (void) Nu_InitRecordContents(pArchive, pRecord); /* mark as freed */ + + return kNuErrNone; +} + +/* + * Free up a NuRecord struct. + */ +static NuError +Nu_RecordFree(NuArchive* pArchive, NuRecord* pRecord) +{ + if (pRecord == nil) + return kNuErrNone; + + (void) Nu_FreeRecordContents(pArchive, pRecord); + Nu_Free(pArchive, pRecord); + + return kNuErrNone; +} + +/* + * Copy a field comprised of a buffer and a length from one structure to + * another. It is assumed that the length value has already been copied. + */ +static NuError +CopySizedField(NuArchive* pArchive, void* vppDst, const void* vpSrc, uint len) +{ + NuError err = kNuErrNone; + uchar** ppDst = vppDst; + const uchar* pSrc = vpSrc; + + Assert(ppDst != nil); + + if (len) { + Assert(pSrc != nil); + *ppDst = Nu_Malloc(pArchive, len); + BailAlloc(*ppDst); + memcpy(*ppDst, pSrc, len); + } else { + Assert(pSrc == nil); + *ppDst = nil; + } + +bail: + return err; +} + +/* + * Make a copy of a record. + */ +static NuError +Nu_RecordCopy(NuArchive* pArchive, NuRecord** ppDst, const NuRecord* pSrc) +{ + NuError err; + NuRecord* pDst; + + err = Nu_RecordNew(pArchive, ppDst); + BailError(err); + + /* copy all the static fields, then copy or blank the "hairy" parts */ + pDst = *ppDst; + memcpy(pDst, pSrc, sizeof(*pSrc)); + CopySizedField(pArchive, &pDst->recOptionList, pSrc->recOptionList, + pSrc->recOptionSize); + CopySizedField(pArchive, &pDst->extraBytes, pSrc->extraBytes, + pSrc->extraCount); + CopySizedField(pArchive, &pDst->recFilename, pSrc->recFilename, + pSrc->recFilenameLength == 0 ? 0 : pSrc->recFilenameLength+1); + CopySizedField(pArchive, &pDst->threadFilename, pSrc->threadFilename, + pSrc->threadFilename == nil ? 0 : strlen(pSrc->threadFilename) +1); + CopySizedField(pArchive, &pDst->newFilename, pSrc->newFilename, + pSrc->newFilename == nil ? 0 : strlen(pSrc->newFilename) +1); + CopySizedField(pArchive, &pDst->pThreads, pSrc->pThreads, + pSrc->recTotalThreads * sizeof(*pDst->pThreads)); + + /* now figure out what the filename is supposed to point at */ + if (pSrc->filename == pSrc->threadFilename) + pDst->filename = pDst->threadFilename; + else if (pSrc->filename == pSrc->recFilename) + pDst->filename = pDst->recFilename; + else if (pSrc->filename == pSrc->newFilename) + pDst->filename = pDst->newFilename; + else + pDst->filename = pSrc->filename; /* probably static kDefault value */ + + pDst->pNext = nil; + + /* these only hold for copy from orig... may need to remove */ + Assert(pSrc->pThreadMods == nil); + Assert(!pSrc->dirtyHeader); + +bail: + return err; +} + + +/* + * Add a ThreadMod to the list in the NuRecord. + * + * In general, the order is not significant. However, if we're adding + * a bunch of "add" threadMods for control threads to a record, their + * order might be important. So, we want to add the threadMod to the + * end of the list. + * + * I'm expecting these lists to be short, so walking down them is + * acceptable. We could do simple optimizations, like only preserving + * ordering for "add" threadMods, but even that seems silly. + */ +void +Nu_RecordAddThreadMod(NuRecord* pRecord, NuThreadMod* pThreadMod) +{ + NuThreadMod* pScanThreadMod; + + Assert(pRecord != nil); + Assert(pThreadMod != nil); + + if (pRecord->pThreadMods == nil) { + pRecord->pThreadMods = pThreadMod; + } else { + pScanThreadMod = pRecord->pThreadMods; + while (pScanThreadMod->pNext != nil) + pScanThreadMod = pScanThreadMod->pNext; + + pScanThreadMod->pNext = pThreadMod; + } + + pThreadMod->pNext = nil; +} + + +/* + * Decide if a record is empty. An empty record is one that will have no + * threads after all adds and deletes are processed. + * + * You can't delete something you just added or has been updated, and you + * can't update something that has been deleted, so any "add" or "update" + * items indicate that the thread isn't empty. + * + * You can't delete a thread more than once, or delete a thread that + * doesn't exist, so all we need to do is count up the number of current + * threads, subtract the number of deletes, and return "true" if the net + * result is zero. + */ +Boolean +Nu_RecordIsEmpty(NuArchive* pArchive, const NuRecord* pRecord) +{ + const NuThreadMod* pThreadMod; + int numThreads; + + Assert(pRecord != nil); + + numThreads = pRecord->recTotalThreads; + + pThreadMod = pRecord->pThreadMods; + while (pThreadMod != nil) { + switch (pThreadMod->entry.kind) { + case kNuThreadModAdd: + case kNuThreadModUpdate: + return false; + case kNuThreadModDelete: + numThreads--; + break; + case kNuThreadModUnknown: + default: + Assert(0); + return false; + } + + pThreadMod = pThreadMod->pNext; + } + + if (numThreads > 0) + return false; + else if (numThreads == 0) + return true; + else { + Assert(0); + Nu_ReportError(NU_BLOB, kNuErrInternal, + "Thread counting failed (%d)", numThreads); + return false; + } +} + + +/* + * =========================================================================== + * NuRecordSet functions + * =========================================================================== + */ + +/* + * Trivial getters and setters + */ + +Boolean +Nu_RecordSet_GetLoaded(const NuRecordSet* pRecordSet) +{ + Assert(pRecordSet != nil); + return pRecordSet->loaded; +} + +void +Nu_RecordSet_SetLoaded(NuRecordSet* pRecordSet, Boolean val) +{ + pRecordSet->loaded = val; +} + +ulong +Nu_RecordSet_GetNumRecords(const NuRecordSet* pRecordSet) +{ + return pRecordSet->numRecords; +} + +void +Nu_RecordSet_SetNumRecords(NuRecordSet* pRecordSet, ulong val) +{ + pRecordSet->numRecords = val; +} + +void +Nu_RecordSet_IncNumRecords(NuRecordSet* pRecordSet) +{ + pRecordSet->numRecords++; +} + +NuRecord* +Nu_RecordSet_GetListHead(const NuRecordSet* pRecordSet) +{ + return pRecordSet->nuRecordHead; +} + +NuRecord** +Nu_RecordSet_GetListHeadPtr(NuRecordSet* pRecordSet) +{ + return &pRecordSet->nuRecordHead; +} + +NuRecord* +Nu_RecordSet_GetListTail(const NuRecordSet* pRecordSet) +{ + return pRecordSet->nuRecordTail; +} + + +/* + * Returns "true" if the record set has no records or hasn't ever been + * used. + */ +Boolean +Nu_RecordSet_IsEmpty(const NuRecordSet* pRecordSet) +{ + if (!pRecordSet->loaded || pRecordSet->numRecords == 0) + return true; + + return false; +} + +/* + * Free the list of records, and reset the record sets to initial state. + */ +NuError +Nu_RecordSet_FreeAllRecords(NuArchive* pArchive, NuRecordSet* pRecordSet) +{ + NuError err = kNuErrNone; + NuRecord* pRecord; + NuRecord* pNextRecord; + + if (!pRecordSet->loaded) { + Assert(pRecordSet->nuRecordHead == nil); + Assert(pRecordSet->numRecords == 0); + return kNuErrNone; + } + + DBUG(("+++ FreeAllRecords\n")); + pRecord = pRecordSet->nuRecordHead; + while (pRecord != nil) { + pNextRecord = pRecord->pNext; + + err = Nu_RecordFree(pArchive, pRecord); + BailError(err); /* don't really expect this to fail */ + + pRecord = pNextRecord; + } + + pRecordSet->nuRecordHead = pRecordSet->nuRecordTail = nil; + pRecordSet->numRecords = 0; + pRecordSet->loaded = false; + +bail: + return err; +} + + +/* + * Add a new record to the end of the list. + */ +static NuError +Nu_RecordSet_AddRecord(NuRecordSet* pRecordSet, NuRecord* pRecord) +{ + Assert(pRecordSet != nil); + Assert(pRecord != nil); + + /* if one is nil, both must be nil */ + Assert(pRecordSet->nuRecordHead == nil || pRecordSet->nuRecordTail != nil); + Assert(pRecordSet->nuRecordTail == nil || pRecordSet->nuRecordHead != nil); + + if (pRecordSet->nuRecordHead == nil) { + /* empty list */ + pRecordSet->nuRecordHead = pRecordSet->nuRecordTail = pRecord; + pRecordSet->loaded = true; + Assert(!pRecordSet->numRecords); + } else { + pRecord->pNext = nil; + pRecordSet->nuRecordTail->pNext = pRecord; + pRecordSet->nuRecordTail = pRecord; + } + + pRecordSet->numRecords++; + + return kNuErrNone; +} + + +/* + * Delete a record from the record set. Pass in a pointer to the pointer + * to the record (usually either the head pointer or another record's + * "pNext" pointer). + * + * (Should have a "heavy assert" mode where we verify that "ppRecord" + * actually has something to do with pRecordSet.) + */ +NuError +Nu_RecordSet_DeleteRecordPtr(NuArchive* pArchive, NuRecordSet* pRecordSet, + NuRecord** ppRecord) +{ + NuError err; + NuRecord* pRecord; + + Assert(pRecordSet != nil); + Assert(ppRecord != nil); + Assert(*ppRecord != nil); + + /* save a copy of the record we're freeing */ + pRecord = *ppRecord; + + /* update the pHead or pNext pointer */ + *ppRecord = (*ppRecord)->pNext; + pRecordSet->numRecords--; + + /* if we're deleting the tail, we have to find the "new" last entry */ + if (pRecord == pRecordSet->nuRecordTail) { + if (pRecordSet->nuRecordHead == nil) { + /* this was the last entry; we're done */ + pRecordSet->nuRecordTail = nil; + } else { + /* walk through the list... delete bottom-up will be slow! */ + pRecordSet->nuRecordTail = pRecordSet->nuRecordHead; + while (pRecordSet->nuRecordTail->pNext != nil) + pRecordSet->nuRecordTail = pRecordSet->nuRecordTail->pNext; + } + } + + if (pRecordSet->numRecords) + Assert(pRecordSet->nuRecordHead!=nil && pRecordSet->nuRecordTail!=nil); + else + Assert(pRecordSet->nuRecordHead==nil && pRecordSet->nuRecordTail==nil); + + err = Nu_RecordFree(pArchive, pRecord); + return err; +} + +/* + * Delete a record from the record set. + */ +NuError +Nu_RecordSet_DeleteRecord(NuArchive* pArchive, NuRecordSet* pRecordSet, + NuRecord* pRecord) +{ + NuError err; + NuRecord** ppRecord; + + ppRecord = Nu_RecordSet_GetListHeadPtr(pRecordSet); + Assert(ppRecord != nil); + Assert(*ppRecord != nil); + + /* look for the record, so we can update his neighbors */ + /* (this also ensures that the record really is in the set we think it is)*/ + while (*ppRecord) { + if (*ppRecord == pRecord) { + err = Nu_RecordSet_DeleteRecordPtr(pArchive, pRecordSet, ppRecord); + BailError(err); + goto bail; + } + + ppRecord = &((*ppRecord)->pNext); + } + + DBUG(("--- Nu_RecordSet_DeleteRecord failed\n")); + err = kNuErrNotFound; + +bail: + return err; +} + +/* + * Make a clone of a record set. This is used to create the "copy" record + * set out of the "orig" set. + */ +NuError +Nu_RecordSet_Clone(NuArchive* pArchive, NuRecordSet* pDstSet, + const NuRecordSet* pSrcSet) +{ + NuError err = kNuErrNone; + const NuRecord* pSrcRecord; + NuRecord* pDstRecord; + + Assert(pDstSet != nil); + Assert(pSrcSet != nil); + Assert(Nu_RecordSet_GetLoaded(pDstSet) == false); + Assert(Nu_RecordSet_GetLoaded(pSrcSet) == true); + + DBUG(("--- Cloning record set\n")); + + Nu_RecordSet_SetLoaded(pDstSet, true); + + /* copy each record over */ + pSrcRecord = pSrcSet->nuRecordHead; + while (pSrcRecord != nil) { + err = Nu_RecordCopy(pArchive, &pDstRecord, pSrcRecord); + BailError(err); + err = Nu_RecordSet_AddRecord(pDstSet, pDstRecord); + BailError(err); + + pSrcRecord = pSrcRecord->pNext; + } + + Assert(pDstSet->numRecords == pSrcSet->numRecords); + +bail: + if (err != kNuErrNone) { + Nu_RecordSet_FreeAllRecords(pArchive, pDstSet); + } + return err; +} + +/* + * Move all of the records from one record set to another. + * + * On completion, "pSrcSet" will be empty. + */ +NuError +Nu_RecordSet_MoveAllRecords(NuArchive* pArchive, NuRecordSet* pDstSet, + NuRecordSet* pSrcSet) +{ + NuError err = kNuErrNone; + + Assert(pDstSet != nil); + Assert(pSrcSet != nil); + + if (!Nu_RecordSet_GetNumRecords(pSrcSet)) /* nothing to do? */ + return kNuErrNone; + + if (pDstSet->nuRecordHead == nil) { + /* empty dst list */ + pDstSet->nuRecordHead = pSrcSet->nuRecordHead; + pDstSet->nuRecordTail = pSrcSet->nuRecordTail; + pDstSet->numRecords = pSrcSet->numRecords; + pDstSet->loaded = true; + } else { + /* append to dst list */ + Assert(pDstSet->loaded); + pDstSet->nuRecordTail->pNext = pSrcSet->nuRecordHead; + pDstSet->nuRecordTail = pSrcSet->nuRecordTail; + pDstSet->numRecords += pSrcSet->numRecords; + } + + /* nuke all pointers in original list */ + pSrcSet->nuRecordHead = pSrcSet->nuRecordTail = nil; + pSrcSet->numRecords = 0; + pSrcSet->loaded = false; + + return err; +} + + +/* + * Find a record in the list by index. + */ +NuError +Nu_RecordSet_FindByIdx(const NuRecordSet* pRecordSet, NuRecordIdx recIdx, + NuRecord** ppRecord) +{ + NuRecord* pRecord; + + pRecord = pRecordSet->nuRecordHead; + while (pRecord != nil) { + if (pRecord->recordIdx == recIdx) { + *ppRecord = pRecord; + return kNuErrNone; + } + + pRecord = pRecord->pNext; + } + + return kNuErrRecIdxNotFound; +} + + +/* + * Search for a specific thread in all records in the specified record set. + */ +NuError +Nu_RecordSet_FindByThreadIdx(NuRecordSet* pRecordSet, NuThreadIdx threadIdx, + NuRecord** ppRecord, NuThread** ppThread) +{ + NuError err = kNuErrThreadIdxNotFound; + NuRecord* pRecord; + + pRecord = Nu_RecordSet_GetListHead(pRecordSet); + while (pRecord != nil) { + err = Nu_FindThreadByIdx(pRecord, threadIdx, ppThread); + if (err == kNuErrNone) { + *ppRecord = pRecord; + break; + } + pRecord = pRecord->pNext; + } + + Assert(err != kNuErrNone || (*ppRecord != nil && *ppThread != nil)); + return err; +} + + +/* + * Compare two filenames pulled out of a record. + * + * Interesting issues: + * - some filesystems are case-sensitive, some aren't + * - the fssep may be different ('/', ':') for otherwise equivalent names + * - system-dependent conversions could resolve two different names to + * the same thing + * + * Some of these are out of our control. For now, I'm just doing a + * case-sensitive comparison, since the most interesting case for us is + * when the person is adding a data fork and a resource fork from the + * same file during the same operation. + * + * [ Could run both names through the pathname conversion callback first? + * Might be expensive. ] + * + * Returns an integer greater than, equal to, or less than 0, if the + * string pointed to by name1 is greater than, equal to, or less than + * the string pointed to by s2, respectively (i.e. same as strcmp). + */ +static int +Nu_CompareRecordNames(const char* name1, const char* name2) +{ + return strcmp(name1, name2); +} + + +/* + * Find a record in the list by storageName. + */ +static NuError +Nu_RecordSet_FindByName(const NuRecordSet* pRecordSet, const char* name, + NuRecord** ppRecord) +{ + NuRecord* pRecord; + + Assert(pRecordSet != nil); + Assert(pRecordSet->loaded); + Assert(name != nil); + Assert(ppRecord != nil); + + pRecord = pRecordSet->nuRecordHead; + while (pRecord != nil) { + if (Nu_CompareRecordNames(pRecord->filename, name) == 0) { + *ppRecord = pRecord; + return kNuErrNone; + } + + pRecord = pRecord->pNext; + } + + return kNuErrRecNameNotFound; +} + +/* + * Find a record in the list by storageName, starting from the end and + * searching backwards. + * + * Since we don't actually have a "prev" pointer in the record, we end + * up scanning the entire list and keeping the last match. If this + * causes a notable reduction in efficiency we'll have to fix this. + */ +static NuError +Nu_RecordSet_ReverseFindByName(const NuRecordSet* pRecordSet, const char* name, + NuRecord** ppRecord) +{ + NuRecord* pRecord; + NuRecord* pFoundRecord = nil; + + Assert(pRecordSet != nil); + Assert(pRecordSet->loaded); + Assert(name != nil); + Assert(ppRecord != nil); + + pRecord = pRecordSet->nuRecordHead; + while (pRecord != nil) { + if (Nu_CompareRecordNames(pRecord->filename, name) == 0) + pFoundRecord = pRecord; + + pRecord = pRecord->pNext; + } + + if (pFoundRecord != nil) { + *ppRecord = pFoundRecord; + return kNuErrNone; + } + return kNuErrRecNameNotFound; +} + + +/* + * We have a copy of the record in the "copy" set, but we've decided + * (perhaps because the user elected to Skip a failed add) that we'd + * rather have the original. + * + * Delete the record from the "copy" set, clone the "orig" record, and + * insert the "orig" record into the same spot in the "copy" set. + * + * "ppNewRecord" will get a pointer to the newly-created clone. + */ +NuError +Nu_RecordSet_ReplaceRecord(NuArchive* pArchive, NuRecordSet* pBadSet, + NuRecord* pBadRecord, NuRecordSet* pGoodSet, NuRecord** ppNewRecord) +{ + NuError err; + NuRecord* pGoodRecord; + NuRecord* pSiblingRecord; + NuRecord* pNewRecord = nil; + + Assert(pArchive != nil); + Assert(pBadSet != nil); + Assert(pBadRecord != nil); + Assert(pGoodSet != nil); + Assert(ppNewRecord != nil); + + /* + * Find a record in "pGoodSet" that has the same record index as + * the "bad" record. + */ + err = Nu_RecordSet_FindByIdx(pGoodSet, pBadRecord->recordIdx, + &pGoodRecord); + BailError(err); + + /* + * Clone the original. + */ + err = Nu_RecordCopy(pArchive, &pNewRecord, pGoodRecord); + BailError(err); + + /* + * Insert the new one into the "bad" record set, in the exact same + * position. + */ + pNewRecord->pNext = pBadRecord->pNext; + if (pBadSet->nuRecordTail == pBadRecord) + pBadSet->nuRecordTail = pNewRecord; + if (pBadSet->nuRecordHead == pBadRecord) + pBadSet->nuRecordHead = pNewRecord; + else { + /* find the record that points to pBadRecord */ + pSiblingRecord = pBadSet->nuRecordHead; + while (pSiblingRecord->pNext != pBadRecord && pSiblingRecord != nil) + pSiblingRecord = pSiblingRecord->pNext; + + if (pSiblingRecord == nil) { + /* looks like "pBadRecord" wasn't part of "pBadSet" after all */ + assert(0); + err = kNuErrInternal; + goto bail; + } + + pSiblingRecord->pNext = pNewRecord; + } + + err = Nu_RecordFree(pArchive, pBadRecord); + BailError(err); + + *ppNewRecord = pNewRecord; + pNewRecord = nil; /* don't free */ + +bail: + if (pNewRecord != nil) + Nu_RecordFree(pArchive, pNewRecord); + return err; +} + + +/* + * =========================================================================== + * Assorted utility functions + * =========================================================================== + */ + +/* + * Ask the user if it's okay to ignore a bad CRC. If we can't ask the + * user, return "false". + */ +Boolean +Nu_ShouldIgnoreBadCRC(NuArchive* pArchive, const NuRecord* pRecord, NuError err) +{ + NuErrorStatus errorStatus; + NuResult result; + Boolean retval = false; + + Assert(pArchive->valIgnoreCRC == false); + + if (pArchive->errorHandlerFunc != nil) { + errorStatus.operation = kNuOpTest; /* mostly accurate */ + errorStatus.err = err; + errorStatus.sysErr = 0; + errorStatus.message = nil; + errorStatus.pRecord = pRecord; + errorStatus.pathname = nil; + errorStatus.filenameSeparator = 0; + if (pRecord != nil) { + errorStatus.pathname = pRecord->filename; + errorStatus.filenameSeparator = + NuGetSepFromSysInfo(pRecord->recFileSysInfo); + } + /*errorStatus.origArchiveTouched = false;*/ + errorStatus.canAbort = true; + errorStatus.canRetry = false; + errorStatus.canIgnore = true; + errorStatus.canSkip = false; + errorStatus.canRename = false; + errorStatus.canOverwrite = false; + + result = (*pArchive->errorHandlerFunc)(pArchive, &errorStatus); + + switch (result) { + case kNuAbort: + goto bail; + case kNuIgnore: + retval = true; + goto bail; + case kNuSkip: + case kNuOverwrite: + case kNuRetry: + case kNuRename: + default: + Nu_ReportError(NU_BLOB, kNuErrSyntax, + "Wasn't expecting result %d here", result); + break; + } + } + +bail: + return retval; +} + + +/* + * Read the next NuFX record from the current offset in the archive stream. + * This includes the record header and the thread header blocks. + * + * Pass in a NuRecord structure that will hold the data we read. + */ +static NuError +Nu_ReadRecordHeader(NuArchive* pArchive, NuRecord* pRecord) +{ + NuError err = kNuErrNone; + ushort crc; + FILE* fp; + int bytesRead; + + Assert(pArchive != nil); + Assert(pRecord != nil); + Assert(pRecord->pThreads == nil); + Assert(pRecord->pNext == nil); + + fp = pArchive->archiveFp; + + pRecord->recordIdx = Nu_GetNextRecordIdx(pArchive); + + /* points to whichever filename storage we like best */ + pRecord->filename = nil; + pRecord->fileOffset = pArchive->currentOffset; + + (void) Nu_ReadBytes(pArchive, fp, pRecord->recNufxID, kNufxIDLen); + if (memcmp(kNufxID, pRecord->recNufxID, kNufxIDLen) != 0) { + err = kNuErrRecHdrNotFound; + Nu_ReportError(NU_BLOB, kNuErrNone, + "Couldn't find start of next record"); + goto bail; + } + + /* + * Read the static fields. + */ + crc = 0; + pRecord->recHeaderCRC = Nu_ReadTwo(pArchive, fp); + pRecord->recAttribCount = Nu_ReadTwoC(pArchive, fp, &crc); + pRecord->recVersionNumber = Nu_ReadTwoC(pArchive, fp, &crc); + pRecord->recTotalThreads = Nu_ReadFourC(pArchive, fp, &crc); + pRecord->recFileSysID = Nu_ReadTwoC(pArchive, fp, &crc); + pRecord->recFileSysInfo = Nu_ReadTwoC(pArchive, fp, &crc); + pRecord->recAccess = Nu_ReadFourC(pArchive, fp, &crc); + pRecord->recFileType = Nu_ReadFourC(pArchive, fp, &crc); + pRecord->recExtraType = Nu_ReadFourC(pArchive, fp, &crc); + pRecord->recStorageType = Nu_ReadTwoC(pArchive, fp, &crc); + pRecord->recCreateWhen = Nu_ReadDateTimeC(pArchive, fp, &crc); + pRecord->recModWhen = Nu_ReadDateTimeC(pArchive, fp, &crc); + pRecord->recArchiveWhen = Nu_ReadDateTimeC(pArchive, fp, &crc); + bytesRead = 56; /* 4-byte 'NuFX' plus the above */ + + /* + * Do some sanity checks before we continue. + */ + if ((err = Nu_HeaderIOFailed(pArchive, fp)) != kNuErrNone) { + Nu_ReportError(NU_BLOB, err, "Failed reading record header"); + goto bail; + } + if (pRecord->recAttribCount > kNuReasonableAttribCount) { + err = kNuErrBadRecord; + Nu_ReportError(NU_BLOB, err, "Attrib count is huge (%u)", + pRecord->recAttribCount); + goto bail; + } + if (pRecord->recVersionNumber > kNuMaxRecordVersion) { + err = kNuErrBadRecord; + Nu_ReportError(NU_BLOB, err, "Unrecognized record version number (%u)", + pRecord->recVersionNumber); + goto bail; + } + if (pRecord->recTotalThreads > kNuReasonableTotalThreads) { + err = kNuErrBadRecord; + Nu_ReportError(NU_BLOB, err, "Unreasonable number of threads (%lu)", + pRecord->recTotalThreads); + goto bail; + } + + /* + * Read the option list, if present. + */ + if (pRecord->recVersionNumber > 0) { + pRecord->recOptionSize = Nu_ReadTwoC(pArchive, fp, &crc); + bytesRead += 2; + + if (pRecord->recOptionSize + bytesRead > pRecord->recAttribCount -2) { + /* option size exceeds the total attribute area */ + err = kNuErrBadRecord; + Nu_ReportError(NU_BLOB, kNuErrBadRecord, + "Option size exceeds attribs (%u)", pRecord->recOptionSize); + goto bail; + } + + if (pRecord->recOptionSize) { + pRecord->recOptionList = Nu_Malloc(pArchive,pRecord->recOptionSize); + BailAlloc(pRecord->recOptionList); + (void) Nu_ReadBytesC(pArchive, fp, pRecord->recOptionList, + pRecord->recOptionSize, &crc); + bytesRead += pRecord->recOptionSize; + } + } else { + pRecord->recOptionSize = 0; + pRecord->recOptionList = nil; + } + + /* last two bytes are the filename len; all else is "extra" */ + pRecord->extraCount = (pRecord->recAttribCount -2) - bytesRead; + Assert(pRecord->extraCount >= 0); + + /* + * Some programs (for example, NuLib) may leave extra junk in here. This + * is allowed by the archive spec. We may want to preserve it, so we + * allocate space for it and read it if it exists. + */ + if (pRecord->extraCount) { + pRecord->extraBytes = Nu_Malloc(pArchive, pRecord->extraCount); + BailAlloc(pRecord->extraBytes); + (void) Nu_ReadBytesC(pArchive, fp, pRecord->extraBytes, + pRecord->extraCount, &crc); + bytesRead += pRecord->extraCount; + } + + /* + * Read the in-record filename if one exists (likely in v0 records only). + */ + pRecord->recFilenameLength = Nu_ReadTwoC(pArchive, fp, &crc); + bytesRead += 2; + if (pRecord->recFilenameLength > kNuReasonableFilenameLen) { + err = kNuErrBadRecord; + Nu_ReportError(NU_BLOB, kNuErrBadRecord, "Filename length is huge (%u)", + pRecord->recFilenameLength); + goto bail; + } + if (pRecord->recFilenameLength) { + pRecord->recFilename = Nu_Malloc(pArchive, pRecord->recFilenameLength +1); + BailAlloc(pRecord->recFilename); + (void) Nu_ReadBytesC(pArchive, fp, pRecord->recFilename, + pRecord->recFilenameLength, &crc); + pRecord->recFilename[pRecord->recFilenameLength] = '\0'; + + bytesRead += pRecord->recFilenameLength; + + Nu_StripHiIfAllSet(pRecord->recFilename); + + /* use the in-header one */ + pRecord->filename = pRecord->recFilename; + } + + /* + * Read the threads records. The data is included in the record header + * CRC, so we have to pass that in too. + */ + err = Nu_ReadThreadHeaders(pArchive, pRecord, &crc); + BailError(err); + + /* + * After all is said and done, did we read the file without errors, + * and does the CRC match? + */ + if ((err = Nu_HeaderIOFailed(pArchive, fp)) != kNuErrNone) { + Nu_ReportError(NU_BLOB, err, "Failed reading late record header"); + goto bail; + } + if (!pArchive->valIgnoreCRC && crc != pRecord->recHeaderCRC) { + if (!Nu_ShouldIgnoreBadCRC(pArchive, pRecord, kNuErrBadRHCRC)) { + err = kNuErrBadRHCRC; + Nu_ReportError(NU_BLOB, err, "Stored RH CRC=0x%04x, calc=0x%04x", + pRecord->recHeaderCRC, crc); + goto bail; + } + } + + /* + * Init or compute misc record fields. + */ + /* adjust "currentOffset" for the entire record header */ + pArchive->currentOffset += bytesRead; + pArchive->currentOffset += pRecord->recTotalThreads * kNuThreadHeaderSize; + + pRecord->recHeaderLength = + bytesRead + pRecord->recTotalThreads * kNuThreadHeaderSize; + + err = Nu_ComputeThreadData(pArchive, pRecord); + BailError(err); + +bail: + if (err != kNuErrNone) + (void)Nu_FreeRecordContents(pArchive, pRecord); + return err; +} + + +/* + * Update the record's storageType if it looks like it needs it, based on + * the current set of threads. + * + * The rules we follow (stopping at the first match) are: + * - If there's a disk thread, leave it alone. Disk block size issues + * should already have been resolved. If we end up copying the same + * bogus block size we were given initially, that's fine. + * - If there's a resource fork, set the storageType to 5. + * - If there's a data fork, set the storageType to 1-3. + * - If there are no data-class threads at all, set the storageType to zero. + * + * This assumes that all updates have already been processed, i.e. there's + * no lingering add or delete threadMods. This only examines the thread + * array. + * + * NOTE: for data files (types 1, 2, and 3), the actual value may not match + * up what ProDOS would use, because this doesn't test for sparseness. + */ +static void +Nu_UpdateStorageType(NuArchive* pArchive, NuRecord* pRecord) +{ + NuError err; + NuThread* pThread; + + err = Nu_FindThreadByID(pRecord, kNuThreadIDDiskImage, &pThread); + if (err == kNuErrNone) + goto bail; + + err = Nu_FindThreadByID(pRecord, kNuThreadIDRsrcFork, &pThread); + if (err == kNuErrNone) { + DBUG(("--- setting storageType to %d (was %d)\n", kNuStorageExtended, + pRecord->recStorageType)); + pRecord->recStorageType = kNuStorageExtended; + goto bail; + } + + err = Nu_FindThreadByID(pRecord, kNuThreadIDDataFork, &pThread); + if (err == kNuErrNone) { + int newType; + if (pThread->actualThreadEOF <= 512) + newType = kNuStorageSeedling; + else if (pThread->actualThreadEOF < 131072) + newType = kNuStorageSapling; + else + newType = kNuStorageTree; + DBUG(("--- setting storageType to %d (was %d)\n", newType, + pRecord->recStorageType)); + pRecord->recStorageType = newType; + goto bail; + } + + DBUG(("--- no stuff here, setting storageType to %d (was %d)\n", + kNuStorageUnknown, pRecord->recStorageType)); + pRecord->recStorageType = kNuStorageUnknown; + +bail: + return; +} + +/* + * Write the record header to the current offset of the specified file. + * This includes writing all of the thread headers. + * + * We don't "promote" records to newer versions, because that might + * require expanding and CRCing data threads. Instead, we write the + * record in a manner appropriate for the version. + * + * As a side effect, this may update the storageType to something appropriate. + * + * The position of the file pointer on exit is undefined. The position + * past the end of the record will be stored in pArchive->currentOffset. + */ +NuError +Nu_WriteRecordHeader(NuArchive* pArchive, NuRecord* pRecord, FILE* fp) +{ + NuError err = kNuErrNone; + ushort crc; + long crcOffset; + int bytesWritten; + + Assert(pArchive != nil); + Assert(pRecord != nil); + Assert(fp != nil); + + /* + * Before we get started, let's make sure the storageType makes sense + * for this record. + */ + Nu_UpdateStorageType(pArchive, pRecord); + + DBUG(("--- Writing record header (v=%d)\n", pRecord->recVersionNumber)); + + (void) Nu_WriteBytes(pArchive, fp, pRecord->recNufxID, kNufxIDLen); + err = Nu_FTell(fp, &crcOffset); + BailError(err); + + /* + * Write the static fields. + */ + crc = 0; + Nu_WriteTwo(pArchive, fp, 0); /* crc -- come back later */ + Nu_WriteTwoC(pArchive, fp, pRecord->recAttribCount, &crc); + Nu_WriteTwoC(pArchive, fp, pRecord->recVersionNumber, &crc); + Nu_WriteFourC(pArchive, fp, pRecord->recTotalThreads, &crc); + Nu_WriteTwoC(pArchive, fp, (ushort)pRecord->recFileSysID, &crc); + Nu_WriteTwoC(pArchive, fp, pRecord->recFileSysInfo, &crc); + Nu_WriteFourC(pArchive, fp, pRecord->recAccess, &crc); + Nu_WriteFourC(pArchive, fp, pRecord->recFileType, &crc); + Nu_WriteFourC(pArchive, fp, pRecord->recExtraType, &crc); + Nu_WriteTwoC(pArchive, fp, pRecord->recStorageType, &crc); + Nu_WriteDateTimeC(pArchive, fp, pRecord->recCreateWhen, &crc); + Nu_WriteDateTimeC(pArchive, fp, pRecord->recModWhen, &crc); + Nu_WriteDateTimeC(pArchive, fp, pRecord->recArchiveWhen, &crc); + bytesWritten = 56; /* 4-byte 'NuFX' plus the above */ + + if ((err = Nu_HeaderIOFailed(pArchive, fp)) != kNuErrNone) { + Nu_ReportError(NU_BLOB, err, "Failed writing record header"); + goto bail; + } + + /* + * Write the option list, if present. + */ + if (pRecord->recVersionNumber > 0) { + Nu_WriteTwoC(pArchive, fp, pRecord->recOptionSize, &crc); + bytesWritten += 2; + + if (pRecord->recOptionSize) { + Nu_WriteBytesC(pArchive, fp, pRecord->recOptionList, + pRecord->recOptionSize, &crc); + bytesWritten += pRecord->recOptionSize; + } + } + + /* + * Preserve whatever miscellaneous junk was left in here by the last guy. + * We don't know what this is or why it's here, but who knows, maybe + * it's important. + * + * Besides, if we don't, we'll have to go back and fix the attrib count. + */ + if (pRecord->extraCount) { + Nu_WriteBytesC(pArchive, fp, pRecord->extraBytes, pRecord->extraCount, + &crc); + bytesWritten += pRecord->extraCount; + } + + /* + * If the record has a filename in the header, write it, unless + * recent changes have inspired us to drop the name from the header. + * + * Records that begin with no filename will have a default one + * stuffed in, so it's possible for pRecord->filename to be set + * already even if there wasn't one in the record. (In such cases, + * we don't write a name.) + */ + if (pRecord->recFilenameLength && !pRecord->dropRecFilename) { + Nu_WriteTwoC(pArchive, fp, pRecord->recFilenameLength, &crc); + bytesWritten += 2; + Nu_WriteBytesC(pArchive, fp, pRecord->recFilename, + pRecord->recFilenameLength, &crc); + } else { + Nu_WriteTwoC(pArchive, fp, 0, &crc); + bytesWritten += 2; + } + + /* make sure we are where we thought we would be */ + if (bytesWritten != pRecord->recAttribCount) { + err = kNuErrInternal; + Nu_ReportError(NU_BLOB, kNuErrNone, + "Didn't write what was expected (%d vs %d)", + bytesWritten, pRecord->recAttribCount); + goto bail; + } + + /* write the thread headers */ + err = Nu_WriteThreadHeaders(pArchive, pRecord, fp, &crc); + BailError(err); + + /* get the current file offset, for some computations later */ + err = Nu_FTell(fp, &pArchive->currentOffset); + BailError(err); + + /* go back and fill in the CRC */ + pRecord->recHeaderCRC = crc; + err = Nu_FSeek(fp, crcOffset, SEEK_SET); + BailError(err); + Nu_WriteTwo(pArchive, fp, pRecord->recHeaderCRC); + + /* + * All okay? + */ + if ((err = Nu_HeaderIOFailed(pArchive, fp)) != kNuErrNone) { + Nu_ReportError(NU_BLOB, err, "Failed writing late record header"); + goto bail; + } + + /* + * Update values for misc record fields. + */ + pRecord->recHeaderLength = + bytesWritten + pRecord->recTotalThreads * kNuThreadHeaderSize; + + err = Nu_ComputeThreadData(pArchive, pRecord); + BailError(err); + +bail: + return err; +} + + +/* + * Prepare for a "walk" through the records. This is useful for the + * "read the TOC as you go" method of archive use. + */ +static NuError +Nu_RecordWalkPrepare(NuArchive* pArchive, NuRecord** ppRecord) +{ + NuError err = kNuErrNone; + + Assert(pArchive != nil); + Assert(ppRecord != nil); + + DBUG(("--- walk prep\n")); + + *ppRecord = nil; + + if (!pArchive->haveToc) { + /* might have tried and aborted earlier, rewind to start of records */ + err = Nu_RewindArchive(pArchive); + BailError(err); + } + +bail: + return err; +} + +/* + * Get the next record from the "orig" set in the archive. + * + * On entry, pArchive->archiveFp must point at the start of the next + * record. On exit, it will point past the end of the record (headers and + * all data) that we just read. + * + * If we have the TOC, we just pull it out of the structure. If we don't, + * we read it from the archive file, and add it to the TOC being + * constructed. + */ +static NuError +Nu_RecordWalkGetNext(NuArchive* pArchive, NuRecord** ppRecord) +{ + NuError err = kNuErrNone; + + Assert(pArchive != nil); + Assert(ppRecord != nil); + + /*DBUG(("--- walk toc=%d\n", pArchive->haveToc));*/ + + if (pArchive->haveToc) { + if (*ppRecord == nil) + *ppRecord = Nu_RecordSet_GetListHead(&pArchive->origRecordSet); + else + *ppRecord = (*ppRecord)->pNext; + } else { + *ppRecord = nil; /* so we don't try to free it on exit */ + + /* allocate and fill in a new record */ + err = Nu_RecordNew(pArchive, ppRecord); + BailError(err); + + /* read data from archive file */ + err = Nu_ReadRecordHeader(pArchive, *ppRecord); + BailError(err); + err = Nu_ScanThreads(pArchive, *ppRecord, (*ppRecord)->recTotalThreads); + BailError(err); + + DBUG(("--- Found record '%s'\n", (*ppRecord)->filename)); + + /* add to list */ + err = Nu_RecordSet_AddRecord(&pArchive->origRecordSet, *ppRecord); + BailError(err); + } + +bail: + if (err != kNuErrNone && !pArchive->haveToc) { + /* on failure, free whatever we allocated */ + Nu_RecordFree(pArchive, *ppRecord); + *ppRecord = nil; + } + return err; +} + +/* + * Finish off a successful record walk by noting that we now have a + * full table of contents. On an unsuccessful walk, blow away the TOC + * if we don't have all of it. + */ +static NuError +Nu_RecordWalkFinish(NuArchive* pArchive, NuError walkErr) +{ + if (pArchive->haveToc) + return kNuErrNone; + + if (walkErr == kNuErrNone) { + pArchive->haveToc = true; + /* mark as loaded, even if there weren't any entries (e.g. new arc) */ + Nu_RecordSet_SetLoaded(&pArchive->origRecordSet, true); + return kNuErrNone; + } else { + pArchive->haveToc = false; /* redundant */ + return Nu_RecordSet_FreeAllRecords(pArchive, &pArchive->origRecordSet); + } +} + + +/* + * If we don't have the complete record listing from the archive in + * the "orig" record set, go get it. + * + * Uses the "record walk" functions, because they're there. + */ +NuError +Nu_GetTOCIfNeeded(NuArchive* pArchive) +{ + NuError err = kNuErrNone; + NuRecord* pRecord; + ulong count; + + Assert(pArchive != nil); + + if (pArchive->haveToc) + goto bail; + + DBUG(("--- GetTOCIfNeeded\n")); + + err = Nu_RecordWalkPrepare(pArchive, &pRecord); + BailError(err); + + count = pArchive->masterHeader.mhTotalRecords; + while (count--) { + err = Nu_RecordWalkGetNext(pArchive, &pRecord); + BailError(err); + } + +bail: + (void) Nu_RecordWalkFinish(pArchive, err); + return err; +} + + + +/* + * =========================================================================== + * Streaming read-only operations + * =========================================================================== + */ + +/* + * Run through the entire archive, pulling out the header bits, skipping + * over the data bits, and calling "contentFunc" for each record. + */ +NuError +Nu_StreamContents(NuArchive* pArchive, NuCallback contentFunc) +{ + NuError err = kNuErrNone; + NuRecord tmpRecord; + NuResult result; + ulong count; + + if (contentFunc == nil) { + err = kNuErrInvalidArg; + goto bail; + } + + Nu_InitRecordContents(pArchive, &tmpRecord); + count = pArchive->masterHeader.mhTotalRecords; + + while (count--) { + err = Nu_ReadRecordHeader(pArchive, &tmpRecord); + BailError(err); + err = Nu_ScanThreads(pArchive, &tmpRecord, tmpRecord.recTotalThreads); + BailError(err); + + /*Nu_DebugDumpRecord(&tmpRecord); + printf("\n");*/ + + /* let them display the contents */ + result = (*contentFunc)(pArchive, &tmpRecord); + if (result == kNuAbort) { + err = kNuErrAborted; + goto bail; + } + + /* dispose of the entry */ + (void) Nu_FreeRecordContents(pArchive, &tmpRecord); + (void) Nu_InitRecordContents(pArchive, &tmpRecord); + } + +bail: + (void) Nu_FreeRecordContents(pArchive, &tmpRecord); + return err; +} + + +/* + * If we're trying to be compatible with ShrinkIt, and we tried to extract + * a record that had nothing in it but comments and filenames, then we need + * to create a zero-byte data file. + * + * GS/ShrinkIt v1.1 has a bug that causes it to store zero-byte data files + * (and, for that matter, zero-byte resource forks) without a thread header. + * It isn't able to extract them. This isn't so much a compatibility + * thing as it is a bug-workaround thing. + * + * The record's storage type should tell us if it was an extended file or + * a plain file, but I'm not sure if there's any value in creating a + * zero-byte resource fork. + */ +static NuError +Nu_FakeZeroExtract(NuArchive* pArchive, NuRecord* pRecord) +{ + NuError err; + NuThread fakeThread; + + Assert(pRecord != nil); + + DBUG(("--- found empty record, creating zero-byte data file\n")); + fakeThread.thThreadClass = kNuThreadClassData; + fakeThread.thThreadFormat = kNuThreadFormatUncompressed; + fakeThread.thThreadKind = 0x0000; /* assume data thread */ + fakeThread.thThreadCRC = kNuInitialThreadCRC; + fakeThread.thThreadEOF = 0; + fakeThread.thCompThreadEOF = 0; + fakeThread.actualThreadEOF = 0; + fakeThread.threadIdx = (NuThreadIdx)-1; /* shouldn't matter */ + fakeThread.fileOffset = 0; /* shouldn't matter */ + + err = Nu_ExtractThreadBulk(pArchive, pRecord, &fakeThread); + if (err == kNuErrSkipped) + err = Nu_SkipThread(pArchive, pRecord, &fakeThread); + + return err; +} + + +/* + * Run through the entire archive, extracting the contents. + */ +NuError +Nu_StreamExtract(NuArchive* pArchive) +{ + NuError err = kNuErrNone; + NuRecord tmpRecord; + Boolean hasInterestingThread; + ulong count; + long idx; + + /* reset this just to be safe */ + pArchive->lastDirCreated = nil; + + Nu_InitRecordContents(pArchive, &tmpRecord); + count = pArchive->masterHeader.mhTotalRecords; + + while (count--) { + /* + * Read the record header (which includes the thread header blocks). + */ + err = Nu_ReadRecordHeader(pArchive, &tmpRecord); + BailError(err); + + /* + * We may need to pull the filename out of a thread, but we don't + * want to blow past any data while we do it. There's no really + * good way to deal with this, so we just assume that all NuFX + * applications are nice and put the filename thread first. + */ + for (idx = 0; idx < (long)tmpRecord.recTotalThreads; idx++) { + const NuThread* pThread = Nu_GetThread(&tmpRecord, idx); + + if (NuMakeThreadID(pThread->thThreadClass, pThread->thThreadKind) + == kNuThreadIDFilename) + { + break; + } + } + /* if we have fn, read it; either way, leave idx pointing at next */ + if (idx < (long)tmpRecord.recTotalThreads) { + idx++; /* want count, not index */ + err = Nu_ScanThreads(pArchive, &tmpRecord, idx); + BailError(err); + } else + idx = 0; + if (tmpRecord.filename == nil) { + Nu_ReportError(NU_BLOB, kNuErrNone, + "Couldn't find filename in record"); + err = kNuErrBadRecord; + goto bail; + } + + /*Nu_DebugDumpRecord(&tmpRecord); + printf("\n");*/ + + hasInterestingThread = false; + + /* extract all relevant (remaining) threads */ + pArchive->lastFileCreated = nil; + for ( ; idx < (long)tmpRecord.recTotalThreads; idx++) { + const NuThread* pThread = Nu_GetThread(&tmpRecord, idx); + + if (pThread->thThreadClass == kNuThreadClassData) { + hasInterestingThread = true; + err = Nu_ExtractThreadBulk(pArchive, &tmpRecord, pThread); + if (err == kNuErrSkipped) { + err = Nu_SkipThread(pArchive, &tmpRecord, pThread); + BailError(err); + } else if (err != kNuErrNone) + goto bail; + } else { + printf("IGNORING 0x%08lx from '%s'\n", + NuMakeThreadID(pThread->thThreadClass, pThread->thThreadKind), + tmpRecord.filename); + if (NuGetThreadID(pThread) != kNuThreadIDComment && + NuGetThreadID(pThread) != kNuThreadIDFilename) + { + hasInterestingThread = true; + } + err = Nu_SkipThread(pArchive, &tmpRecord, pThread); + BailError(err); + } + } + + /* + * If we're trying to be compatible with ShrinkIt, and the record + * had nothing in it but comments and filenames, then we need to + * create a zero-byte data file. + */ + if (pArchive->valMimicSHK && !hasInterestingThread) { + err = Nu_FakeZeroExtract(pArchive, &tmpRecord); + BailError(err); + } + + /* dispose of the entry */ + (void) Nu_FreeRecordContents(pArchive, &tmpRecord); + (void) Nu_InitRecordContents(pArchive, &tmpRecord); + } + +bail: + (void) Nu_FreeRecordContents(pArchive, &tmpRecord); + return err; +} + +/* + * Test the contents of an archive. Works just like extraction, but we + * don't store anything. + */ +NuError +Nu_StreamTest(NuArchive* pArchive) +{ + NuError err; + + pArchive->testMode = true; + err = Nu_StreamExtract(pArchive); + pArchive->testMode = false; + return err; +} + + +/* + * =========================================================================== + * Non-streaming read-only operations + * =========================================================================== + */ + +/* + * Shove the archive table of contents through the callback function. + * + * This only walks through the "orig" list, so it does not reflect the + * results of un-flushed changes. + */ +NuError +Nu_Contents(NuArchive* pArchive, NuCallback contentFunc) +{ + NuError err = kNuErrNone; + NuRecord* pRecord; + NuResult result; + ulong count; + + if (contentFunc == nil) { + err = kNuErrInvalidArg; + goto bail; + } + + err = Nu_RecordWalkPrepare(pArchive, &pRecord); + BailError(err); + + count = pArchive->masterHeader.mhTotalRecords; + while (count--) { + err = Nu_RecordWalkGetNext(pArchive, &pRecord); + BailError(err); + + Assert(pRecord->filename != nil); + result = (*contentFunc)(pArchive, pRecord); + if (result == kNuAbort) { + err = kNuErrAborted; + goto bail; + } + } + +bail: + (void) Nu_RecordWalkFinish(pArchive, err); + return err; +} + + +/* + * Extract all interesting threads from a record, given a NuRecord pointer + * into the archive data structure. + * + * This assumes random access, so it can't be used in streaming mode. + */ +static NuError +Nu_ExtractRecordByPtr(NuArchive* pArchive, NuRecord* pRecord) +{ + NuError err = kNuErrNone; + Boolean hasInterestingThread; + ulong idx; + + Assert(!Nu_IsStreaming(pArchive)); /* we don't skip things we don't read */ + Assert(pRecord != nil); + + /* extract all relevant threads */ + hasInterestingThread = false; + pArchive->lastFileCreated = nil; + for (idx = 0; idx < pRecord->recTotalThreads; idx++) { + const NuThread* pThread = Nu_GetThread(pRecord, idx); + + if (pThread->thThreadClass == kNuThreadClassData) { + hasInterestingThread = true; + err = Nu_ExtractThreadBulk(pArchive, pRecord, pThread); + if (err == kNuErrSkipped) { + err = Nu_SkipThread(pArchive, pRecord, pThread); + BailError(err); + } else if (err != kNuErrNone) + goto bail; + } else { + if (NuGetThreadID(pThread) != kNuThreadIDComment && + NuGetThreadID(pThread) != kNuThreadIDFilename) + { + hasInterestingThread = true; + } + DBUG(("IGNORING 0x%08lx from '%s'\n", + NuMakeThreadID(pThread->thThreadClass, pThread->thThreadKind), + pRecord->filename)); + } + } + + /* + * If we're trying to be compatible with ShrinkIt, and the record + * had nothing in it but comments and filenames, then we need to + * create a zero-byte data file. + */ + if (pArchive->valMimicSHK && !hasInterestingThread) { + err = Nu_FakeZeroExtract(pArchive, pRecord); + BailError(err); + } + +bail: + return err; +} + + +/* + * Extract a big buncha files. + */ +NuError +Nu_Extract(NuArchive* pArchive) +{ + NuError err; + NuRecord* pRecord = nil; + ulong count; + long offset; + + /* reset this just to be safe */ + pArchive->lastDirCreated = nil; + + err = Nu_RecordWalkPrepare(pArchive, &pRecord); + BailError(err); + + count = pArchive->masterHeader.mhTotalRecords; + while (count--) { + /* read the record and threads if we don't have them yet */ + err = Nu_RecordWalkGetNext(pArchive, &pRecord); + BailError(err); + + if (!pArchive->haveToc) { + /* remember where the end of the record is */ + err = Nu_FTell(pArchive->archiveFp, &offset); + BailError(err); + } + + /* extract one or more threads */ + err = Nu_ExtractRecordByPtr(pArchive, pRecord); + BailError(err); + + if (!pArchive->haveToc) { + /* line us back up so RecordWalkGetNext can read the record hdr */ + err = Nu_FSeek(pArchive->archiveFp, offset, SEEK_SET); + BailError(err); + } + } + +bail: + (void) Nu_RecordWalkFinish(pArchive, err); + return err; +} + + +/* + * Extract a single record. + */ +NuError +Nu_ExtractRecord(NuArchive* pArchive, NuRecordIdx recIdx) +{ + NuError err; + NuRecord* pRecord; + + if (Nu_IsStreaming(pArchive)) + return kNuErrUsage; + err = Nu_GetTOCIfNeeded(pArchive); + BailError(err); + + /* find the correct record by index */ + err = Nu_RecordSet_FindByIdx(&pArchive->origRecordSet, recIdx, &pRecord); + BailError(err); + Assert(pRecord != nil); + + /* extract whatever looks promising */ + err = Nu_ExtractRecordByPtr(pArchive, pRecord); + BailError(err); + +bail: + return err; +} + + +/* + * Test the contents of an archive. Works just like extraction, but we + * don't store anything. + */ +NuError +Nu_Test(NuArchive* pArchive) +{ + NuError err; + + pArchive->testMode = true; + err = Nu_Extract(pArchive); + pArchive->testMode = false; + return err; +} + + +/* + * Return a pointer to a NuRecord. + * + * This pulls the record out of the "orig" set, so it will work even + * for records that have been deleted. It will not reflect changes + * made by previous "write" calls, not even SetRecordAttr. + */ +NuError +Nu_GetRecord(NuArchive* pArchive, NuRecordIdx recordIdx, + const NuRecord** ppRecord) +{ + NuError err; + + if (recordIdx == 0 || ppRecord == nil) + return kNuErrInvalidArg; + + if (Nu_IsStreaming(pArchive)) + return kNuErrUsage; + err = Nu_GetTOCIfNeeded(pArchive); + BailError(err); + + err = Nu_RecordSet_FindByIdx(&pArchive->origRecordSet, recordIdx, + (NuRecord**)ppRecord); + if (err == kNuErrNone) { + Assert(*ppRecord != nil); + } + /* fall through with error */ + +bail: + return err; +} + +/* + * Find the recordIdx of a record by storage name. + */ +NuError +Nu_GetRecordIdxByName(NuArchive* pArchive, const char* name, + NuRecordIdx* pRecordIdx) +{ + NuError err; + NuRecord* pRecord = nil; + + if (pRecordIdx == nil) + return kNuErrInvalidArg; + + if (Nu_IsStreaming(pArchive)) + return kNuErrUsage; + err = Nu_GetTOCIfNeeded(pArchive); + BailError(err); + + err = Nu_RecordSet_FindByName(&pArchive->origRecordSet, name, &pRecord); + if (err == kNuErrNone) { + Assert(pRecord != nil); + *pRecordIdx = pRecord->recordIdx; + } + /* fall through with error */ + +bail: + return err; +} + +/* + * Find the recordIdx of a record by zero-based position. + */ +NuError +Nu_GetRecordIdxByPosition(NuArchive* pArchive, ulong position, + NuRecordIdx* pRecordIdx) +{ + NuError err; + const NuRecord* pRecord; + + if (pRecordIdx == nil) + return kNuErrInvalidArg; + + if (Nu_IsStreaming(pArchive)) + return kNuErrUsage; + err = Nu_GetTOCIfNeeded(pArchive); + BailError(err); + + if (position >= Nu_RecordSet_GetNumRecords(&pArchive->origRecordSet)) { + err = kNuErrRecordNotFound; + goto bail; + } + + pRecord = Nu_RecordSet_GetListHead(&pArchive->origRecordSet); + while (position--) { + Assert(pRecord->pNext != nil); + pRecord = pRecord->pNext; + } + + *pRecordIdx = pRecord->recordIdx; + +bail: + return err; +} + + +/* + * =========================================================================== + * Read/write record operations (add, delete) + * =========================================================================== + */ + +/* + * Find an existing record somewhere in the archive. If the "copy" set + * exists it will be searched. If not, the "orig" set is searched, and + * if an entry is found a "copy" set will be created. + * + * The goal is to always return something from the "copy" set, which we + * could do easily by just creating the "copy" set and then searching in + * it. However, we don't want to create the "copy" set if we don't have + * to, so we search "orig" if "copy" doesn't exist yet. + * + * The record returned will always be from the "copy" set. An error result + * is returned if the record isn't found. + */ +NuError +Nu_FindRecordForWriteByIdx(NuArchive* pArchive, NuRecordIdx recIdx, + NuRecord** ppFoundRecord) +{ + NuError err; + + Assert(pArchive != nil); + Assert(ppFoundRecord != nil); + + if (Nu_RecordSet_GetLoaded(&pArchive->copyRecordSet)) { + err = Nu_RecordSet_FindByIdx(&pArchive->copyRecordSet, recIdx, + ppFoundRecord); + } else { + Assert(Nu_RecordSet_GetLoaded(&pArchive->origRecordSet)); + err = Nu_RecordSet_FindByIdx(&pArchive->origRecordSet, recIdx, + ppFoundRecord); + *ppFoundRecord = nil; /* can't delete from here */ + } + BailErrorQuiet(err); + + /* + * The record exists. If we were looking in the "orig" set, we have + * to create a "copy" set and return it from there. + */ + if (*ppFoundRecord == nil) { + err = Nu_RecordSet_Clone(pArchive, &pArchive->copyRecordSet, + &pArchive->origRecordSet); + BailError(err); + err = Nu_RecordSet_FindByIdx(&pArchive->copyRecordSet, recIdx, + ppFoundRecord); + Assert(err == kNuErrNone && *ppFoundRecord != nil); /* must succeed */ + BailError(err); + } + +bail: + return err; +} + + +/* + * Deal with the situation where we're trying to add a record with the + * same name as an existing record. The existing record can't be in the + * "new" list (that's handled differently) and can't already have been + * deleted. + * + * This will either delete the existing record or return with an error. + * + * If we decide to delete the record, and the "orig" record set was + * passed in, then the record will be deleted from the "copy" set (which + * will be created only if necessary). + */ +static NuError +Nu_HandleAddDuplicateRecord(NuArchive* pArchive, NuRecordSet* pRecordSet, + NuRecord* pRecord, const NuFileDetails* pFileDetails) +{ + NuError err = kNuErrNone; + NuErrorStatus errorStatus; + NuResult result; + + Assert(pRecordSet == &pArchive->origRecordSet || + pRecordSet == &pArchive->copyRecordSet); + Assert(pRecord != nil); + Assert(pFileDetails != nil); + Assert(pArchive->valAllowDuplicates == false); + + /* + * If "only update older" is set, check the dates. Reject the + * request if the archived file isn't older than the new file. This + * tells the application that the request was rejected, but it's + * okay for them to move on to the next file. + */ + if (pArchive->valOnlyUpdateOlder) { + if (!Nu_IsOlder(&pRecord->recModWhen, &pFileDetails->modWhen)) + return kNuErrNotNewer; + } + + /* + * The file exists when it shouldn't. Decide what to do, based + * on the options configured by the application. + * + * If they "might" allow overwrites, and they have an error-handling + * callback defined, call that to find out what they want to do + * here. Options include skipping or overwriting the record. + * + * We don't currently allow renaming of records, though I suppose we + * could. + */ + switch (pArchive->valHandleExisting) { + case kNuMaybeOverwrite: + if (pArchive->errorHandlerFunc != nil) { + errorStatus.operation = kNuOpAdd; + errorStatus.err = kNuErrRecordExists; + errorStatus.sysErr = 0; + errorStatus.message = nil; + errorStatus.pRecord = pRecord; + errorStatus.pathname = pFileDetails->storageName; + errorStatus.filenameSeparator = + NuGetSepFromSysInfo(pFileDetails->fileSysInfo); + /*errorStatus.origArchiveTouched = false;*/ + errorStatus.canAbort = true; + errorStatus.canRetry = false; + errorStatus.canIgnore = false; + errorStatus.canSkip = true; + errorStatus.canRename = false; + errorStatus.canOverwrite = true; + + result = (*pArchive->errorHandlerFunc)(pArchive, &errorStatus); + + switch (result) { + case kNuAbort: + err = kNuErrAborted; + goto bail; + case kNuSkip: + err = kNuErrSkipped; + goto bail; + case kNuOverwrite: + break; /* fall back into main code */ + case kNuRetry: + case kNuRename: + case kNuIgnore: + default: + err = kNuErrSyntax; + Nu_ReportError(NU_BLOB, err, + "Wasn't expecting result %d here", result); + goto bail; + } + } else { + /* no error handler, treat like NeverOverwrite */ + err = kNuErrSkipped; + goto bail; + } + break; + case kNuNeverOverwrite: + err = kNuErrSkipped; + goto bail; + case kNuMustOverwrite: + case kNuAlwaysOverwrite: + /* fall through to record deletion */ + break; + default: + assert(0); + err = kNuErrInternal; + goto bail; + } + + err = kNuErrNone; + + /* + * We're going to overwrite the existing record. To do this, we have + * to start by deleting it from the "copy" list. + * + * If the copy set doesn't yet exist, we have to create it and find + * the record in the new set. + */ + if (pRecordSet == &pArchive->origRecordSet) { + Assert(!Nu_RecordSet_GetLoaded(&pArchive->copyRecordSet)); + err = Nu_RecordSet_Clone(pArchive, &pArchive->copyRecordSet, + &pArchive->origRecordSet); + BailError(err); + + err = Nu_RecordSet_FindByIdx(&pArchive->copyRecordSet, + pRecord->recordIdx, &pRecord); + Assert(err == kNuErrNone && pRecord != nil); /* must succeed */ + BailError(err); + } + + DBUG(("+++ deleting record %ld\n", pRecord->recordIdx)); + err = Nu_RecordSet_DeleteRecord(pArchive,&pArchive->copyRecordSet, pRecord); + BailError(err); + +bail: + return err; +} + +/* + * Create a new record, filling in most of the blanks from "pFileDetails". + * + * The filename in pFileDetails->storageName will be remembered. If no + * filename thread is added to this record before the next Flush call, a + * filename thread will be generated from this name. + * + * This always creates a "version 3" record, regardless of what else is + * in the archive. The filename is always in a thread. + * + * On success, the NuRecordIdx of the newly-created record will be placed + * in "*pRecordIdx", and the NuThreadIdx of the filename thread will be + * placed in "*pThreadIdx". If "*ppNewRecord" is non-nil, it gets a pointer + * to the newly-created record (this isn't part of the external interface). + */ +NuError +Nu_AddRecord(NuArchive* pArchive, const NuFileDetails* pFileDetails, + NuRecordIdx* pRecordIdx, NuRecord** ppNewRecord) +{ + NuError err; + NuRecord* pNewRecord = nil; + + if (pFileDetails == nil || pFileDetails->storageName == nil || + pFileDetails->storageName[0] == '\0' || + NuGetSepFromSysInfo(pFileDetails->fileSysInfo) == 0) + /* pRecordIdx may be nil */ + /* ppNewRecord may be nil */ + { + err = kNuErrInvalidArg; + goto bail; + } + + if (Nu_IsReadOnly(pArchive)) + return kNuErrArchiveRO; + err = Nu_GetTOCIfNeeded(pArchive); + BailError(err); + + /* NuFX spec forbids leading fssep chars */ + if (pFileDetails->storageName[0] == + NuGetSepFromSysInfo(pFileDetails->fileSysInfo)) + { + err = kNuErrLeadingFssep; + goto bail; + } + + /* + * If requested, look for an existing record. Look in the "copy" + * list if we have it (so we don't complain if they've already deleted + * the record), or in the "orig" list if we don't. Look in the "new" + * list to see if it clashes with something we've just added. + * + * If this is a brand-new archive, there won't be an "orig" list + * either. + */ + if (!pArchive->valAllowDuplicates) { + NuRecordSet* pRecordSet; + NuRecord* pFoundRecord; + + pRecordSet = &pArchive->copyRecordSet; + if (!Nu_RecordSet_GetLoaded(pRecordSet)) + pRecordSet = &pArchive->origRecordSet; + Assert(Nu_RecordSet_GetLoaded(pRecordSet)); + err = Nu_RecordSet_FindByName(pRecordSet, pFileDetails->storageName, + &pFoundRecord); + if (err == kNuErrNone) { + /* handle the existing record */ + DBUG(("--- Duplicate record found (%06ld) '%s'\n", + pFoundRecord->recordIdx, pFoundRecord->filename)); + err = Nu_HandleAddDuplicateRecord(pArchive, pRecordSet, + pFoundRecord, pFileDetails); + if (err != kNuErrNone) { + /* for whatever reason, we're not replacing it */ + DBUG(("--- Returning err=%d\n", err)); + goto bail; + } + } else { + /* if we *must* replace an existing file, we fail now */ + if (pArchive->valHandleExisting == kNuMustOverwrite) { + DBUG(("+++ can't freshen nonexistent '%s'\n", + pFileDetails->storageName)); + err = kNuErrDuplicateNotFound; + goto bail; + } + } + + if (Nu_RecordSet_GetLoaded(&pArchive->newRecordSet)) { + err = Nu_RecordSet_FindByName(&pArchive->newRecordSet, + pFileDetails->storageName, &pFoundRecord); + if (err == kNuErrNone) { + /* we can't delete from the "new" list, so return an error */ + err = kNuErrRecordExists; + goto bail; + } + } + + /* clear "err" so we can continue */ + err = kNuErrNone; + } + + /* + * Prepare the new record structure. + */ + err = Nu_RecordNew(pArchive, &pNewRecord); + BailError(err); + (void) Nu_InitRecordContents(pArchive, pNewRecord); + memcpy(pNewRecord->recNufxID, kNufxID, kNufxIDLen); + /*pNewRecord->recHeaderCRC*/ + /*pNewRecord->recAttribCount*/ + pNewRecord->recVersionNumber = kNuOurRecordVersion; + pNewRecord->recTotalThreads = 0; + pNewRecord->recFileSysID = pFileDetails->fileSysID; + pNewRecord->recFileSysInfo = pFileDetails->fileSysInfo; + pNewRecord->recAccess = pFileDetails->access; + pNewRecord->recFileType = pFileDetails->fileType; + pNewRecord->recExtraType = pFileDetails->extraType; + pNewRecord->recStorageType = pFileDetails->storageType; + pNewRecord->recCreateWhen = pFileDetails->createWhen; + pNewRecord->recModWhen = pFileDetails->modWhen; + pNewRecord->recArchiveWhen = pFileDetails->archiveWhen; + pNewRecord->recOptionSize = 0; + pNewRecord->extraCount = 0; + pNewRecord->recFilenameLength = 0; + + pNewRecord->recordIdx = Nu_GetNextRecordIdx(pArchive); + pNewRecord->newFilename = strdup(pFileDetails->storageName); + pNewRecord->filename = pNewRecord->newFilename; + pNewRecord->totalCompLength = 0; + pNewRecord->fileOffset = -1; + + /* + * Add it to the "new" record set. + */ + err = Nu_RecordSet_AddRecord(&pArchive->newRecordSet, pNewRecord); + BailError(err); + + /* return values */ + if (pRecordIdx != nil) + *pRecordIdx = pNewRecord->recordIdx; + if (ppNewRecord != nil) + *ppNewRecord = pNewRecord; + +bail: + return err; +} + + +/* + * Add a new "add file" thread mod to the specified record. + * + * The caller should have already verified that there isn't another + * "add file" thread mod with the same ThreadID. + */ +static NuError +Nu_AddFileThreadMod(NuArchive* pArchive, NuRecord* pRecord, + const char* pathname, const NuFileDetails* pFileDetails, + Boolean fromRsrcFork) +{ + NuError err; + NuThreadFormat threadFormat; + NuDataSource* pDataSource = nil; + NuThreadMod* pThreadMod = nil; + + Assert(pArchive != nil); + Assert(pRecord != nil); + Assert(pathname != nil); + Assert(pFileDetails != nil); + Assert(fromRsrcFork == true || fromRsrcFork == false); + + if (Nu_IsReadOnly(pArchive)) + return kNuErrArchiveRO; + + /* decide if this should be compressed; we know source isn't */ + if (Nu_IsCompressibleThreadID(pFileDetails->threadID)) + threadFormat = Nu_ConvertCompressValToFormat(pArchive, + pArchive->valDataCompression); + else + threadFormat = kNuThreadFormatUncompressed; + + /* create a data source for this file, which is assumed uncompressed */ + err = Nu_DataSourceFile_New(kNuThreadFormatUncompressed, false, 0, + pathname, fromRsrcFork, &pDataSource); + BailError(err); + + /* create a new ThreadMod */ + err = Nu_ThreadModAdd_New(pArchive, pFileDetails->threadID, threadFormat, + pDataSource, &pThreadMod); + BailError(err); + Assert(pThreadMod != nil); + pDataSource = nil; /* don't free on exit */ + + /* add the thread mod to the record */ + Nu_RecordAddThreadMod(pRecord, pThreadMod); + pThreadMod = nil; /* don't free on exit */ + +bail: + if (pDataSource != nil) + Nu_DataSourceFree(pDataSource); + if (pThreadMod != nil) + Nu_ThreadModFree(pArchive, pThreadMod); + return err; +} + +/* + * Make note of a file to add. This goes beyond AddRecord and AddThread + * calls by searching the list of newly-added files for matching pairs + * of data and rsrc forks. This is independent of the "overwrite existing + * files" feature. The comparison is made based on storageName. + * + * "fromRsrcFork" tells us how to open the source file, not what type + * of thread the file should be stored as. + * + * If "pRecordIdx" is non-nil, it will receive the newly assigned recordID. + */ +NuError +Nu_AddFile(NuArchive* pArchive, const char* pathname, + const NuFileDetails* pFileDetails, Boolean fromRsrcFork, + NuRecordIdx* pRecordIdx) +{ + NuError err = kNuErrNone; + NuRecordIdx recordIdx = 0; + NuRecord* pRecord; + + if (pathname == nil || pFileDetails == nil || + !(fromRsrcFork == true || fromRsrcFork == false)) + { + return kNuErrInvalidArg; + } + + if (Nu_IsReadOnly(pArchive)) + return kNuErrArchiveRO; + err = Nu_GetTOCIfNeeded(pArchive); + BailError(err); + + if (pFileDetails->storageName == nil) { + err = kNuErrInvalidArg; + Nu_ReportError(NU_BLOB, err, "Must specify storageName"); + goto bail; + } + if (pFileDetails->storageName[0] == + NuGetSepFromSysInfo(pFileDetails->fileSysInfo)) + { + err = kNuErrLeadingFssep; + goto bail; + } + + DBUG(("+++ ADDING '%s' (%s) 0x%02lx 0x%04lx threadID=0x%08lx\n", pathname, + pFileDetails->storageName, pFileDetails->fileType, + pFileDetails->extraType, pFileDetails->threadID)); + + /* + * See if there's another record among the "new additions" with the + * same storageName and compatible threads. + * + * If found, add a new thread in that record. If an incompatibility + * exists (same fork already present, disk image is there, etc), either + * create a new record or return with an error. + * + * We want to search from the *end* of the "new" list, so that if + * duplicates are allowed we find the entry most likely to be paired + * up with the fork currently being added. + */ + if (Nu_RecordSet_GetLoaded(&pArchive->newRecordSet)) { + NuRecord* pNewRecord; + + err = Nu_RecordSet_ReverseFindByName(&pArchive->newRecordSet, + pFileDetails->storageName, &pNewRecord); + if (err == kNuErrNone) { + /* is it okay to add it here? */ + err = Nu_OkayToAddThread(pArchive, pNewRecord, + pFileDetails->threadID); + + if (err == kNuErrNone) { + /* okay to add it to this record */ + DBUG((" attaching to existing record %06ld\n", + pNewRecord->recordIdx)); + err = Nu_AddFileThreadMod(pArchive, pNewRecord, pathname, + pFileDetails, fromRsrcFork); + BailError(err); + recordIdx = pNewRecord->recordIdx; + goto bail; /* we're done! */ + } + + err = kNuErrNone; /* go a little farther */ + + /* + * We found a brand-new record with the same name, but we + * can't add this fork to that record. We can't delete the + * item from the "new" list, so we can ignore HandleExisting. + * If we don't allow duplicates, return an error; if we do, + * then just continue with the normal processing path. + */ + if (!pArchive->valAllowDuplicates) { + DBUG(("+++ found matching record in new list, no dups\n")); + err = kNuErrRecordExists; + goto bail; + } + + } else if (err == kNuErrRecNameNotFound) { + /* no match in "new" list, fall through to normal processing */ + err = kNuErrNone; + } else { + /* general failure */ + goto bail; + } + } + + /* + * Wasn't found, invoke Nu_AddRecord. This will search through the + * existing records, using the "allow duplicates" flag to cope with + * any matches it finds. On success, we should have a brand-new record + * to play with. + */ + err = Nu_AddRecord(pArchive, pFileDetails, &recordIdx, &pRecord); + BailError(err); + DBUG(("--- Added new record %06ld\n", recordIdx)); + + /* + * Got the record, now add a data file thread. + */ + err = Nu_AddFileThreadMod(pArchive, pRecord, pathname, pFileDetails, + fromRsrcFork); + BailError(err); + +bail: + if (err == kNuErrNone && pRecordIdx != nil) + *pRecordIdx = recordIdx; + + return err; +} + + +/* + * Rename a record. There are three situations: + * + * (1) Record has the filename in a thread, and the field has enough + * room to hold the new name. For this case we add an "update" threadMod + * with the new data. + * (2) Record has the filename in a thread, and there is not enough room + * to hold the new name. Here, we add a "delete" threadMod for the + * existing filename, and add an "add" threadMod for the new. + * (3) Record stores the filename in the header. We zero out the filename + * and add a filename thread. + * + * We don't actually check to see if the filename is changing. If you + * want to rename something to the same thing, go right ahead. (This + * provides a way for applications to "filter" records that have filenames + * in the headers.) + */ +NuError +Nu_Rename(NuArchive* pArchive, NuRecordIdx recIdx, const char* pathname, + char fssep) +{ + NuError err; + NuRecord* pRecord; + NuThread* pFilenameThread; + const NuThreadMod* pThreadMod; + NuThreadMod* pNewThreadMod = nil; + NuDataSource* pDataSource = nil; + long requiredCapacity, existingCapacity, newCapacity; + Boolean doDelete, doAdd, doUpdate; + + if (recIdx == 0 || pathname == nil || pathname[0] == '\0' || fssep == '\0') + return kNuErrInvalidArg; + + if (pathname[0] == fssep) { + err = kNuErrLeadingFssep; + Nu_ReportError(NU_BLOB, err, "rename path"); + goto bail; + } + + if (Nu_IsReadOnly(pArchive)) + return kNuErrArchiveRO; + err = Nu_GetTOCIfNeeded(pArchive); + BailError(err); + + /* find the record in the "copy" set */ + err = Nu_FindRecordForWriteByIdx(pArchive, recIdx, &pRecord); + BailError(err); + Assert(pRecord != nil); + + /* look for a filename thread */ + err = Nu_FindThreadByID(pRecord, kNuThreadIDFilename, &pFilenameThread); + + if (err != kNuErrNone) + pFilenameThread = nil; + else if (err == kNuErrNone && pRecord->pThreadMods) { + /* found a thread, check to see if it has been deleted (or modifed) */ + Assert(pFilenameThread != nil); + pThreadMod = Nu_ThreadMod_FindByThreadIdx(pRecord, + pFilenameThread->threadIdx); + if (pThreadMod != nil) { + DBUG(("--- tried to modify threadIdx %ld, which has already been\n", + pFilenameThread->threadIdx)); + err = kNuErrModThreadChange; + goto bail; + } + } + + /* + * Looks like we're okay so far. Figure out what to do. + */ + doDelete = doAdd = doUpdate = false; + newCapacity = existingCapacity = 0; + requiredCapacity = strlen(pathname); + + if (pFilenameThread != nil) { + existingCapacity = pFilenameThread->thCompThreadEOF; + if (existingCapacity >= requiredCapacity) { + doUpdate = true; + newCapacity = existingCapacity; + } else { + doDelete = doAdd = true; + /* make sure they have a few bytes of leeway */ + /*newCapacity = (requiredCapacity + kNuDefaultFilenameThreadSize) & + (~(kNuDefaultFilenameThreadSize-1));*/ + newCapacity = requiredCapacity + 8; + } + } else { + doAdd = true; + /*newCapacity = (requiredCapacity + kNuDefaultFilenameThreadSize) & + (~(kNuDefaultFilenameThreadSize-1));*/ + newCapacity = requiredCapacity + 8; + } + + Assert(doAdd || doDelete || doUpdate); + Assert(doDelete == false || doAdd == true); + + /* create a data source for the filename, if needed */ + if (doAdd || doUpdate) { + Assert(newCapacity); + err = Nu_DataSourceBuffer_New(kNuThreadFormatUncompressed, true, + newCapacity, (const uchar*)strdup(pathname), 0, + requiredCapacity /*(strlen)*/, &pDataSource); + BailError(err); + } + + if (doDelete) { + err = Nu_ThreadModDelete_New(pArchive, pFilenameThread->threadIdx, + kNuThreadIDFilename, &pNewThreadMod); + BailError(err); + Nu_RecordAddThreadMod(pRecord, pNewThreadMod); + pNewThreadMod = nil; /* successful, don't free */ + } + + if (doAdd) { + err = Nu_ThreadModAdd_New(pArchive, kNuThreadIDFilename, + kNuThreadFormatUncompressed, pDataSource, &pNewThreadMod); + BailError(err); + pDataSource = nil; /* successful, don't free */ + Nu_RecordAddThreadMod(pRecord, pNewThreadMod); + pNewThreadMod = nil; /* successful, don't free */ + } + + if (doUpdate) { + err = Nu_ThreadModUpdate_New(pArchive, pFilenameThread->threadIdx, + pDataSource, &pNewThreadMod); + BailError(err); + pDataSource = nil; /* successful, don't free */ + Nu_RecordAddThreadMod(pRecord, pNewThreadMod); + pNewThreadMod = nil; /* successful, don't free */ + } + + DBUG(("--- renaming '%s' to '%s' with delete=%d add=%d update=%d\n", + pRecord->filename, pathname, doDelete, doAdd, doUpdate)); + + /* + * Update the fssep, if necessary. (This is slightly silly -- we + * have to rewrite the record header anyway since we're changing + * threads around.) + */ + if (NuGetSepFromSysInfo(pRecord->recFileSysInfo) != fssep) { + DBUG(("--- and updating the fssep\n")); + pRecord->recFileSysInfo = NuSetSepInSysInfo(pRecord->recFileSysInfo, + fssep); + pRecord->dirtyHeader = true; + } + + /* if we had a header filename, mark it for oblivion */ + if (pFilenameThread == nil) { + DBUG(("+++ rename gonna drop the filename\n")); + pRecord->dropRecFilename = true; + } + +bail: + Nu_ThreadModFree(pArchive, pNewThreadMod); + Nu_DataSourceFree(pDataSource); + return err; +} + + +/* + * Update a record's attributes with the contents of pRecordAttr. + */ +NuError +Nu_SetRecordAttr(NuArchive* pArchive, NuRecordIdx recordIdx, + const NuRecordAttr* pRecordAttr) +{ + NuError err; + NuRecord* pRecord; + + if (pRecordAttr == nil) + return kNuErrInvalidArg; + + if (Nu_IsReadOnly(pArchive)) + return kNuErrArchiveRO; + err = Nu_GetTOCIfNeeded(pArchive); + BailError(err); + + /* pull the record out of the "copy" set */ + err = Nu_FindRecordForWriteByIdx(pArchive, recordIdx, &pRecord); + BailError(err); + + Assert(pRecord != nil); + pRecord->recFileSysID = pRecordAttr->fileSysID; + /*pRecord->recFileSysInfo = pRecordAttr->fileSysInfo;*/ + pRecord->recAccess = pRecordAttr->access; + pRecord->recFileType = pRecordAttr->fileType; + pRecord->recExtraType = pRecordAttr->extraType; + pRecord->recCreateWhen = pRecordAttr->createWhen; + pRecord->recModWhen = pRecordAttr->modWhen; + pRecord->recArchiveWhen = pRecordAttr->archiveWhen; + pRecord->dirtyHeader = true; + +bail: + return err; +} + + +/* + * Bulk-delete several records, using the selection filter callback. + */ +NuError +Nu_Delete(NuArchive* pArchive) +{ + NuError err; + NuSelectionProposal selProposal; + NuRecord* pNextRecord; + NuRecord* pRecord; + NuResult result; + + if (Nu_IsReadOnly(pArchive)) + return kNuErrArchiveRO; + err = Nu_GetTOCIfNeeded(pArchive); + BailError(err); + + /* + * If we don't yet have a copy set, make one. + */ + if (!Nu_RecordSet_GetLoaded(&pArchive->copyRecordSet)) { + err = Nu_RecordSet_Clone(pArchive, &pArchive->copyRecordSet, + &pArchive->origRecordSet); + BailError(err); + } + + /* + * Run through the copy set. This is different from most other + * operations, which run through the "orig" set. However, since + * we're not interested in allowing the user to delete things that + * have already been deleted, we might as well use this set. + */ + pNextRecord = Nu_RecordSet_GetListHead(&pArchive->copyRecordSet); + while (pNextRecord != nil) { + pRecord = pNextRecord; + pNextRecord = pRecord->pNext; + + /* + * Deletion of modified records (thread adds, deletes, or updates) + * isn't allowed. There's no point in showing the record to the + * user. + */ + if (pRecord->pThreadMods != nil) { + DBUG(("+++ Skipping delete on a modified record\n")); + continue; + } + + /* + * If a selection filter is defined, allow the user the opportunity + * to select which files will be deleted, or abort the entire + * operation. + */ + if (pArchive->selectionFilterFunc != nil) { + selProposal.pRecord = pRecord; + selProposal.pThread = pRecord->pThreads; /* doesn't matter */ + result = (*pArchive->selectionFilterFunc)(pArchive, &selProposal); + + if (result == kNuSkip) + continue; + if (result == kNuAbort) { + err = kNuErrAborted; + goto bail; + } + } + + /* + * Do we want to allow this? (Same test as for DeleteRecord.) + */ + if (pRecord->pThreadMods != nil || pRecord->dirtyHeader) { + DBUG(("--- Tried to delete a modified record\n")); + err = kNuErrModRecChange; + goto bail; + } + + err = Nu_RecordSet_DeleteRecord(pArchive, &pArchive->copyRecordSet, + pRecord); + BailError(err); + } + +bail: + return err; +} + +/* + * Delete an entire record. + */ +NuError +Nu_DeleteRecord(NuArchive* pArchive, NuRecordIdx recIdx) +{ + NuError err; + NuRecord* pRecord; + + if (Nu_IsReadOnly(pArchive)) + return kNuErrArchiveRO; + err = Nu_GetTOCIfNeeded(pArchive); + BailError(err); + + err = Nu_FindRecordForWriteByIdx(pArchive, recIdx, &pRecord); + BailError(err); + + /* + * Deletion of modified records (thread adds, deletes, or updates) isn't + * allowed. It probably wouldn't be hard to handle, but it's pointless. + * Preventing the action maintains our general semantics of disallowing + * conflicting actions on the same object. + * + * We also block it if the header is dirty (e.g. they changed the + * record's filetype). This isn't necessary for correct operation, + * but again it maintains the semantics. + */ + if (pRecord->pThreadMods != nil || pRecord->dirtyHeader) { + DBUG(("--- Tried to delete a modified record\n")); + err = kNuErrModRecChange; + goto bail; + } + + err = Nu_RecordSet_DeleteRecord(pArchive,&pArchive->copyRecordSet, pRecord); + BailError(err); + +bail: + return err; +} + diff --git a/nufxlib-0/SourceSink.c b/nufxlib-0/SourceSink.c new file mode 100644 index 0000000..172fcee --- /dev/null +++ b/nufxlib-0/SourceSink.c @@ -0,0 +1,866 @@ +/* + * NuFX archive manipulation library + * 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 Library General Public License, see the file COPYING.LIB. + * + * Implementation of DataSource and DataSink objects. + */ +#include "NufxLibPriv.h" + + + +/* + * =========================================================================== + * NuDataSource + * =========================================================================== + */ + +/* + * Allocate a new DataSource structure. + */ +static NuError +Nu_DataSourceNew(NuDataSource** ppDataSource) +{ + Assert(ppDataSource != nil); + + *ppDataSource = Nu_Malloc(nil, sizeof(**ppDataSource)); + if (*ppDataSource == nil) + return kNuErrMalloc; + + (*ppDataSource)->sourceType = kNuDataSourceUnknown; + + return kNuErrNone; +} + + +/* + * Make a copy of a DataSource. + * + * IMPORTANT: if the original had the "doClose" flag set, it will be cleared, + * but left set in the duplicate. The assumption is that the original will + * be thrown out before the duplicate. If this isn't the case, you will + * need to fix the flags on the copied data. + * + * Returns nil on error. + */ +NuDataSource* +Nu_DataSourceCopy(NuDataSource* pDataSource) +{ + NuDataSource* pNewDataSource; + + Assert(pDataSource != nil); + + if (Nu_DataSourceNew(&pNewDataSource) != kNuErrNone) + return nil; + Assert(pNewDataSource != nil); + + /* this gets most of it */ + memcpy(pNewDataSource, pDataSource, sizeof(*pNewDataSource)); + + /* copy anything we're sure to free up */ + if (pDataSource->sourceType == kNuDataSourceFromFile) { + Assert(pDataSource->fromFile.fp == nil); /* does this matter? */ + pNewDataSource->fromFile.pathname = + strdup(pDataSource->fromFile.pathname); + } + + /* don't let the original free up the resources */ + if (pDataSource->common.doClose) { + DBUG(("--- clearing doClose on source-copy of data source\n")); + pDataSource->common.doClose = false; + } + + return pNewDataSource; +} + + +/* + * Free a data source structure, and any type-specific elements. + */ +NuError +Nu_DataSourceFree(NuDataSource* pDataSource) +{ + if (pDataSource == nil) + return kNuErrNone; + + switch (pDataSource->sourceType) { + case kNuDataSourceFromFile: + Nu_Free(nil, pDataSource->fromFile.pathname); + if (pDataSource->fromFile.fp != nil) { + fclose(pDataSource->fromFile.fp); + pDataSource->fromFile.fp = nil; + } + break; + case kNuDataSourceFromFP: + if (pDataSource->common.doClose) { + fclose(pDataSource->fromFile.fp); + pDataSource->fromFile.fp = nil; + } + break; + case kNuDataSourceFromBuffer: + if (pDataSource->common.doClose) { + Nu_Free(nil, (char*)pDataSource->fromBuffer.buffer); + pDataSource->fromBuffer.buffer = nil; + } + break; + case kNuDataSourceUnknown: + break; + default: + Assert(0); + return kNuErrInternal; + } + + Nu_Free(nil, pDataSource); + return kNuErrNone; +} + + +/* + * Create a data source for an unopened file. + */ +NuError +Nu_DataSourceFile_New(NuThreadFormat threadFormat, Boolean doClose, + ulong otherLen, const char* pathname, Boolean isFromRsrcFork, + NuDataSource** ppDataSource) +{ + NuError err; + + if (!(doClose == true || doClose == false) || + pathname == nil || + !(isFromRsrcFork == true || isFromRsrcFork == false) || + ppDataSource == nil) + { + return kNuErrInvalidArg; + } + + err = Nu_DataSourceNew(ppDataSource); + BailErrorQuiet(err); + + (*ppDataSource)->common.sourceType = kNuDataSourceFromFile; + (*ppDataSource)->common.threadFormat = threadFormat; + (*ppDataSource)->common.doClose = doClose; + (*ppDataSource)->common.otherLen = otherLen; + (*ppDataSource)->fromFile.pathname = strdup(pathname); + (*ppDataSource)->fromFile.fromRsrcFork = isFromRsrcFork; + + (*ppDataSource)->common.dataLen = 0; /* to be filled in later */ + (*ppDataSource)->fromFile.fp = nil; /* to be filled in later */ + +bail: + return err; +} + + +/* + * Create a data source for an open file at a specific offset. The FILE* + * must be seekable. + */ +NuError +Nu_DataSourceFP_New(NuThreadFormat threadFormat, Boolean doClose, + ulong otherLen, FILE* fp, long offset, long length, + NuDataSource** ppDataSource) +{ + NuError err; + + if (!(doClose == true || doClose == false) || + fp == nil || offset < 0 || length < 0 || + ppDataSource == nil) + { + return kNuErrInvalidArg; + } + + if (otherLen && otherLen < (ulong)length) { + DBUG(("--- rejecting FP len=%ld other=%ld\n", length, otherLen)); + err = kNuErrPreSizeOverflow; + goto bail; + } + + err = Nu_DataSourceNew(ppDataSource); + BailErrorQuiet(err); + + (*ppDataSource)->common.sourceType = kNuDataSourceFromFP; + (*ppDataSource)->common.threadFormat = threadFormat; + (*ppDataSource)->common.doClose = doClose; + (*ppDataSource)->common.dataLen = length; + (*ppDataSource)->common.otherLen = otherLen; + (*ppDataSource)->fromFP.fp = fp; + (*ppDataSource)->fromFP.offset = offset; + +bail: + return err; +} + + +/* + * Create a data source for a buffer. + * + * We allow "buffer" to be nil so long as "offset" and "length" are also + * nil. This is useful for creating empty pre-sized buffers, such as + * blank comment fields. + */ +NuError +Nu_DataSourceBuffer_New(NuThreadFormat threadFormat, Boolean doClose, + ulong otherLen, const uchar* buffer, long offset, long length, + NuDataSource** ppDataSource) +{ + NuError err; + + if (!(doClose == true || doClose == false) || + offset < 0 || length < 0 || ppDataSource == nil) + { + return kNuErrInvalidArg; + } + if (buffer == nil && (offset != 0 || length != 0)) + { + return kNuErrInvalidArg; + } + if (buffer == nil) + doClose = false; + + if (otherLen && otherLen < (ulong)length) { + DBUG(("--- rejecting buffer len=%ld other=%ld\n", length, otherLen)); + err = kNuErrPreSizeOverflow; + goto bail; + } + + err = Nu_DataSourceNew(ppDataSource); + BailErrorQuiet(err); + + (*ppDataSource)->common.sourceType = kNuDataSourceFromBuffer; + (*ppDataSource)->common.threadFormat = threadFormat; + (*ppDataSource)->common.doClose = doClose; + (*ppDataSource)->common.dataLen = length; + (*ppDataSource)->common.otherLen = otherLen; + (*ppDataSource)->fromBuffer.buffer = buffer; + (*ppDataSource)->fromBuffer.offset = offset; + + (*ppDataSource)->fromBuffer.curOffset = offset; + (*ppDataSource)->fromBuffer.curDataLen = length; + +bail: + return err; +} + + +/* + * Get the type of a NuDataSource. + */ +NuDataSourceType +Nu_DataSourceGetType(const NuDataSource* pDataSource) +{ + Assert(pDataSource != nil); + return pDataSource->sourceType; +} + +/* + * Get the threadFormat for a data source. + */ +NuThreadFormat +Nu_DataSourceGetThreadFormat(const NuDataSource* pDataSource) +{ + Assert(pDataSource != nil); + return pDataSource->common.threadFormat; +} + +/* + * Get "dataLen" from a dataSource. + */ +ulong +Nu_DataSourceGetDataLen(const NuDataSource* pDataSource) +{ + Assert(pDataSource != nil); + + if (pDataSource->sourceType == kNuDataSourceFromFile) { + /* dataLen can only be valid if file has been opened */ + Assert(pDataSource->fromFile.fp != nil); + } + + return pDataSource->common.dataLen; +} + +/* + * Get "otherLen" from a dataSource. + */ +ulong +Nu_DataSourceGetOtherLen(const NuDataSource* pDataSource) +{ + Assert(pDataSource != nil); + return pDataSource->common.otherLen; +} + +/* + * Change the "otherLen" value. + */ +void +Nu_DataSourceSetOtherLen(NuDataSource* pDataSource, long otherLen) +{ + Assert(pDataSource != nil && otherLen > 0); + pDataSource->common.otherLen = otherLen; +} + + +/* + * Get the "raw CRC" value. + */ +ushort +Nu_DataSourceGetRawCrc(const NuDataSource* pDataSource) +{ + Assert(pDataSource != nil); + return pDataSource->common.rawCrc; +} + +/* + * Set the "raw CRC" value. You would want to do this if the input was + * already-compressed data, and you wanted to propagate the thread CRC. + */ +void +Nu_DataSourceSetRawCrc(NuDataSource* pDataSource, ushort crc) +{ + Assert(pDataSource != nil); + pDataSource->common.rawCrc = crc; +} + + +/* + * Prepare a data source for action. + */ +NuError +Nu_DataSourcePrepareInput(NuArchive* pArchive, NuDataSource* pDataSource) +{ + NuError err = kNuErrNone; + FILE* fileFp = nil; + + /* + * Doesn't apply to buffer sources. + */ + if (Nu_DataSourceGetType(pDataSource) == kNuDataSourceFromBuffer) + goto bail; + + /* + * FP sources can be used several times, so we need to seek them + * to the correct offset before we begin. + */ + if (Nu_DataSourceGetType(pDataSource) == kNuDataSourceFromFP) { + err = Nu_FSeek(pDataSource->fromFP.fp, pDataSource->fromFP.offset, + SEEK_SET); + goto bail; /* return this err */ + } + + /* + * We're adding from a file on disk. Open it. + */ + err = Nu_OpenInputFile(pArchive, + pDataSource->fromFile.pathname, pDataSource->fromFile.fromRsrcFork, + &fileFp); + BailError(err); + + Assert(fileFp != nil); + pDataSource->fromFile.fp = fileFp; + err = Nu_GetFileLength(pArchive, fileFp, + (long*)&pDataSource->common.dataLen); + BailError(err); + + if (pDataSource->common.otherLen && + pDataSource->common.otherLen < pDataSource->common.dataLen) + { + DBUG(("--- Uh oh, looks like file len is too small for presized\n")); + } + +bail: + return err; +} + + +/* + * Un-prepare a data source. This really only affects "file" sources, and + * is only here so we don't end up with 200+ FILE* structures hanging around. + * If we don't do this, the first resource we're likely to run out of is + * file descriptors. + * + * It's not necessary to do this in all error cases -- the DataSource "Free" + * call will take care of this eventually -- but for normal operation on + * a large number of files, it's vital. + */ +void +Nu_DataSourceUnPrepareInput(NuArchive* pArchive, NuDataSource* pDataSource) +{ + if (Nu_DataSourceGetType(pDataSource) != kNuDataSourceFromFile) + return; + + if (pDataSource->fromFile.fp != nil) { + fclose(pDataSource->fromFile.fp); + pDataSource->fromFile.fp = nil; + pDataSource->common.dataLen = 0; + } +} + + +/* + * Get the pathname from a "from-file" dataSource. + */ +const char* +Nu_DataSourceFile_GetPathname(NuDataSource* pDataSource) +{ + Assert(pDataSource != nil); + Assert(pDataSource->sourceType == kNuDataSourceFromFile); + Assert(pDataSource->fromFile.pathname != nil); + + return pDataSource->fromFile.pathname; +} + + +/* + * Read a block of data from a dataSource. + */ +NuError +Nu_DataSourceGetBlock(NuDataSource* pDataSource, uchar* buf, ulong len) +{ + NuError err; + + Assert(pDataSource != nil); + Assert(buf != nil); + Assert(len > 0); + + switch (pDataSource->sourceType) { + case kNuDataSourceFromFile: + Assert(pDataSource->fromFile.fp != nil); + err = Nu_FRead(pDataSource->fromFile.fp, buf, len); + if (feof(pDataSource->fromFile.fp)) + Nu_ReportError(NU_NILBLOB, err, "EOF hit unexpectedly"); + return err; + + case kNuDataSourceFromFP: + err = Nu_FRead(pDataSource->fromFP.fp, buf, len); + if (feof(pDataSource->fromFP.fp)) + Nu_ReportError(NU_NILBLOB, err, "EOF hit unexpectedly"); + return err; + + case kNuDataSourceFromBuffer: + if ((long)len > pDataSource->fromBuffer.curDataLen) { + /* buffer underrun */ + return kNuErrBufferUnderrun; + } + memcpy(buf, + pDataSource->fromBuffer.buffer + pDataSource->fromBuffer.curOffset, + len); + pDataSource->fromBuffer.curOffset += len; + pDataSource->fromBuffer.curDataLen -= len; + return kNuErrNone; + + default: + Assert(false); + return kNuErrInternal; + } +} + + +/* + * Rewind a data source to the start of its input. + */ +NuError +Nu_DataSourceRewind(NuDataSource* pDataSource) +{ + NuError err; + + Assert(pDataSource != nil); + + switch (pDataSource->sourceType) { + case kNuDataSourceFromFile: + Assert(pDataSource->fromFile.fp != nil); + err = Nu_FSeek(pDataSource->fromFile.fp, 0, SEEK_SET); + break; /* fall through with error */ + case kNuDataSourceFromFP: + err = Nu_FSeek(pDataSource->fromFP.fp, pDataSource->fromFP.offset, + SEEK_SET); + break; /* fall through with error */ + case kNuDataSourceFromBuffer: + pDataSource->fromBuffer.curOffset = pDataSource->fromBuffer.offset; + pDataSource->fromBuffer.curDataLen = pDataSource->common.dataLen; + err = kNuErrNone; + break; + default: + Assert(false); + err = kNuErrInternal; + } + + return err; +} + + +/* + * =========================================================================== + * NuDataSink + * =========================================================================== + */ + +/* + * Allocate a new DataSink structure. + */ +static NuError +Nu_DataSinkNew(NuDataSink** ppDataSink) +{ + Assert(ppDataSink != nil); + + *ppDataSink = Nu_Malloc(nil, sizeof(**ppDataSink)); + if (*ppDataSink == nil) + return kNuErrMalloc; + + (*ppDataSink)->sinkType = kNuDataSinkUnknown; + + return kNuErrNone; +} + + +/* + * Free a data sink structure, and any type-specific elements. + */ +NuError +Nu_DataSinkFree(NuDataSink* pDataSink) +{ + if (pDataSink == nil) + return kNuErrNone; + + switch (pDataSink->sinkType) { + case kNuDataSinkToFile: + Nu_DataSinkFile_Close(pDataSink); + Nu_Free(nil, pDataSink->toFile.pathname); + break; + case kNuDataSinkToFP: + break; + case kNuDataSinkToBuffer: + break; + case kNuDataSinkToVoid: + break; + case kNuDataSinkUnknown: + break; + default: + Assert(0); + return kNuErrInternal; + } + + Nu_Free(nil, pDataSink); + return kNuErrNone; +} + + +/* + * Create a data sink for an unopened file. + */ +NuError +Nu_DataSinkFile_New(Boolean doExpand, NuValue convertEOL, const char* pathname, + char fssep, NuDataSink** ppDataSink) +{ + NuError err; + + if ((doExpand != true && doExpand != false) || + (convertEOL != kNuConvertOff && convertEOL != kNuConvertOn && + convertEOL != kNuConvertAuto) || + pathname == nil || + fssep == 0 || + ppDataSink == nil) + { + return kNuErrInvalidArg; + } + + err = Nu_DataSinkNew(ppDataSink); + BailErrorQuiet(err); + + (*ppDataSink)->common.sinkType = kNuDataSinkToFile; + (*ppDataSink)->common.doExpand = doExpand; + if (doExpand) + (*ppDataSink)->common.convertEOL = convertEOL; + else + (*ppDataSink)->common.convertEOL = kNuConvertOff; + (*ppDataSink)->common.outCount = 0; + (*ppDataSink)->toFile.pathname = strdup(pathname); + (*ppDataSink)->toFile.fssep = fssep; + + (*ppDataSink)->toFile.fp = nil; + +bail: + return err; +} + + +/* + * Create a data sink based on a file pointer. + */ +NuError +Nu_DataSinkFP_New(Boolean doExpand, NuValue convertEOL, FILE* fp, + NuDataSink** ppDataSink) +{ + NuError err; + + if ((doExpand != true && doExpand != false) || + (convertEOL != kNuConvertOff && convertEOL != kNuConvertOn && + convertEOL != kNuConvertAuto) || + fp == nil || + ppDataSink == nil) + { + return kNuErrInvalidArg; + } + + err = Nu_DataSinkNew(ppDataSink); + BailErrorQuiet(err); + + (*ppDataSink)->common.sinkType = kNuDataSinkToFP; + (*ppDataSink)->common.doExpand = doExpand; + if (doExpand) + (*ppDataSink)->common.convertEOL = convertEOL; + else + (*ppDataSink)->common.convertEOL = kNuConvertOff; + (*ppDataSink)->common.outCount = 0; + (*ppDataSink)->toFP.fp = fp; + +bail: + return err; +} + + +/* + * Create a data sink for a buffer in memory. + */ +NuError +Nu_DataSinkBuffer_New(Boolean doExpand, NuValue convertEOL, uchar* buffer, + ulong bufLen, NuDataSink** ppDataSink) +{ + NuError err; + + if ((doExpand != true && doExpand != false) || + (convertEOL != kNuConvertOff && convertEOL != kNuConvertOn && + convertEOL != kNuConvertAuto) || + buffer == nil || + bufLen == 0 || + ppDataSink == nil) + { + return kNuErrInvalidArg; + } + + err = Nu_DataSinkNew(ppDataSink); + BailErrorQuiet(err); + + (*ppDataSink)->common.sinkType = kNuDataSinkToBuffer; + (*ppDataSink)->common.doExpand = doExpand; + if (doExpand) + (*ppDataSink)->common.convertEOL = convertEOL; + else + (*ppDataSink)->common.convertEOL = kNuConvertOff; + (*ppDataSink)->common.convertEOL = convertEOL; + (*ppDataSink)->common.outCount = 0; + (*ppDataSink)->toBuffer.buffer = buffer; + (*ppDataSink)->toBuffer.bufLen = bufLen; + (*ppDataSink)->toBuffer.stickyErr = kNuErrNone; + +bail: + return err; +} + + +/* + * Create a data sink that goes nowhere. + */ +NuError +Nu_DataSinkVoid_New(Boolean doExpand, NuValue convertEOL, + NuDataSink** ppDataSink) +{ + NuError err; + + Assert(doExpand == true || doExpand == false); + Assert(ppDataSink != nil); + + err = Nu_DataSinkNew(ppDataSink); + BailErrorQuiet(err); + + (*ppDataSink)->common.sinkType = kNuDataSinkToVoid; + (*ppDataSink)->common.doExpand = doExpand; + (*ppDataSink)->common.convertEOL = convertEOL; + (*ppDataSink)->common.outCount = 0; + +bail: + return err; +} + + +/* + * Get the type of a NuDataSink. + */ +NuDataSinkType +Nu_DataSinkGetType(const NuDataSink* pDataSink) +{ + Assert(pDataSink != nil); + return pDataSink->sinkType; +} + + +/* + * Return the "doExpand" parameter from any kind of sink. + */ +Boolean +Nu_DataSinkGetDoExpand(const NuDataSink* pDataSink) +{ + return pDataSink->common.doExpand; +} + +/* + * Return the "convertEOL" parameter from any kind of sink. + */ +NuValue +Nu_DataSinkGetConvertEOL(const NuDataSink* pDataSink) +{ + return pDataSink->common.convertEOL; +} + +/* + * Return the #of bytes written to the sink. + */ +ulong +Nu_DataSinkGetOutCount(const NuDataSink* pDataSink) +{ + return pDataSink->common.outCount; +} + + +/* + * Get "pathname" from a to-file sink. + */ +const char* +Nu_DataSinkFile_GetPathname(const NuDataSink* pDataSink) +{ + Assert(pDataSink != nil); + Assert(pDataSink->sinkType == kNuDataSinkToFile); + + return pDataSink->toFile.pathname; +} + +/* + * Get "fssep" from a to-file sink. + */ +char +Nu_DataSinkFile_GetFssep(const NuDataSink* pDataSink) +{ + Assert(pDataSink != nil); + Assert(pDataSink->sinkType == kNuDataSinkToFile); + + return pDataSink->toFile.fssep; +} + +/* + * Get the "fp" for a file sink. + */ +FILE* +Nu_DataSinkFile_GetFP(const NuDataSink* pDataSink) +{ + Assert(pDataSink != nil); + Assert(pDataSink->sinkType == kNuDataSinkToFile); + + return pDataSink->toFile.fp; +} + +/* + * Set the "fp" for a file sink. + */ +void +Nu_DataSinkFile_SetFP(NuDataSink* pDataSink, FILE* fp) +{ + Assert(pDataSink != nil); + Assert(pDataSink->sinkType == kNuDataSinkToFile); + + pDataSink->toFile.fp = fp; +} + +/* + * Close a to-file sink. + */ +void +Nu_DataSinkFile_Close(NuDataSink* pDataSink) +{ + Assert(pDataSink != nil); + + if (pDataSink->toFile.fp != nil) { + fclose(pDataSink->toFile.fp); + pDataSink->toFile.fp = nil; + } +} + + +/* + * Write a block of data to a DataSink. + */ +NuError +Nu_DataSinkPutBlock(NuDataSink* pDataSink, const uchar* buf, ulong len) +{ + NuError err; + + Assert(pDataSink != nil); + Assert(buf != nil); + Assert(len > 0); + + switch (pDataSink->sinkType) { + case kNuDataSinkToFile: + err = Nu_FWrite(pDataSink->toFile.fp, buf, len); + if (err != kNuErrNone) + return err; + break; + case kNuDataSinkToFP: + err = Nu_FWrite(pDataSink->toFP.fp, buf, len); + if (err != kNuErrNone) + return err; + break; + case kNuDataSinkToBuffer: + if (len > pDataSink->toBuffer.bufLen) { + /* buffer overrun; set a "sticky" error, like FILE* does */ + err = kNuErrBufferOverrun; + pDataSink->toBuffer.stickyErr = err; + return err; + } + memcpy(pDataSink->toBuffer.buffer, buf, len); + pDataSink->toBuffer.buffer += len; + pDataSink->toBuffer.bufLen -= len; + break; + case kNuDataSinkToVoid: + /* do nothing */ + break; + default: + Assert(false); + return kNuErrInternal; + } + pDataSink->common.outCount += len; + return kNuErrNone; +} + + +/* + * Figure out if one of our earlier writes has failed. + */ +NuError +Nu_DataSinkGetError(NuDataSink* pDataSink) +{ + NuError err = kNuErrNone; + + Assert(pDataSink != nil); + + switch (pDataSink->sinkType) { + case kNuDataSinkToFile: + if (ferror(pDataSink->toFile.fp)) + err = kNuErrFileWrite; + break; + case kNuDataSinkToFP: + if (ferror(pDataSink->toFP.fp)) + err = kNuErrFileWrite; + break; + case kNuDataSinkToBuffer: + err = pDataSink->toBuffer.stickyErr; + break; + case kNuDataSinkToVoid: + /* do nothing */ + break; + default: + Assert(false); + err = kNuErrInternal; + break; + } + + return err; +} + diff --git a/nufxlib-0/SunOS4.h b/nufxlib-0/SunOS4.h new file mode 100644 index 0000000..76161ea --- /dev/null +++ b/nufxlib-0/SunOS4.h @@ -0,0 +1,47 @@ +/* + * NuFX archive manipulation library + * 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 Library General Public License, see the file COPYING.LIB. + * + * This file was adapted from Devin Reade's "sunos4.h" in NuLib 3.2.5. + * It is provided for compilation under SunOS 4.x, when an ANSI compiler + * (such as gcc) is used. The system header files aren't quite sufficient + * to eliminate hordes of warnings. + */ +#ifndef __SunOS4__ +#define __SunOS4__ + +#ifdef __GNUC__ +extern int _flsbuf(int, FILE*); +extern int _filbuf(FILE*); +#endif + +extern void bcopy(char*, char*, int); +extern int fclose(FILE*); +extern int fflush(FILE*); +extern int fprintf(FILE*, const char*, ...); +extern int fread(char*, int, int, FILE *); +extern int fseek(FILE*, long, int); +extern int ftruncate(int, off_t); +extern int fwrite(const char*, int, int, FILE*); +extern char* mktemp(char *template); +extern time_t mktime(struct tm*); +extern int perror(const char*); +extern int printf(const char*, ...); +extern int remove(const char*); +extern int rename(const char*, const char*); +extern int tolower(int); +extern int setvbuf(FILE*, char*, int, int); +extern int sscanf(char*, const char*, ...); +extern int strcasecmp(const char*, const char*); +extern int strncasecmp(const char*, const char*, size_t); +extern long strtol(const char *, char **, int); +extern int system(const char*); +extern time_t timelocal(struct tm*); +extern time_t time(time_t*); +extern int toupper(int); +extern int vfprintf(FILE*, const char *, va_list); +extern char* vsprintf(char *str, const char *format, va_list ap); + +#endif /*__SunOS4__*/ diff --git a/nufxlib-0/SysDefs.h b/nufxlib-0/SysDefs.h new file mode 100644 index 0000000..35e22a2 --- /dev/null +++ b/nufxlib-0/SysDefs.h @@ -0,0 +1,138 @@ +/* + * NuFX archive manipulation library + * 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 Library General Public License, see the file COPYING.LIB. + * + * External type definitions and function prototypes. + */ +#ifndef __SysDefs__ +#define __SysDefs__ + +#ifdef HAVE_CONFIG_H +# include +#endif + +#ifdef DEBUG_VERBOSE +# define DEBUG_MSGS +#endif + +/* these should exist everywhere */ +#include +#include +#include +#include +#include +#include +#include + +/* basic Win32 stuff -- info-zip has much more complete defs */ +#if defined(WIN32) || defined(MSDOS) +# define WINDOWS_LIKE + +# ifndef HAVE_CONFIG_H +# define HAVE_FCNTL_H +# define HAVE_MALLOC_H +# define HAVE_STDLIB_H +# define HAVE_SYS_STAT_H +# undef HAVE_SYS_TIME_H +# define HAVE_SYS_TYPES_H +# undef HAVE_UNISTD_H +# undef HAVE_UTIME_H +# define HAVE_SYS_UTIME_H +# define HAVE_WINDOWS_H +# define HAVE_FDOPEN +# undef HAVE_FTRUNCATE +# define HAVE_MEMMOVE +# undef HAVE_MKSTEMP +# define HAVE_MKTIME +# define HAVE_SNPRINTF +# undef HAVE_STRCASECMP +# undef HAVE_STRNCASECMP +# define HAVE_STRERROR +# define HAVE_STRTOUL +# define HAVE_VSNPRINTF +# define SNPRINTF_DECLARED +# define VSNPRINTF_DECLARED +# define SPRINTF_RETURNS_INT +# define uchar unsigned char +# define ushort unsigned short +# define uint unsigned int +# define ulong unsigned long +# define inline /*Visual C++6.0 can't inline ".c" files*/ +# define mode_t int +# endif + +# include +# include +# define FOPEN_WANTS_B +# define HAVE_CHSIZE +# define snprintf _snprintf +# define vsnprintf _vsnprintf +#endif + +#ifdef HAVE_MALLOC_H +# include +#endif +#ifdef HAVE_STDLIB_H +# include +#endif +#ifdef HAVE_SYS_STAT_H +# include +#endif +#ifdef HAVE_SYS_TIME_H +# include +#endif +#ifdef HAVE_SYS_TYPES_H +# include +#endif +#ifdef HAVE_UNISTD_H +# include +#endif +#ifdef HAVE_UTIME_H +# include +#endif +#ifdef HAVE_SYS_UTIME_H +# include +#endif + +#if defined(WINDOWS_LIKE) +# ifndef F_OK +# define F_OK 02 +# endif +#endif + + +#if defined(__unix__) || defined(__unix) || defined(__BEOS__) || \ + defined(__hpux) || defined(_AIX) || defined(__APPLE__) +# define UNIX_LIKE /* standardize */ +#endif + +#if defined(UNIX_LIKE) +# ifdef USE_REENTRANT_CALLS +# define _REENTRANT /* Solaris 2.x convention */ +# endif +#endif + +#ifdef MAC +# define HAS_RESOURCE_FORKS +#endif + +#ifdef __ORCAC__ +# define HAS_RESOURCE_FORKS +#endif + +/* __FUNCTION__ was missing from BeOS __MWERKS__, and might be gcc-only */ +#ifdef __GNUC__ +# define HAS__FUNCTION__ +#endif + +#if defined(__sun__) && !defined(__SVR4) +# include "SunOS4.h" +#endif + +#if defined(__linux__) +# define HAS_MALLOC_CHECK_ +#endif + +#endif /*__SysDefs__*/ diff --git a/nufxlib-0/Thread.c b/nufxlib-0/Thread.c new file mode 100644 index 0000000..c764fad --- /dev/null +++ b/nufxlib-0/Thread.c @@ -0,0 +1,1278 @@ +/* + * NuFX archive manipulation library + * 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 Library General Public License, see the file COPYING.LIB. + * + * Thread-level operations. + */ +#define __Thread_c__ 1 +#include "NufxLibPriv.h" + + +/* + * =========================================================================== + * Utils + * =========================================================================== + */ + +/* + * ShrinkIt v3.0.0 had a bug where the filename thread would get created + * with the high bits set. We want to undo that without stomping on + * filenames that just happen to have a fancy character in them. If all + * of the high bits are set, assume it's a "defective" name and clear + * them all. If some aren't set, assume it's just a fancy filename. + * + * This high-bit-ism was also done for disk archives by most older versions + * of ShrinkIt. + */ +void +Nu_StripHiIfAllSet(char* str) +{ + uchar* cp; + + for (cp = (uchar*)str; *cp != '\0'; cp++) + if (!(*cp & 0x80)) + return; + + for (cp = (uchar*)str; *cp != '\0'; cp++) + *cp &= 0x7f; +} + + +/* + * Decide if a thread is pre-sized (i.e. has a fixed maximum size with a + * lesser amount of uncompressed data within) based on the threadID. + */ +Boolean +Nu_IsPresizedThreadID(NuThreadID threadID) +{ + if (threadID == kNuThreadIDFilename || threadID == kNuThreadIDComment) + return true; + else + return false; +} + + +/* + * Return an indication of whether the type of thread specified by ThreadID + * should ever be compressed. Right now, that's only data-class threads. + */ +Boolean +Nu_IsCompressibleThreadID(NuThreadID threadID) +{ + if (NuThreadIDGetClass(threadID) == kNuThreadClassData) + return true; + else + return false; +} + + +/* + * Decide if the thread has a CRC, based on the record version and the + * threadID. + */ +Boolean +Nu_ThreadHasCRC(long recordVersion, NuThreadID threadID) +{ + return recordVersion >= 3 && + NuThreadIDGetClass(threadID) == kNuThreadClassData; +} + + +/* + * Search through a given NuRecord for the specified thread. + */ +NuError +Nu_FindThreadByIdx(const NuRecord* pRecord, NuThreadIdx thread, + NuThread** ppThread) +{ + NuThread* pThread; + int idx; + + for (idx = 0; idx < (int)pRecord->recTotalThreads; idx++) { + pThread = Nu_GetThread(pRecord, idx); + Assert(pThread != nil); + + if (pThread->threadIdx == thread) { + *ppThread = pThread; + return kNuErrNone; + } + } + + return kNuErrThreadIdxNotFound; +} + + +/* + * Search through a given NuRecord for the first thread with a matching + * threadID. + */ +NuError +Nu_FindThreadByID(const NuRecord* pRecord, NuThreadID threadID, + NuThread** ppThread) +{ + NuThread* pThread; + int idx; + + for (idx = 0; idx < (int)pRecord->recTotalThreads; idx++) { + pThread = Nu_GetThread(pRecord, idx); + Assert(pThread != nil); + + if (NuGetThreadID(pThread) == threadID) { + *ppThread = pThread; + return kNuErrNone; + } + } + + return kNuErrThreadIDNotFound; +} + + +/* + * Copy the contents of a NuThread. + */ +void +Nu_CopyThreadContents(NuThread* pDstThread, const NuThread* pSrcThread) +{ + Assert(pDstThread != nil); + Assert(pSrcThread != nil); + + memcpy(pDstThread, pSrcThread, sizeof(*pDstThread)); +} + + +/* + * =========================================================================== + * Reading threads from the archive + * =========================================================================== + */ + +/* + * Read a single thread header from the archive. + */ +static NuError +Nu_ReadThreadHeader(NuArchive* pArchive, NuThread* pThread, ushort* pCrc) +{ + FILE* fp; + + Assert(pArchive != nil); + Assert(pThread != nil); + Assert(pCrc != nil); + + fp = pArchive->archiveFp; + + pThread->thThreadClass = Nu_ReadTwoC(pArchive, fp, pCrc); + pThread->thThreadFormat = Nu_ReadTwoC(pArchive, fp, pCrc); + pThread->thThreadKind = Nu_ReadTwoC(pArchive, fp, pCrc); + pThread->thThreadCRC = Nu_ReadTwoC(pArchive, fp, pCrc); + pThread->thThreadEOF = Nu_ReadFourC(pArchive, fp, pCrc); + pThread->thCompThreadEOF = Nu_ReadFourC(pArchive, fp, pCrc); + + pThread->threadIdx = Nu_GetNextThreadIdx(pArchive); + pThread->actualThreadEOF = 0; /* fix me later */ + pThread->fileOffset = -1; /* mark as invalid */ + + return Nu_HeaderIOFailed(pArchive, fp); +} + +/* + * Read the threads from the current archive file position. + * + * The storage for the threads is allocated here, in one block. We could + * have used a linked list like NuLib, but that doesn't really provide any + * benefit for us, and adds complexity. + */ +NuError +Nu_ReadThreadHeaders(NuArchive* pArchive, NuRecord* pRecord, ushort* pCrc) +{ + NuError err = kNuErrNone; + NuThread* pThread; + long count; + + Assert(pArchive != nil); + Assert(pRecord != nil); + Assert(pCrc != nil); + + if (!pRecord->recTotalThreads) { + /* not sure if this is reasonable, but we can handle it */ + DBUG(("--- WEIRD: no threads in the record?\n")); + goto bail; + } + + pRecord->pThreads = Nu_Malloc(pArchive, + pRecord->recTotalThreads * sizeof(NuThread)); + BailAlloc(pRecord->pThreads); + + count = pRecord->recTotalThreads; + pThread = pRecord->pThreads; + while (count--) { + err = Nu_ReadThreadHeader(pArchive, pThread, pCrc); + BailError(err); + + /* + * Some versions of ShrinkIt write an invalid thThreadEOF for disks, + * so we have to figure out what it's supposed to be. + */ + if (NuMakeThreadID(pThread->thThreadClass, pThread->thThreadKind) == + kNuThreadIDDiskImage) + { + if (pRecord->recStorageType <= 13) { + /* supposed to be block size, but SHK v3.0.1 stored it wrong */ + pThread->actualThreadEOF = + pRecord->recExtraType * 512; + } else { + pThread->actualThreadEOF = + pRecord->recExtraType * pRecord->recStorageType; + } + } else { + pThread->actualThreadEOF = pThread->thThreadEOF; + } + + pThread->used = false; + pThread++; + } + +bail: + return err; +} + + +/* + * Write a single thread header to the archive. + */ +static NuError +Nu_WriteThreadHeader(NuArchive* pArchive, const NuThread* pThread, FILE* fp, + ushort* pCrc) +{ + Assert(pArchive != nil); + Assert(pThread != nil); + Assert(fp != nil); + Assert(pCrc != nil); + + Nu_WriteTwoC(pArchive, fp, pThread->thThreadClass, pCrc); + Nu_WriteTwoC(pArchive, fp, (ushort)pThread->thThreadFormat, pCrc); + Nu_WriteTwoC(pArchive, fp, pThread->thThreadKind, pCrc); + Nu_WriteTwoC(pArchive, fp, pThread->thThreadCRC, pCrc); + Nu_WriteFourC(pArchive, fp, pThread->thThreadEOF, pCrc); + Nu_WriteFourC(pArchive, fp, pThread->thCompThreadEOF, pCrc); + + return Nu_HeaderIOFailed(pArchive, fp); +} + +/* + * Write the thread headers for the record at the current file position. + */ +NuError +Nu_WriteThreadHeaders(NuArchive* pArchive, NuRecord* pRecord, FILE* fp, + ushort* pCrc) +{ + NuError err = kNuErrNone; + NuThread* pThread; + int idx; + + for (idx = 0; idx < (int)pRecord->recTotalThreads; idx++) { + pThread = Nu_GetThread(pRecord, idx); + Assert(pThread != nil); + + err = Nu_WriteThreadHeader(pArchive, pThread, fp, pCrc); + BailError(err); + } + +bail: + return err; +} + + +/* + * Compute miscellaneous thread information, like total size and file + * offsets. Some values (like file offsets) will not be useful for + * streaming archives. + * + * Requires that the pArchive->currentOffset be set to the offset + * immediately after the last of the thread headers. + */ +NuError +Nu_ComputeThreadData(NuArchive* pArchive, NuRecord* pRecord) +{ + NuThread* pThread; + long fileOffset, count; + + Assert(pArchive != nil); + Assert(pRecord != nil); + + /*pRecord->totalLength = 0;*/ + pRecord->totalCompLength = 0; + + fileOffset = pArchive->currentOffset; + + count = pRecord->recTotalThreads; + pThread = pRecord->pThreads; + while (count--) { + pThread->fileOffset = fileOffset; + + /*pRecord->totalLength += pThread->thThreadEOF;*/ + pRecord->totalCompLength += pThread->thCompThreadEOF; + fileOffset += pThread->thCompThreadEOF; + + pThread++; + } + + return kNuErrNone; +} + + +/* + * Skip past some or all of the thread data in the archive. For file + * archives, we scan all the threads, but for streaming archives we only + * want to scan up to the filename thread. (If the filename thread comes + * after one of the data threads, we have a problem!) + * + * The tricky part here is that we don't want to skip over a filename + * thread. We actually want to read it in, so that we have something to + * show to the application. (Someday I'll get AndyN for putting me + * through this...) + */ +NuError +Nu_ScanThreads(NuArchive* pArchive, NuRecord* pRecord, long numThreads) +{ + NuError err = kNuErrNone; + NuThread* pThread; + FILE* fp; + + Assert(pArchive != nil); + Assert(pRecord != nil); + + fp = pArchive->archiveFp; + + Assert(numThreads <= (long)pRecord->recTotalThreads); + + pThread = pRecord->pThreads; + while (numThreads--) { + if (pRecord->threadFilename == nil && + NuMakeThreadID(pThread->thThreadClass, pThread->thThreadKind) == + kNuThreadIDFilename) + { + /* it's the first filename thread, read the whole thing */ + if (pThread->thCompThreadEOF > kNuReasonableFilenameLen) { + err = kNuErrBadRecord; + Nu_ReportError(NU_BLOB, err, "Bad thread filename len (%lu)", + pThread->thCompThreadEOF); + goto bail; + } + pRecord->threadFilename = Nu_Malloc(pArchive, + pThread->thCompThreadEOF +1); + BailAlloc(pRecord->threadFilename); + + /* note there is no CRC on a filename thread */ + (void) Nu_ReadBytes(pArchive, fp, pRecord->threadFilename, + pThread->thCompThreadEOF); + if ((err = Nu_HeaderIOFailed(pArchive, fp)) != kNuErrNone) { + Nu_ReportError(NU_BLOB, err, "Failed reading filename thread"); + goto bail; + } + + /* null-terminate on the actual len, not the buffer len */ + pRecord->threadFilename[pThread->thThreadEOF] = '\0'; + + Nu_StripHiIfAllSet(pRecord->threadFilename); + + /* prefer this one over the record one, but only one should exist */ + if (pRecord->filename != nil) { + DBUG(("--- HEY: got record filename and thread filename\n")); + } + pRecord->filename = pRecord->threadFilename; + + } else { + /* not a filename (or not first filename), skip past it */ + err = Nu_SeekArchive(pArchive, pArchive->archiveFp, + pThread->thCompThreadEOF, SEEK_CUR); + BailError(err); + } + + pThread++; + } + + /* + * Should've had one by now. Supposedly, older versions of ShrinkIt + * wouldn't prompt for a disk image name on DOS 3.3 volumes, so you'd + * end up with a disk image that had no name attached. This will tend + * to confuse things, so we go ahead and give it a name. + */ + if (pRecord->filename == nil) + pRecord->filename = kNuDefaultRecordName; + + pArchive->currentOffset += pRecord->totalCompLength; + + if (!Nu_IsStreaming(pArchive)) { + Assert(pArchive->currentOffset == ftell(pArchive->archiveFp)); + } + +bail: + return err; +} + + +/* + * Skip the thread. This only has meaning for streaming archives, and + * assumes that the file pointer is set to the start of the thread's data + * already. + */ +NuError +Nu_SkipThread(NuArchive* pArchive, const NuRecord* pRecord, + const NuThread* pThread) +{ + NuError err; + + if (!Nu_IsStreaming(pArchive)) /* for debugging */ + return kNuErrNone; /* for debugging */ + Assert(Nu_IsStreaming(pArchive)); + + err = Nu_SeekArchive(pArchive, pArchive->archiveFp, + pThread->thCompThreadEOF, SEEK_CUR); + return err; +} + + +/* + * =========================================================================== + * Extract + * =========================================================================== + */ + +/* + * Extract the thread to the specified file pointer. + * + * If the archive is a stream, the stream must be positioned at the + * start of pThread's data. If not, it will be seeked first. + */ +static NuError +Nu_ExtractThreadToDataSink(NuArchive* pArchive, const NuRecord* pRecord, + const NuThread* pThread, NuProgressData* pProgress, NuDataSink* pDataSink) +{ + NuError err; + NuFunnel* pFunnel = nil; + + /* if it's not a stream, seek to the appropriate spot in the file */ + if (!Nu_IsStreaming(pArchive)) { + err = Nu_SeekArchive(pArchive, pArchive->archiveFp, + pThread->fileOffset, SEEK_SET); + if (err != kNuErrNone) { + Nu_ReportError(NU_BLOB, err, "Unable to seek input to %ld", + pThread->fileOffset); + goto bail; + } + } + + /* + * Set up an output funnel to write to. + */ + err = Nu_FunnelNew(pArchive, pDataSink, Nu_DataSinkGetConvertEOL(pDataSink), + pArchive->valEOL, pProgress, &pFunnel); + BailError(err); + + /* + * Write it. + */ + err = Nu_ExpandStream(pArchive, pRecord, pThread, pArchive->archiveFp, + pFunnel); + if (err != kNuErrNone) { + if (err != kNuErrSkipped && err != kNuErrAborted) + Nu_ReportError(NU_BLOB, err, "ExpandStream failed"); + goto bail; + } + +bail: + (void) Nu_FunnelFree(pArchive, pFunnel); + return err; +} + + +/* + * Extract the specified thread to "pDataSink". If the sink is to a file, + * this will take care of opening (and, if appropriate, creating) the file. + * + * If we're operating on a streaming archive, the file pointer must be + * positioned at the start of the thread's data. If not, it will be + * seeked appropriately. + * + * This calls the "should we extract" and "what pathname should we use" + * filters for every thread, which means we can reject specific kinds + * of forks and/or give them different names. This is a good thing. + */ +static NuError +Nu_ExtractThreadCommon(NuArchive* pArchive, const NuRecord* pRecord, + const NuThread* pThread, NuDataSink* pDataSink) +{ + NuError err = kNuErrNone; + NuSelectionProposal selProposal; + NuPathnameProposal pathProposal; + NuProgressData progressData; + NuProgressData* pProgressData; + NuDataSink* pOrigDataSink; + char* newPathStorage = nil; + const char* newPathname; + NuResult result; + uchar newFssep; + Boolean doFreeSink = false; + + Assert(pRecord != nil); + Assert(pThread != nil); + Assert(pDataSink != nil); + + memset(&progressData, 0, sizeof(progressData)); + pProgressData = nil; + + /* + * If we're just trying to verify the archive contents, create a + * data sink that goes nowhere at all. + */ + if (pArchive->testMode) { + err = Nu_DataSinkVoid_New( + Nu_DataSinkGetDoExpand(pDataSink), + Nu_DataSinkGetConvertEOL(pDataSink), + &pDataSink); + BailError(err); + doFreeSink = true; + } + + pOrigDataSink = pDataSink; /* save a copy for the "retry" loop */ + + /* + * Decide if we want to extract this thread. This is mostly for + * use by the "bulk" extract, not the per-thread extract, but it + * still applies if they so desire. + */ + if (pArchive->selectionFilterFunc != nil) { + selProposal.pRecord = pRecord; + selProposal.pThread = pThread; + result = (*pArchive->selectionFilterFunc)(pArchive, &selProposal); + + if (result == kNuSkip) + return Nu_SkipThread(pArchive, pRecord, pThread); + if (result == kNuAbort) { + err = kNuErrAborted; + goto bail; + } + } + + newPathname = nil; + newFssep = 0; + +retry_name: + if (Nu_DataSinkGetType(pDataSink) == kNuDataSinkToFile) { + /* + * We're extracting. Figure out the name of the file to write it to. + * If they want to use the sleazy FILE* back door, create a new + * data sink and use that instead. + * + * Start by resetting everything to defaults, in case this isn't + * our first time through the "rename" loop. + */ + newPathname = Nu_DataSinkFile_GetPathname(pDataSink); + newFssep = Nu_DataSinkFile_GetFssep(pDataSink); + pDataSink = pOrigDataSink; + + /* if they don't have a pathname func defined, we just use default */ + if (pArchive->outputPathnameFunc != nil) { + pathProposal.pathname = pRecord->filename; + pathProposal.filenameSeparator = + NuGetSepFromSysInfo(pRecord->recFileSysInfo); + pathProposal.pRecord = pRecord; + pathProposal.pThread = pThread; + pathProposal.newPathname = nil; + pathProposal.newFilenameSeparator = '\0'; + /*pathProposal.newStorage = (NuThreadID)-1;*/ + pathProposal.newDataSink = nil; + + result = (*pArchive->outputPathnameFunc)(pArchive, &pathProposal); + + if (result == kNuSkip) + return Nu_SkipThread(pArchive, pRecord, pThread); + if (result == kNuAbort) { + err = kNuErrAborted; + goto bail; + } + + /* we don't own this string, so make a copy */ + if (pathProposal.newPathname != nil) { + newPathStorage = strdup(pathProposal.newPathname); + newPathname = newPathStorage; + } else + newPathname = nil; + if (pathProposal.newFilenameSeparator != '\0') + newFssep = pathProposal.newFilenameSeparator; + + /* if they want to send this somewhere else, let them */ + if (pathProposal.newDataSink != nil) + pDataSink = pathProposal.newDataSink; + } + + /* at least one of these must be set */ + Assert(!(newPathname == nil && pathProposal.newDataSink == nil)); + } + + /* + * Prepare the progress data if this is a data thread. + */ + if (newPathname == nil) { + /* using a data sink; get the pathname out of the record */ + newPathname = pRecord->filename; + newFssep = NuGetSepFromSysInfo(pRecord->recFileSysInfo); + } + if (pThread->thThreadClass == kNuThreadClassData) { + pProgressData = &progressData; + err = Nu_ProgressDataInit_Expand(pArchive, pProgressData, pRecord, + newPathname, newFssep, Nu_DataSinkGetConvertEOL(pOrigDataSink)); + BailError(err); + + /* send initial progress so they see the right name if "open" fails */ + pProgressData->state = kNuProgressOpening; + err = Nu_SendInitialProgress(pArchive, pProgressData); + BailError(err); + } + + if (Nu_DataSinkGetType(pDataSink) == kNuDataSinkToFile) { + /* + * We're extracting to a file. Open it, creating it if necessary and + * allowed. + */ + FILE* fileFp = nil; + + err = Nu_OpenOutputFile(pArchive, pRecord, pThread, newPathname, + newFssep, &fileFp); + if (err == kNuErrRename) { + /* they want to rename; the OutputPathname callback handles this */ + Nu_Free(pArchive, newPathStorage); + newPathStorage = nil; + /* reset these just to be careful */ + newPathname = nil; + fileFp = nil; + goto retry_name; + } else if (err != kNuErrNone) { + goto bail; + } + + Assert(fileFp != nil); + (void) Nu_DataSinkFile_SetFP(pDataSink, fileFp); + + DBUG(("+++ EXTRACTING 0x%08lx from '%s' at offset %0ld to '%s'\n", + NuMakeThreadID(pThread->thThreadClass, pThread->thThreadKind), + pRecord->filename, pThread->fileOffset, newPathname)); + } else { + DBUG(("+++ EXTRACTING 0x%08lx from '%s' at offset %0ld to sink\n", + NuMakeThreadID(pThread->thThreadClass, pThread->thThreadKind), + pRecord->filename, pThread->fileOffset)); + } + + /* extract to the file */ + err = Nu_ExtractThreadToDataSink(pArchive, pRecord, pThread, + pProgressData, pDataSink); + BailError(err); + + if (Nu_DataSinkGetType(pDataSink) == kNuDataSinkToFile) { + /* + * Close the file, adjusting the modification date and access + * permissions as appropriate. + */ + err = Nu_CloseOutputFile(pArchive, pRecord, + Nu_DataSinkFile_GetFP(pDataSink), newPathname); + Nu_DataSinkFile_SetFP(pDataSink, nil); + BailError(err); + } + +bail: + if (err != kNuErrNone) { + /* send a final progress message, indicating failure */ + if (err == kNuErrSkipped) + pProgressData->state = kNuProgressSkipped; + else + pProgressData->state = kNuProgressFailed; + (void) Nu_SendInitialProgress(pArchive, pProgressData); + } + + /* if this was an ordinary file, and it's still open, close it */ + if (Nu_DataSinkGetType(pDataSink) == kNuDataSinkToFile) + Nu_DataSinkFile_Close(pDataSink); + + Nu_Free(pArchive, newPathStorage); + + if (doFreeSink) + Nu_DataSinkFree(pDataSink); + return err; +} + +/* + * Extract a thread from the archive as part of a "bulk" extract operation. + * + * Streaming archives must be properly positioned. + */ +NuError +Nu_ExtractThreadBulk(NuArchive* pArchive, const NuRecord* pRecord, + const NuThread* pThread) +{ + NuError err; + NuDataSink* pDataSink = nil; + + /* + * Create a file data sink for the file. We use whatever EOL conversion + * is set as the default for the entire archive. (If you want to + * specify your own EOL conversion for each individual file, you will + * need to extract them individually, creating a data sink for each.) + */ + err = Nu_DataSinkFile_New(true, pArchive->valConvertExtractedEOL, + pRecord->filename, NuGetSepFromSysInfo(pRecord->recFileSysInfo), + &pDataSink); + BailError(err); + + err = Nu_ExtractThreadCommon(pArchive, pRecord, pThread, pDataSink); + BailError(err); + +bail: + if (pDataSink != nil) { + NuError err2 = Nu_DataSinkFree(pDataSink); + if (err == kNuErrNone) + err = err2; + } + + return err; +} + + +/* + * Extract a thread, given the IDs and a data sink. + */ +NuError +Nu_ExtractThread(NuArchive* pArchive, NuThreadIdx threadIdx, + NuDataSink* pDataSink) +{ + NuError err; + NuRecord* pRecord; + NuThread* pThread; + + if (Nu_IsStreaming(pArchive)) + return kNuErrUsage; + if (threadIdx == 0 || pDataSink == nil) + return kNuErrInvalidArg; + + /* find the correct record and thread by index */ + err = Nu_RecordSet_FindByThreadIdx(&pArchive->origRecordSet, threadIdx, + &pRecord, &pThread); + BailError(err); + Assert(pRecord != nil); + + /* extract away */ + err = Nu_ExtractThreadCommon(pArchive, pRecord, pThread, pDataSink); + BailError(err); + +bail: + return err; +} + + +/* + * =========================================================================== + * Add/update/delete + * =========================================================================== + */ + +/* + * Verify that a conflicting thread with the specified threadID does not + * exist in this record, now or in the future. + * + * The set of interesting threads is equal to the current threads, minus + * any that have been deleted, plus any that have been added already. + * + * If a matching threadID is found, this returns an error. + */ +static NuError +Nu_FindNoFutureThread(NuArchive* pArchive, const NuRecord* pRecord, + NuThreadID threadID) +{ + NuError err = kNuErrNone; + const NuThread* pThread; + const NuThreadMod* pThreadMod; + int idx; + + /* + * Start by scanning the existing threads (if any). + */ + for (idx = 0; idx < (int)pRecord->recTotalThreads; idx++) { + pThread = Nu_GetThread(pRecord, idx); + Assert(pThread != nil); + + if (NuGetThreadID(pThread) == threadID) { + /* found a match, see if it has been deleted */ + pThreadMod = Nu_ThreadMod_FindByThreadIdx(pRecord, + pThread->threadIdx); + if (pThreadMod != nil && + pThreadMod->entry.kind == kNuThreadModDelete) + { + /* it's deleted, ignore it */ + continue; + } + DBUG(("--- found existing thread matching 0x%08lx\n", threadID)); + err = kNuErrThreadAdd; + goto bail; + } + } + + /* + * Now look for "add" threadMods with a matching threadID. + */ + pThreadMod = pRecord->pThreadMods; + while (pThreadMod != nil) { + if (pThreadMod->entry.kind == kNuThreadModAdd && + pThreadMod->entry.add.threadID == threadID) + { + DBUG(("--- found 'add' threadMod matching 0x%08lx\n", threadID)); + err = kNuErrThreadAdd; + goto bail; + } + + pThreadMod = pThreadMod->pNext; + } + +bail: + return err; +} + +/* + * Like Nu_FindNoFutureThread, but tests against a whole class. + */ +static NuError +Nu_FindNoFutureThreadClass(NuArchive* pArchive, const NuRecord* pRecord, + long threadClass) +{ + NuError err = kNuErrNone; + const NuThread* pThread; + const NuThreadMod* pThreadMod; + int idx; + + /* + * Start by scanning the existing threads (if any). + */ + for (idx = 0; idx < (int)pRecord->recTotalThreads; idx++) { + pThread = Nu_GetThread(pRecord, idx); + Assert(pThread != nil); + + if (pThread->thThreadClass == threadClass) { + /* found a match, see if it has been deleted */ + pThreadMod = Nu_ThreadMod_FindByThreadIdx(pRecord, + pThread->threadIdx); + if (pThreadMod != nil && + pThreadMod->entry.kind == kNuThreadModDelete) + { + /* it's deleted, ignore it */ + continue; + } + DBUG(("--- Found existing thread matching 0x%04lx\n", threadClass)); + err = kNuErrThreadAdd; + goto bail; + } + } + + /* + * Now look for "add" threadMods with a matching threadClass. + */ + pThreadMod = pRecord->pThreadMods; + while (pThreadMod != nil) { + if (pThreadMod->entry.kind == kNuThreadModAdd && + NuThreadIDGetClass(pThreadMod->entry.add.threadID) == threadClass) + { + DBUG(("--- Found 'add' threadMod matching 0x%04lx\n", threadClass)); + err = kNuErrThreadAdd; + goto bail; + } + + pThreadMod = pThreadMod->pNext; + } + +bail: + return err; +} + + +/* + * Find an existing thread somewhere in the archive. If the "copy" set + * exists it will be searched. If not, the "orig" set is searched, and + * if an entry is found a "copy" set will be created. + * + * The record and thread returned will always be from the "copy" set. An + * error result is returned if the record and thread aren't found. + */ +static NuError +Nu_FindThreadForWriteByIdx(NuArchive* pArchive, NuThreadIdx threadIdx, + NuRecord** ppFoundRecord, NuThread** ppFoundThread) +{ + NuError err; + + if (Nu_RecordSet_GetLoaded(&pArchive->copyRecordSet)) { + err = Nu_RecordSet_FindByThreadIdx(&pArchive->copyRecordSet, threadIdx, + ppFoundRecord, ppFoundThread); + } else { + Assert(Nu_RecordSet_GetLoaded(&pArchive->origRecordSet)); + err = Nu_RecordSet_FindByThreadIdx(&pArchive->origRecordSet, threadIdx, + ppFoundRecord, ppFoundThread); + *ppFoundThread = nil; /* can't delete from here, wipe ptr */ + } + BailError(err); + + /* + * The thread exists. If we were looking in the "orig" set, we have + * to create a "copy" set, and delete it from that. + */ + if (*ppFoundThread == nil) { + err = Nu_RecordSet_Clone(pArchive, &pArchive->copyRecordSet, + &pArchive->origRecordSet); + BailError(err); + err = Nu_RecordSet_FindByThreadIdx(&pArchive->copyRecordSet, threadIdx, + ppFoundRecord, ppFoundThread); + Assert(err == kNuErrNone && *ppFoundThread != nil); /* must succeed */ + BailError(err); + } + +bail: + return err; +} + +/* + * Determine if it's okay to add a thread of the type specified by + * "threadID" into "pRecord". + * + * Returns with an error (kNuErrThreadAdd) if it's not okay. + */ +NuError +Nu_OkayToAddThread(NuArchive* pArchive, const NuRecord* pRecord, + NuThreadID threadID) +{ + NuError err = kNuErrNone; + + /* + * Check for class conflicts (can't mix data and control threads). + */ + if (NuThreadIDGetClass(threadID) == kNuThreadClassData) { + err = Nu_FindNoFutureThreadClass(pArchive, pRecord, + kNuThreadClassControl); + BailError(err); + } else if (NuThreadIDGetClass(threadID) == kNuThreadClassControl) { + err = Nu_FindNoFutureThreadClass(pArchive, pRecord, + kNuThreadClassData); + BailError(err); + } + + /* + * Check for specific type conflicts. + */ + if (threadID == kNuThreadIDDataFork) { + err = Nu_FindNoFutureThread(pArchive, pRecord, kNuThreadIDDataFork); + BailError(err); + err = Nu_FindNoFutureThread(pArchive, pRecord, kNuThreadIDDiskImage); + BailError(err); + } else if (threadID == kNuThreadIDRsrcFork) { + err = Nu_FindNoFutureThread(pArchive, pRecord, kNuThreadIDRsrcFork); + BailError(err); + err = Nu_FindNoFutureThread(pArchive, pRecord, kNuThreadIDDiskImage); + BailError(err); + } else if (threadID == kNuThreadIDDiskImage) { + err = Nu_FindNoFutureThread(pArchive, pRecord, kNuThreadIDDataFork); + BailError(err); + err = Nu_FindNoFutureThread(pArchive, pRecord, kNuThreadIDRsrcFork); + BailError(err); + err = Nu_FindNoFutureThread(pArchive, pRecord, kNuThreadIDDiskImage); + BailError(err); + } else if (threadID == kNuThreadIDFilename) { + err = Nu_FindNoFutureThread(pArchive, pRecord, kNuThreadIDFilename); + BailError(err); + } + +bail: + return err; +} + + + +/* + * Add a new thread to a record. + * + * In some cases, you aren't allowed to add a thread whose type matches + * one that already exists. This applies to data threads and filenames, + * but not to comments, control threads, or IIgs icons. You also can't + * add a disk image thread when there are data-class threads, or vice-versa. + * + * This is the first and last place we do this sort of checking. If + * an illegal situation gets past this function, it will either get + * caught with a fatal assert or (if NDEBUG is defined) not at all. + * + * On success, the NuThreadIdx of the newly-created record will be placed + * in "*pThreadIdx". + */ +NuError +Nu_AddThread(NuArchive* pArchive, NuRecordIdx recIdx, NuThreadID threadID, + NuDataSource* pDataSource, NuThreadIdx* pThreadIdx) +{ + NuError err; + NuRecord* pRecord; + NuThreadMod* pThreadMod = nil; + NuThreadFormat threadFormat; + + /* okay for pThreadIdx to be nil */ + if (recIdx == 0 || pDataSource == nil) + return kNuErrInvalidArg; + + if (Nu_IsReadOnly(pArchive)) + return kNuErrArchiveRO; + err = Nu_GetTOCIfNeeded(pArchive); + BailError(err); + + /* + * Find the record. If it doesn't exist in the copy set, check to + * see if it's in the "new" set. + */ + err = Nu_FindRecordForWriteByIdx(pArchive, recIdx, &pRecord); + if (err == kNuErrRecIdxNotFound && + Nu_RecordSet_GetLoaded(&pArchive->newRecordSet)) + { + err = Nu_RecordSet_FindByIdx(&pArchive->newRecordSet, recIdx, &pRecord); + } + BailError(err); + Assert(pRecord != nil); + + /* + * Do some tests, looking for specific types of threads that conflict + * with what we're trying to add. + */ + err = Nu_OkayToAddThread(pArchive, pRecord, threadID); + BailError(err); + + /* + * Decide if we want to compress the data from this source. If the + * data is already compressed (as indicated by the data source) or + * this type of thread isn't compressible (e.g. it's a filename), then + * we don't compress it. Otherwise, we use whatever compression mode + * is currently configured. + */ + if (Nu_DataSourceGetThreadFormat(pDataSource) == kNuThreadFormatUncompressed && + Nu_IsCompressibleThreadID(threadID)) + { + threadFormat = Nu_ConvertCompressValToFormat(pArchive, + pArchive->valDataCompression); + } else { + threadFormat = kNuThreadFormatUncompressed; + } + DBUG(("--- using threadFormat = %d\n", threadFormat)); + + /* create a new ThreadMod */ + err = Nu_ThreadModAdd_New(pArchive, threadID, threadFormat, pDataSource, + &pThreadMod); + BailError(err); + Assert(pThreadMod != nil); + + /* add the thread mod to the record */ + Nu_RecordAddThreadMod(pRecord, pThreadMod); + if (pThreadIdx != nil) + *pThreadIdx = pThreadMod->entry.add.threadIdx; + pThreadMod = nil; /* successful, don't free */ + + /* + * If we've got a header filename and we're adding a filename thread, + * we don't want to write the record header name when we reconstruct + * the record. + */ + if (threadID == kNuThreadIDFilename && pRecord->recFilenameLength) { + DBUG(("+++ gonna drop the filename\n")); + pRecord->dropRecFilename = true; + } + +bail: + if (pThreadMod != nil) + Nu_ThreadModFree(pArchive, pThreadMod); + return err; +} + + +/* + * Update the contents of a pre-sized thread, such as a filename or + * comment thread. + * + * The data from the source must fit within the limits of the existing + * thread. The source data is never compressed, and must not come from + * a compressed source. + * + * You aren't allowed to update threads that have been deleted. Updating + * newly-added threads isn't possible, since they aren't really threads yet. + */ +NuError +Nu_UpdatePresizedThread(NuArchive* pArchive, NuThreadIdx threadIdx, + NuDataSource* pDataSource, long* pMaxLen) +{ + NuError err; + NuThreadMod* pThreadMod = nil; + NuRecord* pFoundRecord; + NuThread* pFoundThread; + + if (pDataSource == nil) { + err = kNuErrInvalidArg; + goto bail; + } + + /* presized threads always contain uncompressed data */ + if (Nu_DataSourceGetThreadFormat(pDataSource) != + kNuThreadFormatUncompressed) + { + err = kNuErrBadFormat; + Nu_ReportError(NU_BLOB, err, + "presized threads can't hold compressed data"); + goto bail; + } + + if (Nu_IsReadOnly(pArchive)) + return kNuErrArchiveRO; + err = Nu_GetTOCIfNeeded(pArchive); + BailError(err); + + /* + * Find the thread in the "copy" set. (If there isn't a copy set, + * make one.) + */ + err = Nu_FindThreadForWriteByIdx(pArchive, threadIdx, &pFoundRecord, + &pFoundThread); + BailError(err); + + if (!Nu_IsPresizedThreadID(NuGetThreadID(pFoundThread)) || + !(pFoundThread->thCompThreadEOF >= pFoundThread->thThreadEOF)) + { + err = kNuErrNotPreSized; + Nu_ReportError(NU_BLOB, err, "invalid thread for update"); + goto bail; + } + + if (pMaxLen != nil) + *pMaxLen = pFoundThread->thCompThreadEOF; + + /* + * Check to see if somebody is trying to delete this, or has already + * updated it. + */ + if (Nu_ThreadMod_FindByThreadIdx(pFoundRecord, threadIdx) != nil) { + DBUG(("--- Tried to modify a deleted or modified thread\n")); + err = kNuErrModThreadChange; + goto bail; + } + + /* + * Verify that "otherLen" in the data source is less than or equal + * to our len, if we can. If the data source is a file on disk, + * we're not really supposed to look at it until we flush. We + * could sneak a peek right now, which would prevent us from aborting + * the entire operation when it turns out the file won't fit, but + * that violates our semantics (and besides, the application really + * should've done that already). + * + * If the data source is from a file, we just assume it'll fit and + * let the chips fall where they may later on. + */ + if (Nu_DataSourceGetType(pDataSource) != kNuDataSourceFromFile) { + if (pFoundThread->thCompThreadEOF < + Nu_DataSourceGetOtherLen(pDataSource)) + { + err = kNuErrPreSizeOverflow; + Nu_ReportError(NU_BLOB, err, "can't put %ld bytes into %ld", + Nu_DataSourceGetOtherLen(pDataSource), + pFoundThread->thCompThreadEOF); + goto bail; + } + + /* check for zero-length and excessively long filenames */ + if (NuGetThreadID(pFoundThread) == kNuThreadIDFilename && + (Nu_DataSourceGetOtherLen(pDataSource) == 0 || + Nu_DataSourceGetOtherLen(pDataSource) > kNuReasonableFilenameLen)) + { + err = kNuErrInvalidFilename; + Nu_ReportError(NU_BLOB, err, "invalid filename (%ld bytes)", + Nu_DataSourceGetOtherLen(pDataSource)); + goto bail; + } + } + + /* + * Looks like it'll fit, and it's the right kind of data. Create + * an "update" threadMod. Note this copies the data source. + */ + Assert(pFoundThread->thThreadFormat == kNuThreadFormatUncompressed); + err = Nu_ThreadModUpdate_New(pArchive, threadIdx, pDataSource, &pThreadMod); + BailError(err); + Assert(pThreadMod != nil); + + /* add the thread mod to the record */ + Nu_RecordAddThreadMod(pFoundRecord, pThreadMod); + + /* + * NOTE: changes to filename threads will be picked up later and + * incorporated into the record's threadFilename. We don't worry + * about the record header filename, because we might be doing an + * update-in-place and that prevents us from removing the filename + * (doing so would change the size of the archive). No need to + * do any filename-specific changes here. + */ + +bail: + return err; +} + +/* + * Delete an individual thread. + * + * You aren't allowed to delete threads that have been updated. Deleting + * newly-added threads isn't possible, since they aren't really threads yet. + * + * Don't worry about deleting filename threads here; we take care of that + * later on. Besides, it's sort of handy to hang on to the filename for + * as long as possible. + */ +NuError +Nu_DeleteThread(NuArchive* pArchive, NuThreadIdx threadIdx) +{ + NuError err; + NuThreadMod* pThreadMod = nil; + NuRecord* pFoundRecord; + NuThread* pFoundThread; + + if (Nu_IsReadOnly(pArchive)) + return kNuErrArchiveRO; + err = Nu_GetTOCIfNeeded(pArchive); + BailError(err); + + /* + * Find the thread in the "copy" set. (If there isn't a copy set, + * make one.) + */ + err = Nu_FindThreadForWriteByIdx(pArchive, threadIdx, &pFoundRecord, + &pFoundThread); + BailError(err); + + /* + * Deletion of modified threads (updates or previous deletes) isn't + * allowed. Deletion of threads from deleted records can't happen, + * because deleted records are completely removed from the "copy" set. + */ + if (Nu_ThreadMod_FindByThreadIdx(pFoundRecord, threadIdx) != nil) { + DBUG(("--- Tried to delete a deleted or modified thread\n")); + err = kNuErrModThreadChange; + goto bail; + } + + /* + * Looks good. Add a new "delete" ThreadMod to the list. + */ + err = Nu_ThreadModDelete_New(pArchive, threadIdx, + NuGetThreadID(pFoundThread), &pThreadMod); + BailError(err); + Nu_RecordAddThreadMod(pFoundRecord, pThreadMod); + pThreadMod = nil; /* successful, don't free */ + +bail: + Nu_ThreadModFree(pArchive, pThreadMod); + return err; +} + diff --git a/nufxlib-0/Value.c b/nufxlib-0/Value.c new file mode 100644 index 0000000..c6999d9 --- /dev/null +++ b/nufxlib-0/Value.c @@ -0,0 +1,231 @@ +/* + * NuFX archive manipulation library + * 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 Library General Public License, see the file COPYING.LIB. + * + * Get/set certain values and attributes. + */ +#include "NufxLibPriv.h" + + +/* + * Get a configurable parameter. + */ +NuError +Nu_GetValue(NuArchive* pArchive, NuValueID ident, NuValue* pValue) +{ + NuError err = kNuErrNone; + + if (pValue == nil) + return kNuErrInvalidArg; + + switch (ident) { + case kNuValueAllowDuplicates: + *pValue = pArchive->valAllowDuplicates; + break; + case kNuValueConvertExtractedEOL: + *pValue = pArchive->valConvertExtractedEOL; + break; + case kNuValueDataCompression: + *pValue = pArchive->valDataCompression; + break; + case kNuValueDiscardWrapper: + *pValue = pArchive->valDiscardWrapper; + break; + case kNuValueEOL: + *pValue = pArchive->valEOL; + break; + case kNuValueHandleExisting: + *pValue = pArchive->valHandleExisting; + break; + case kNuValueIgnoreCRC: + *pValue = pArchive->valIgnoreCRC; + break; + case kNuValueMimicSHK: + *pValue = pArchive->valMimicSHK; + break; + case kNuValueModifyOrig: + *pValue = pArchive->valModifyOrig; + break; + case kNuValueOnlyUpdateOlder: + *pValue = pArchive->valOnlyUpdateOlder; + break; + default: + err = kNuErrInvalidArg; + Nu_ReportError(NU_BLOB, err, "Unknown ValueID %d requested", ident); + goto bail; + } + +bail: + return err; +} + + +/* + * Set a configurable parameter. + */ +NuError +Nu_SetValue(NuArchive* pArchive, NuValueID ident, NuValue value) +{ + NuError err = kNuErrInvalidArg; + + switch (ident) { + case kNuValueAllowDuplicates: + if (value != true && value != false) { + Nu_ReportError(NU_BLOB, err, + "Invalid kNuValueAllowDuplicates value %ld\n", value); + goto bail; + } + pArchive->valAllowDuplicates = value; + break; + case kNuValueConvertExtractedEOL: + if (value < kNuConvertOff || value > kNuConvertAuto) { + Nu_ReportError(NU_BLOB, err, + "Invalid kNuValueConvertExtractedEOL value %ld\n", value); + goto bail; + } + pArchive->valConvertExtractedEOL = value; + break; + case kNuValueDataCompression: + if (value < kNuCompressNone || value > kNuCompressLZC16) { + Nu_ReportError(NU_BLOB, err, + "Invalid kNuValueDataCompression value %ld\n", value); + goto bail; + } + pArchive->valDataCompression = value; + break; + case kNuValueDiscardWrapper: + if (value != true && value != false) { + Nu_ReportError(NU_BLOB, err, + "Invalid kNuValueDiscardWrapper value %ld\n", value); + goto bail; + } + pArchive->valDiscardWrapper = value; + break; + case kNuValueEOL: + if (value < kNuEOLUnknown || value > kNuEOLCRLF) { + Nu_ReportError(NU_BLOB, err, + "Invalid kNuValueEOL value %ld\n", value); + goto bail; + } + pArchive->valEOL = value; + break; + case kNuValueHandleExisting: + if (value < kNuMaybeOverwrite || value > kNuMustOverwrite) { + Nu_ReportError(NU_BLOB, err, + "Invalid kNuValueHandleExisting value %ld\n", value); + goto bail; + } + pArchive->valHandleExisting = value; + break; + case kNuValueIgnoreCRC: + if (value != true && value != false) { + Nu_ReportError(NU_BLOB, err, + "Invalid kNuValueIgnoreCRC value %ld\n", value); + goto bail; + } + pArchive->valIgnoreCRC = value; + break; + case kNuValueMimicSHK: + if (value != true && value != false) { + Nu_ReportError(NU_BLOB, err, + "Invalid kNuValueMimicSHK value %ld\n", value); + goto bail; + } + pArchive->valMimicSHK = value; + break; + case kNuValueModifyOrig: + if (value != true && value != false) { + Nu_ReportError(NU_BLOB, err, + "Invalid kNuValueModifyOrig value %ld\n", value); + goto bail; + } + pArchive->valModifyOrig = value; + break; + case kNuValueOnlyUpdateOlder: + if (value != true && value != false) { + Nu_ReportError(NU_BLOB, err, + "Invalid kNuValueOnlyUpdateOlder value %ld\n", value); + goto bail; + } + pArchive->valOnlyUpdateOlder = value; + break; + default: + Nu_ReportError(NU_BLOB, err, "Unknown ValueID %d requested", ident); + goto bail; + } + + err = kNuErrNone; + +bail: + return err; +} + + +/* + * Get an archive attribute. These are things that you would have to + * pry into pArchive to get at (like the archive type) or get the master + * header (like the number of records). + */ +NuError +Nu_GetAttr(NuArchive* pArchive, NuAttrID ident, NuAttr* pAttr) +{ + NuError err = kNuErrNone; + if (pAttr == nil) + return kNuErrInvalidArg; + + switch (ident) { + case kNuAttrArchiveType: + *pAttr = pArchive->archiveType; + break; + case kNuAttrNumRecords: + *pAttr = pArchive->masterHeader.mhTotalRecords; + break; + default: + err = kNuErrInvalidArg; + Nu_ReportError(NU_BLOB, err, "Unknown AttrID %d requested", ident); + goto bail; + } + +bail: + return err; +} + +/* + * Convert a NuValue compression type to a "phyiscal" ThreadFormat. + * + * Unsupported compression types cause a warning to be flagged. + */ +NuThreadFormat +Nu_ConvertCompressValToFormat(NuArchive* pArchive, NuValue compValue) +{ + NuThreadFormat threadFormat; + Boolean unsup = false; + + switch (compValue) { + case kNuCompressNone: threadFormat = kNuThreadFormatUncompressed; break; + case kNuCompressLZW1: threadFormat = kNuThreadFormatLZW1; break; + case kNuCompressLZW2: threadFormat = kNuThreadFormatLZW2; break; + case kNuCompressSQ: threadFormat = kNuThreadFormatHuffmanSQ; break; + case kNuCompressLZC12: threadFormat = kNuThreadFormatLZC12; + unsup = true; break; + case kNuCompressLZC16: threadFormat = kNuThreadFormatLZC16; + unsup = true; break; + default: + Assert(false); + Nu_ReportError(NU_BLOB, kNuErrInvalidArg, + "Unknown compress value %ld", compValue); + return kNuThreadFormatUncompressed; + } + + if (unsup) { + Nu_ReportError(NU_BLOB, kNuErrNone, + "Unsupported compression type 0x%04x requested (%ld)", + threadFormat, compValue); + return kNuThreadFormatUncompressed; + } + + return threadFormat; +} + diff --git a/nufxlib-0/Version.c.in b/nufxlib-0/Version.c.in new file mode 100644 index 0000000..f021f2e --- /dev/null +++ b/nufxlib-0/Version.c.in @@ -0,0 +1,42 @@ +/* + * NuFX archive manipulation library + * 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 Library General Public License, see the file COPYING.LIB. + * + * !!! NOTE !!! + * The file Version.c is automatically generated from Version.c.in. Don't + * edit the .c file if you want your changes to be permanent. + */ +#include "NufxLibPriv.h" + +/* version number; edit by hand */ +static const long gNuMajorVersion = 1; +static const long gNuMinorVersion = 0; +static const long gNuBugVersion = 0; + +/* executable was build on or after this date (inserted automatically) */ +static const char gNuBuildDate[] = "BUILT"; /* approximate */ +static const char gNuBuildFlags[] = "OPTFLAGS"; + + +/* + * Return the version number, date built, and build flags. + */ +NuError +Nu_GetVersion(long* pMajorVersion, long* pMinorVersion, long* pBugVersion, + const char** ppBuildDate, const char** ppBuildFlags) +{ + if (pMajorVersion != nil) + *pMajorVersion = gNuMajorVersion; + if (pMinorVersion != nil) + *pMinorVersion = gNuMinorVersion; + if (pBugVersion != nil) + *pBugVersion = gNuBugVersion; + if (ppBuildDate != nil) + *ppBuildDate = gNuBuildDate; + if (ppBuildFlags != nil) + *ppBuildFlags = gNuBuildFlags; + return kNuErrNone; +} + diff --git a/nufxlib-0/config.guess b/nufxlib-0/config.guess new file mode 100644 index 0000000..cca6757 --- /dev/null +++ b/nufxlib-0/config.guess @@ -0,0 +1,1088 @@ +#! /bin/sh +# Attempt to guess a canonical system name. +# Copyright (C) 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999 +# Free Software Foundation, Inc. +# +# This file is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +# As a special exception to the GNU General Public License, if you +# distribute this file as part of a program that contains a +# configuration script generated by Autoconf, you may include it under +# the same distribution terms that you use for the rest of that program. + +# Written by Per Bothner . +# The master version of this file is at the FSF in /home/gd/gnu/lib. +# Please send patches to . +# +# This script attempts to guess a canonical system name similar to +# config.sub. If it succeeds, it prints the system name on stdout, and +# exits with 0. Otherwise, it exits with 1. +# +# The plan is that this can be called by configure scripts if you +# don't specify an explicit system type (host/target name). +# +# Only a few systems have been added to this list; please add others +# (but try to keep the structure clean). +# + +# Use $HOST_CC if defined. $CC may point to a cross-compiler +if test x"$CC_FOR_BUILD" = x; then + if test x"$HOST_CC" != x; then + CC_FOR_BUILD="$HOST_CC" + else + if test x"$CC" != x; then + CC_FOR_BUILD="$CC" + else + CC_FOR_BUILD=cc + fi + fi +fi + + +# This is needed to find uname on a Pyramid OSx when run in the BSD universe. +# (ghazi@noc.rutgers.edu 8/24/94.) +if (test -f /.attbin/uname) >/dev/null 2>&1 ; then + PATH=$PATH:/.attbin ; export PATH +fi + +UNAME_MACHINE=`(uname -m) 2>/dev/null` || UNAME_MACHINE=unknown +UNAME_RELEASE=`(uname -r) 2>/dev/null` || UNAME_RELEASE=unknown +UNAME_SYSTEM=`(uname -s) 2>/dev/null` || UNAME_SYSTEM=unknown +UNAME_VERSION=`(uname -v) 2>/dev/null` || UNAME_VERSION=unknown + +dummy=dummy-$$ +trap 'rm -f $dummy.c $dummy.o $dummy; exit 1' 1 2 15 + +# Note: order is significant - the case branches are not exclusive. + +case "${UNAME_MACHINE}:${UNAME_SYSTEM}:${UNAME_RELEASE}:${UNAME_VERSION}" in + alpha:OSF1:*:*) + if test $UNAME_RELEASE = "V4.0"; then + UNAME_RELEASE=`/usr/sbin/sizer -v | awk '{print $3}'` + fi + # A Vn.n version is a released version. + # A Tn.n version is a released field test version. + # A Xn.n version is an unreleased experimental baselevel. + # 1.2 uses "1.2" for uname -r. + cat <$dummy.s + .globl main + .ent main +main: + .frame \$30,0,\$26,0 + .prologue 0 + .long 0x47e03d80 # implver $0 + lda \$2,259 + .long 0x47e20c21 # amask $2,$1 + srl \$1,8,\$2 + sll \$2,2,\$2 + sll \$0,3,\$0 + addl \$1,\$0,\$0 + addl \$2,\$0,\$0 + ret \$31,(\$26),1 + .end main +EOF + $CC_FOR_BUILD $dummy.s -o $dummy 2>/dev/null + if test "$?" = 0 ; then + ./$dummy + case "$?" in + 7) + UNAME_MACHINE="alpha" + ;; + 15) + UNAME_MACHINE="alphaev5" + ;; + 14) + UNAME_MACHINE="alphaev56" + ;; + 10) + UNAME_MACHINE="alphapca56" + ;; + 16) + UNAME_MACHINE="alphaev6" + ;; + esac + fi + rm -f $dummy.s $dummy + echo ${UNAME_MACHINE}-dec-osf`echo ${UNAME_RELEASE} | sed -e 's/^[VTX]//' | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz'` + exit 0 ;; + Alpha\ *:Windows_NT*:*) + # How do we know it's Interix rather than the generic POSIX subsystem? + # Should we change UNAME_MACHINE based on the output of uname instead + # of the specific Alpha model? + echo alpha-pc-interix + exit 0 ;; + 21064:Windows_NT:50:3) + echo alpha-dec-winnt3.5 + exit 0 ;; + Amiga*:UNIX_System_V:4.0:*) + echo m68k-cbm-sysv4 + exit 0;; + amiga:NetBSD:*:*) + echo m68k-cbm-netbsd${UNAME_RELEASE} + exit 0 ;; + amiga:OpenBSD:*:*) + echo m68k-unknown-openbsd${UNAME_RELEASE} + exit 0 ;; + *:[Aa]miga[Oo][Ss]:*:*) + echo ${UNAME_MACHINE}-unknown-amigaos + exit 0 ;; + arc64:OpenBSD:*:*) + echo mips64el-unknown-openbsd${UNAME_RELEASE} + exit 0 ;; + arc:OpenBSD:*:*) + echo mipsel-unknown-openbsd${UNAME_RELEASE} + exit 0 ;; + hkmips:OpenBSD:*:*) + echo mips-unknown-openbsd${UNAME_RELEASE} + exit 0 ;; + pmax:OpenBSD:*:*) + echo mipsel-unknown-openbsd${UNAME_RELEASE} + exit 0 ;; + sgi:OpenBSD:*:*) + echo mips-unknown-openbsd${UNAME_RELEASE} + exit 0 ;; + wgrisc:OpenBSD:*:*) + echo mipsel-unknown-openbsd${UNAME_RELEASE} + exit 0 ;; + arm:RISC*:1.[012]*:*|arm:riscix:1.[012]*:*) + echo arm-acorn-riscix${UNAME_RELEASE} + exit 0;; + arm32:NetBSD:*:*) + echo arm-unknown-netbsd`echo ${UNAME_RELEASE}|sed -e 's/[-_].*/\./'` + exit 0 ;; + SR2?01:HI-UX/MPP:*:*) + echo hppa1.1-hitachi-hiuxmpp + exit 0;; + Pyramid*:OSx*:*:* | MIS*:OSx*:*:* | MIS*:SMP_DC-OSx*:*:*) + # akee@wpdis03.wpafb.af.mil (Earle F. Ake) contributed MIS and NILE. + if test "`(/bin/universe) 2>/dev/null`" = att ; then + echo pyramid-pyramid-sysv3 + else + echo pyramid-pyramid-bsd + fi + exit 0 ;; + NILE*:*:*:dcosx) + echo pyramid-pyramid-svr4 + exit 0 ;; + sun4H:SunOS:5.*:*) + echo sparc-hal-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` + exit 0 ;; + sun4*:SunOS:5.*:* | tadpole*:SunOS:5.*:*) + echo sparc-sun-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` + exit 0 ;; + i86pc:SunOS:5.*:*) + echo i386-pc-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` + exit 0 ;; + sun4*:SunOS:6*:*) + # According to config.sub, this is the proper way to canonicalize + # SunOS6. Hard to guess exactly what SunOS6 will be like, but + # it's likely to be more like Solaris than SunOS4. + echo sparc-sun-solaris3`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` + exit 0 ;; + sun4*:SunOS:*:*) + case "`/usr/bin/arch -k`" in + Series*|S4*) + UNAME_RELEASE=`uname -v` + ;; + esac + # Japanese Language versions have a version number like `4.1.3-JL'. + echo sparc-sun-sunos`echo ${UNAME_RELEASE}|sed -e 's/-/_/'` + exit 0 ;; + sun3*:SunOS:*:*) + echo m68k-sun-sunos${UNAME_RELEASE} + exit 0 ;; + sun*:*:4.2BSD:*) + UNAME_RELEASE=`(head -1 /etc/motd | awk '{print substr($5,1,3)}') 2>/dev/null` + test "x${UNAME_RELEASE}" = "x" && UNAME_RELEASE=3 + case "`/bin/arch`" in + sun3) + echo m68k-sun-sunos${UNAME_RELEASE} + ;; + sun4) + echo sparc-sun-sunos${UNAME_RELEASE} + ;; + esac + exit 0 ;; + aushp:SunOS:*:*) + echo sparc-auspex-sunos${UNAME_RELEASE} + exit 0 ;; + atari*:NetBSD:*:*) + echo m68k-atari-netbsd${UNAME_RELEASE} + exit 0 ;; + atari*:OpenBSD:*:*) + echo m68k-unknown-openbsd${UNAME_RELEASE} + exit 0 ;; + # The situation for MiNT is a little confusing. The machine name + # can be virtually everything (everything which is not + # "atarist" or "atariste" at least should have a processor + # > m68000). The system name ranges from "MiNT" over "FreeMiNT" + # to the lowercase version "mint" (or "freemint"). Finally + # the system name "TOS" denotes a system which is actually not + # MiNT. But MiNT is downward compatible to TOS, so this should + # be no problem. + atarist[e]:*MiNT:*:* | atarist[e]:*mint:*:* | atarist[e]:*TOS:*:*) + echo m68k-atari-mint${UNAME_RELEASE} + exit 0 ;; + atari*:*MiNT:*:* | atari*:*mint:*:* | atarist[e]:*TOS:*:*) + echo m68k-atari-mint${UNAME_RELEASE} + exit 0 ;; + *falcon*:*MiNT:*:* | *falcon*:*mint:*:* | *falcon*:*TOS:*:*) + echo m68k-atari-mint${UNAME_RELEASE} + exit 0 ;; + milan*:*MiNT:*:* | milan*:*mint:*:* | *milan*:*TOS:*:*) + echo m68k-milan-mint${UNAME_RELEASE} + exit 0 ;; + hades*:*MiNT:*:* | hades*:*mint:*:* | *hades*:*TOS:*:*) + echo m68k-hades-mint${UNAME_RELEASE} + exit 0 ;; + *:*MiNT:*:* | *:*mint:*:* | *:*TOS:*:*) + echo m68k-unknown-mint${UNAME_RELEASE} + exit 0 ;; + sun3*:NetBSD:*:*) + echo m68k-sun-netbsd${UNAME_RELEASE} + exit 0 ;; + sun3*:OpenBSD:*:*) + echo m68k-unknown-openbsd${UNAME_RELEASE} + exit 0 ;; + mac68k:NetBSD:*:*) + echo m68k-apple-netbsd${UNAME_RELEASE} + exit 0 ;; + mac68k:OpenBSD:*:*) + echo m68k-unknown-openbsd${UNAME_RELEASE} + exit 0 ;; + mvme68k:OpenBSD:*:*) + echo m68k-unknown-openbsd${UNAME_RELEASE} + exit 0 ;; + mvme88k:OpenBSD:*:*) + echo m88k-unknown-openbsd${UNAME_RELEASE} + exit 0 ;; + powerpc:machten:*:*) + echo powerpc-apple-machten${UNAME_RELEASE} + exit 0 ;; + macppc:NetBSD:*:*) + echo powerpc-apple-netbsd${UNAME_RELEASE} + exit 0 ;; + RISC*:Mach:*:*) + echo mips-dec-mach_bsd4.3 + exit 0 ;; + RISC*:ULTRIX:*:*) + echo mips-dec-ultrix${UNAME_RELEASE} + exit 0 ;; + VAX*:ULTRIX*:*:*) + echo vax-dec-ultrix${UNAME_RELEASE} + exit 0 ;; + 2020:CLIX:*:* | 2430:CLIX:*:*) + echo clipper-intergraph-clix${UNAME_RELEASE} + exit 0 ;; + mips:*:*:UMIPS | mips:*:*:RISCos) + sed 's/^ //' << EOF >$dummy.c +#ifdef __cplusplus + int main (int argc, char *argv[]) { +#else + int main (argc, argv) int argc; char *argv[]; { +#endif + #if defined (host_mips) && defined (MIPSEB) + #if defined (SYSTYPE_SYSV) + printf ("mips-mips-riscos%ssysv\n", argv[1]); exit (0); + #endif + #if defined (SYSTYPE_SVR4) + printf ("mips-mips-riscos%ssvr4\n", argv[1]); exit (0); + #endif + #if defined (SYSTYPE_BSD43) || defined(SYSTYPE_BSD) + printf ("mips-mips-riscos%sbsd\n", argv[1]); exit (0); + #endif + #endif + exit (-1); + } +EOF + $CC_FOR_BUILD $dummy.c -o $dummy \ + && ./$dummy `echo "${UNAME_RELEASE}" | sed -n 's/\([0-9]*\).*/\1/p'` \ + && rm $dummy.c $dummy && exit 0 + rm -f $dummy.c $dummy + echo mips-mips-riscos${UNAME_RELEASE} + exit 0 ;; + Night_Hawk:Power_UNIX:*:*) + echo powerpc-harris-powerunix + exit 0 ;; + m88k:CX/UX:7*:*) + echo m88k-harris-cxux7 + exit 0 ;; + m88k:*:4*:R4*) + echo m88k-motorola-sysv4 + exit 0 ;; + m88k:*:3*:R3*) + echo m88k-motorola-sysv3 + exit 0 ;; + AViiON:dgux:*:*) + # DG/UX returns AViiON for all architectures + UNAME_PROCESSOR=`/usr/bin/uname -p` + if [ $UNAME_PROCESSOR = mc88100 -o $UNAME_PROCESSOR = mc88110 ] ; then + if [ ${TARGET_BINARY_INTERFACE}x = m88kdguxelfx \ + -o ${TARGET_BINARY_INTERFACE}x = x ] ; then + echo m88k-dg-dgux${UNAME_RELEASE} + else + echo m88k-dg-dguxbcs${UNAME_RELEASE} + fi + else echo i586-dg-dgux${UNAME_RELEASE} + fi + exit 0 ;; + M88*:DolphinOS:*:*) # DolphinOS (SVR3) + echo m88k-dolphin-sysv3 + exit 0 ;; + M88*:*:R3*:*) + # Delta 88k system running SVR3 + echo m88k-motorola-sysv3 + exit 0 ;; + XD88*:*:*:*) # Tektronix XD88 system running UTekV (SVR3) + echo m88k-tektronix-sysv3 + exit 0 ;; + Tek43[0-9][0-9]:UTek:*:*) # Tektronix 4300 system running UTek (BSD) + echo m68k-tektronix-bsd + exit 0 ;; + *:IRIX*:*:*) + echo mips-sgi-irix`echo ${UNAME_RELEASE}|sed -e 's/-/_/g'` + exit 0 ;; + ????????:AIX?:[12].1:2) # AIX 2.2.1 or AIX 2.1.1 is RT/PC AIX. + echo romp-ibm-aix # uname -m gives an 8 hex-code CPU id + exit 0 ;; # Note that: echo "'`uname -s`'" gives 'AIX ' + i?86:AIX:*:*) + echo i386-ibm-aix + exit 0 ;; + *:AIX:2:3) + if grep bos325 /usr/include/stdio.h >/dev/null 2>&1; then + sed 's/^ //' << EOF >$dummy.c + #include + + main() + { + if (!__power_pc()) + exit(1); + puts("powerpc-ibm-aix3.2.5"); + exit(0); + } +EOF + $CC_FOR_BUILD $dummy.c -o $dummy && ./$dummy && rm $dummy.c $dummy && exit 0 + rm -f $dummy.c $dummy + echo rs6000-ibm-aix3.2.5 + elif grep bos324 /usr/include/stdio.h >/dev/null 2>&1; then + echo rs6000-ibm-aix3.2.4 + else + echo rs6000-ibm-aix3.2 + fi + exit 0 ;; + *:AIX:*:4) + IBM_CPU_ID=`/usr/sbin/lsdev -C -c processor -S available | head -1 | awk '{ print $1 }'` + if /usr/sbin/lsattr -EHl ${IBM_CPU_ID} | grep POWER >/dev/null 2>&1; then + IBM_ARCH=rs6000 + else + IBM_ARCH=powerpc + fi + if [ -x /usr/bin/oslevel ] ; then + IBM_REV=`/usr/bin/oslevel` + else + IBM_REV=4.${UNAME_RELEASE} + fi + echo ${IBM_ARCH}-ibm-aix${IBM_REV} + exit 0 ;; + *:AIX:*:*) + echo rs6000-ibm-aix + exit 0 ;; + ibmrt:4.4BSD:*|romp-ibm:BSD:*) + echo romp-ibm-bsd4.4 + exit 0 ;; + ibmrt:*BSD:*|romp-ibm:BSD:*) # covers RT/PC NetBSD and + echo romp-ibm-bsd${UNAME_RELEASE} # 4.3 with uname added to + exit 0 ;; # report: romp-ibm BSD 4.3 + *:BOSX:*:*) + echo rs6000-bull-bosx + exit 0 ;; + DPX/2?00:B.O.S.:*:*) + echo m68k-bull-sysv3 + exit 0 ;; + 9000/[34]??:4.3bsd:1.*:*) + echo m68k-hp-bsd + exit 0 ;; + hp300:4.4BSD:*:* | 9000/[34]??:4.3bsd:2.*:*) + echo m68k-hp-bsd4.4 + exit 0 ;; + 9000/[34678]??:HP-UX:*:*) + case "${UNAME_MACHINE}" in + 9000/31? ) HP_ARCH=m68000 ;; + 9000/[34]?? ) HP_ARCH=m68k ;; + 9000/[678][0-9][0-9]) + sed 's/^ //' << EOF >$dummy.c + #include + #include + + int main () + { + #if defined(_SC_KERNEL_BITS) + long bits = sysconf(_SC_KERNEL_BITS); + #endif + long cpu = sysconf (_SC_CPU_VERSION); + + switch (cpu) + { + case CPU_PA_RISC1_0: puts ("hppa1.0"); break; + case CPU_PA_RISC1_1: puts ("hppa1.1"); break; + case CPU_PA_RISC2_0: + #if defined(_SC_KERNEL_BITS) + switch (bits) + { + case 64: puts ("hppa2.0w"); break; + case 32: puts ("hppa2.0n"); break; + default: puts ("hppa2.0"); break; + } break; + #else /* !defined(_SC_KERNEL_BITS) */ + puts ("hppa2.0"); break; + #endif + default: puts ("hppa1.0"); break; + } + exit (0); + } +EOF + ($CC_FOR_BUILD $dummy.c -o $dummy 2>/dev/null ) && HP_ARCH=`./$dummy` + rm -f $dummy.c $dummy + esac + HPUX_REV=`echo ${UNAME_RELEASE}|sed -e 's/[^.]*.[0B]*//'` + echo ${HP_ARCH}-hp-hpux${HPUX_REV} + exit 0 ;; + 3050*:HI-UX:*:*) + sed 's/^ //' << EOF >$dummy.c + #include + int + main () + { + long cpu = sysconf (_SC_CPU_VERSION); + /* The order matters, because CPU_IS_HP_MC68K erroneously returns + true for CPU_PA_RISC1_0. CPU_IS_PA_RISC returns correct + results, however. */ + if (CPU_IS_PA_RISC (cpu)) + { + switch (cpu) + { + case CPU_PA_RISC1_0: puts ("hppa1.0-hitachi-hiuxwe2"); break; + case CPU_PA_RISC1_1: puts ("hppa1.1-hitachi-hiuxwe2"); break; + case CPU_PA_RISC2_0: puts ("hppa2.0-hitachi-hiuxwe2"); break; + default: puts ("hppa-hitachi-hiuxwe2"); break; + } + } + else if (CPU_IS_HP_MC68K (cpu)) + puts ("m68k-hitachi-hiuxwe2"); + else puts ("unknown-hitachi-hiuxwe2"); + exit (0); + } +EOF + $CC_FOR_BUILD $dummy.c -o $dummy && ./$dummy && rm $dummy.c $dummy && exit 0 + rm -f $dummy.c $dummy + echo unknown-hitachi-hiuxwe2 + exit 0 ;; + 9000/7??:4.3bsd:*:* | 9000/8?[79]:4.3bsd:*:* ) + echo hppa1.1-hp-bsd + exit 0 ;; + 9000/8??:4.3bsd:*:*) + echo hppa1.0-hp-bsd + exit 0 ;; + *9??*:MPE/iX:*:*) + echo hppa1.0-hp-mpeix + exit 0 ;; + hp7??:OSF1:*:* | hp8?[79]:OSF1:*:* ) + echo hppa1.1-hp-osf + exit 0 ;; + hp8??:OSF1:*:*) + echo hppa1.0-hp-osf + exit 0 ;; + i?86:OSF1:*:*) + if [ -x /usr/sbin/sysversion ] ; then + echo ${UNAME_MACHINE}-unknown-osf1mk + else + echo ${UNAME_MACHINE}-unknown-osf1 + fi + exit 0 ;; + parisc*:Lites*:*:*) + echo hppa1.1-hp-lites + exit 0 ;; + hppa*:OpenBSD:*:*) + echo hppa-unknown-openbsd + exit 0 ;; + C1*:ConvexOS:*:* | convex:ConvexOS:C1*:*) + echo c1-convex-bsd + exit 0 ;; + C2*:ConvexOS:*:* | convex:ConvexOS:C2*:*) + if getsysinfo -f scalar_acc + then echo c32-convex-bsd + else echo c2-convex-bsd + fi + exit 0 ;; + C34*:ConvexOS:*:* | convex:ConvexOS:C34*:*) + echo c34-convex-bsd + exit 0 ;; + C38*:ConvexOS:*:* | convex:ConvexOS:C38*:*) + echo c38-convex-bsd + exit 0 ;; + C4*:ConvexOS:*:* | convex:ConvexOS:C4*:*) + echo c4-convex-bsd + exit 0 ;; + CRAY*X-MP:*:*:*) + echo xmp-cray-unicos + exit 0 ;; + CRAY*Y-MP:*:*:*) + echo ymp-cray-unicos${UNAME_RELEASE} + exit 0 ;; + CRAY*[A-Z]90:*:*:*) + echo ${UNAME_MACHINE}-cray-unicos${UNAME_RELEASE} \ + | sed -e 's/CRAY.*\([A-Z]90\)/\1/' \ + -e y/ABCDEFGHIJKLMNOPQRSTUVWXYZ/abcdefghijklmnopqrstuvwxyz/ + exit 0 ;; + CRAY*TS:*:*:*) + echo t90-cray-unicos${UNAME_RELEASE} + exit 0 ;; + CRAY*T3E:*:*:*) + echo alpha-cray-unicosmk${UNAME_RELEASE} + exit 0 ;; + CRAY-2:*:*:*) + echo cray2-cray-unicos + exit 0 ;; + F300:UNIX_System_V:*:*) + FUJITSU_SYS=`uname -p | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz' | sed -e 's/\///'` + FUJITSU_REL=`echo ${UNAME_RELEASE} | sed -e 's/ /_/'` + echo "f300-fujitsu-${FUJITSU_SYS}${FUJITSU_REL}" + exit 0 ;; + F301:UNIX_System_V:*:*) + echo f301-fujitsu-uxpv`echo $UNAME_RELEASE | sed 's/ .*//'` + exit 0 ;; + hp3[0-9][05]:NetBSD:*:*) + echo m68k-hp-netbsd${UNAME_RELEASE} + exit 0 ;; + hp300:OpenBSD:*:*) + echo m68k-unknown-openbsd${UNAME_RELEASE} + exit 0 ;; + i?86:BSD/386:*:* | i?86:BSD/OS:*:*) + echo ${UNAME_MACHINE}-pc-bsdi${UNAME_RELEASE} + exit 0 ;; + sparc*:BSD/OS:*:*) + echo sparc-unknown-bsdi${UNAME_RELEASE} + exit 0 ;; + *:BSD/OS:*:*) + echo ${UNAME_MACHINE}-unknown-bsdi${UNAME_RELEASE} + exit 0 ;; + *:FreeBSD:*:*) + if test -x /usr/bin/objformat; then + if test "elf" = "`/usr/bin/objformat`"; then + echo ${UNAME_MACHINE}-unknown-freebsdelf`echo ${UNAME_RELEASE}|sed -e 's/[-_].*//'` + exit 0 + fi + fi + echo ${UNAME_MACHINE}-unknown-freebsd`echo ${UNAME_RELEASE}|sed -e 's/[-(].*//'` + exit 0 ;; + *:NetBSD:*:*) + echo ${UNAME_MACHINE}-unknown-netbsd`echo ${UNAME_RELEASE}|sed -e 's/[-_].*/\./'` + exit 0 ;; + *:OpenBSD:*:*) + echo ${UNAME_MACHINE}-unknown-openbsd`echo ${UNAME_RELEASE}|sed -e 's/[-_].*/\./'` + exit 0 ;; + i*:CYGWIN*:*) + echo ${UNAME_MACHINE}-pc-cygwin + exit 0 ;; + i*:MINGW*:*) + echo ${UNAME_MACHINE}-pc-mingw32 + exit 0 ;; + i*:Windows_NT*:* | Pentium*:Windows_NT*:*) + # How do we know it's Interix rather than the generic POSIX subsystem? + # It also conflicts with pre-2.0 versions of AT&T UWIN. Should we + # UNAME_MACHINE based on the output of uname instead of i386? + echo i386-pc-interix + exit 0 ;; + i*:UWIN*:*) + echo ${UNAME_MACHINE}-pc-uwin + exit 0 ;; + p*:CYGWIN*:*) + echo powerpcle-unknown-cygwin + exit 0 ;; + prep*:SunOS:5.*:*) + echo powerpcle-unknown-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` + exit 0 ;; + *:GNU:*:*) + echo `echo ${UNAME_MACHINE}|sed -e 's,[-/].*$,,'`-unknown-gnu`echo ${UNAME_RELEASE}|sed -e 's,/.*$,,'` + exit 0 ;; + *:Linux:*:*) + # uname on the ARM produces all sorts of strangeness, and we need to + # filter it out. + case "$UNAME_MACHINE" in + armv*) UNAME_MACHINE=$UNAME_MACHINE ;; + arm* | sa110*) UNAME_MACHINE="arm" ;; + esac + + # The BFD linker knows what the default object file format is, so + # first see if it will tell us. cd to the root directory to prevent + # problems with other programs or directories called `ld' in the path. + ld_help_string=`cd /; ld --help 2>&1` + ld_supported_emulations=`echo $ld_help_string \ + | sed -ne '/supported emulations:/!d + s/[ ][ ]*/ /g + s/.*supported emulations: *// + s/ .*// + p'` + case "$ld_supported_emulations" in + *ia64) echo "${UNAME_MACHINE}-unknown-linux" ; exit 0 ;; + i?86linux) echo "${UNAME_MACHINE}-pc-linux-gnuaout" ; exit 0 ;; + i?86coff) echo "${UNAME_MACHINE}-pc-linux-gnucoff" ; exit 0 ;; + sparclinux) echo "${UNAME_MACHINE}-unknown-linux-gnuaout" ; exit 0 ;; + armlinux) echo "${UNAME_MACHINE}-unknown-linux-gnuaout" ; exit 0 ;; + m68klinux) echo "${UNAME_MACHINE}-unknown-linux-gnuaout" ; exit 0 ;; + elf32ppc | elf32ppclinux) + # Determine Lib Version + cat >$dummy.c < +#if defined(__GLIBC__) +extern char __libc_version[]; +extern char __libc_release[]; +#endif +main(argc, argv) + int argc; + char *argv[]; +{ +#if defined(__GLIBC__) + printf("%s %s\n", __libc_version, __libc_release); +#else + printf("unkown\n"); +#endif + return 0; +} +EOF + LIBC="" + $CC_FOR_BUILD $dummy.c -o $dummy 2>/dev/null + if test "$?" = 0 ; then + ./$dummy | grep 1\.99 > /dev/null + if test "$?" = 0 ; then + LIBC="libc1" + fi + fi + rm -f $dummy.c $dummy + echo powerpc-unknown-linux-gnu${LIBC} ; exit 0 ;; + esac + + if test "${UNAME_MACHINE}" = "alpha" ; then + sed 's/^ //' <$dummy.s + .globl main + .ent main + main: + .frame \$30,0,\$26,0 + .prologue 0 + .long 0x47e03d80 # implver $0 + lda \$2,259 + .long 0x47e20c21 # amask $2,$1 + srl \$1,8,\$2 + sll \$2,2,\$2 + sll \$0,3,\$0 + addl \$1,\$0,\$0 + addl \$2,\$0,\$0 + ret \$31,(\$26),1 + .end main +EOF + LIBC="" + $CC_FOR_BUILD $dummy.s -o $dummy 2>/dev/null + if test "$?" = 0 ; then + ./$dummy + case "$?" in + 7) + UNAME_MACHINE="alpha" + ;; + 15) + UNAME_MACHINE="alphaev5" + ;; + 14) + UNAME_MACHINE="alphaev56" + ;; + 10) + UNAME_MACHINE="alphapca56" + ;; + 16) + UNAME_MACHINE="alphaev6" + ;; + esac + + objdump --private-headers $dummy | \ + grep ld.so.1 > /dev/null + if test "$?" = 0 ; then + LIBC="libc1" + fi + fi + rm -f $dummy.s $dummy + echo ${UNAME_MACHINE}-unknown-linux-gnu${LIBC} ; exit 0 + elif test "${UNAME_MACHINE}" = "mips" ; then + cat >$dummy.c </dev/null && ./$dummy "${UNAME_MACHINE}" && rm $dummy.c $dummy && exit 0 + rm -f $dummy.c $dummy + else + # Either a pre-BFD a.out linker (linux-gnuoldld) + # or one that does not give us useful --help. + # GCC wants to distinguish between linux-gnuoldld and linux-gnuaout. + # If ld does not provide *any* "supported emulations:" + # that means it is gnuoldld. + echo "$ld_help_string" | grep >/dev/null 2>&1 "supported emulations:" + test $? != 0 && echo "${UNAME_MACHINE}-pc-linux-gnuoldld" && exit 0 + + case "${UNAME_MACHINE}" in + i?86) + VENDOR=pc; + ;; + *) + VENDOR=unknown; + ;; + esac + # Determine whether the default compiler is a.out or elf + cat >$dummy.c < +#ifdef __cplusplus + int main (int argc, char *argv[]) { +#else + int main (argc, argv) int argc; char *argv[]; { +#endif +#ifdef __ELF__ +# ifdef __GLIBC__ +# if __GLIBC__ >= 2 + printf ("%s-${VENDOR}-linux-gnu\n", argv[1]); +# else + printf ("%s-${VENDOR}-linux-gnulibc1\n", argv[1]); +# endif +# else + printf ("%s-${VENDOR}-linux-gnulibc1\n", argv[1]); +# endif +#else + printf ("%s-${VENDOR}-linux-gnuaout\n", argv[1]); +#endif + return 0; +} +EOF + $CC_FOR_BUILD $dummy.c -o $dummy 2>/dev/null && ./$dummy "${UNAME_MACHINE}" && rm $dummy.c $dummy && exit 0 + rm -f $dummy.c $dummy + fi ;; +# ptx 4.0 does uname -s correctly, with DYNIX/ptx in there. earlier versions +# are messed up and put the nodename in both sysname and nodename. + i?86:DYNIX/ptx:4*:*) + echo i386-sequent-sysv4 + exit 0 ;; + i?86:UNIX_SV:4.2MP:2.*) + # Unixware is an offshoot of SVR4, but it has its own version + # number series starting with 2... + # I am not positive that other SVR4 systems won't match this, + # I just have to hope. -- rms. + # Use sysv4.2uw... so that sysv4* matches it. + echo ${UNAME_MACHINE}-pc-sysv4.2uw${UNAME_VERSION} + exit 0 ;; + i?86:*:4.*:* | i?86:SYSTEM_V:4.*:*) + if grep Novell /usr/include/link.h >/dev/null 2>/dev/null; then + echo ${UNAME_MACHINE}-univel-sysv${UNAME_RELEASE} + else + echo ${UNAME_MACHINE}-pc-sysv${UNAME_RELEASE} + fi + exit 0 ;; + i?86:*:5:7*) + UNAME_REL=`(/bin/uname -X|egrep Release|sed -e 's/.*= //')` + (/bin/uname -X|egrep i80486 >/dev/null) && UNAME_MACHINE=i486 + (/bin/uname -X|egrep '^Machine.*Pentium' >/dev/null) && UNAME_MACHINE=i586 + (/bin/uname -X|egrep '^Machine.*Pent.*II' >/dev/null) && UNAME_MACHINE=i686 + (/bin/uname -X|egrep '^Machine.*Pentium Pro' >/dev/null) && UNAME_MACHINE=i585 + echo ${UNAME_MACHINE}-${UNAME_SYSTEM}${UNAME_VERSION}-sysv${UNAME_RELEASE} + exit 0 ;; + i?86:*:3.2:*) + if test -f /usr/options/cb.name; then + UNAME_REL=`sed -n 's/.*Version //p' /dev/null >/dev/null ; then + UNAME_REL=`(/bin/uname -X|egrep Release|sed -e 's/.*= //')` + (/bin/uname -X|egrep i80486 >/dev/null) && UNAME_MACHINE=i486 + (/bin/uname -X|egrep '^Machine.*Pentium' >/dev/null) \ + && UNAME_MACHINE=i586 + (/bin/uname -X|egrep '^Machine.*Pent ?II' >/dev/null) \ + && UNAME_MACHINE=i686 + (/bin/uname -X|egrep '^Machine.*Pentium Pro' >/dev/null) \ + && UNAME_MACHINE=i686 + echo ${UNAME_MACHINE}-pc-sco$UNAME_REL + else + echo ${UNAME_MACHINE}-pc-sysv32 + fi + exit 0 ;; + pc:*:*:*) + # uname -m prints for DJGPP always 'pc', but it prints nothing about + # the processor, so we play safe by assuming i386. + echo i386-pc-msdosdjgpp + exit 0 ;; + Intel:Mach:3*:*) + echo i386-pc-mach3 + exit 0 ;; + paragon:*:*:*) + echo i860-intel-osf1 + exit 0 ;; + i860:*:4.*:*) # i860-SVR4 + if grep Stardent /usr/include/sys/uadmin.h >/dev/null 2>&1 ; then + echo i860-stardent-sysv${UNAME_RELEASE} # Stardent Vistra i860-SVR4 + else # Add other i860-SVR4 vendors below as they are discovered. + echo i860-unknown-sysv${UNAME_RELEASE} # Unknown i860-SVR4 + fi + exit 0 ;; + mini*:CTIX:SYS*5:*) + # "miniframe" + echo m68010-convergent-sysv + exit 0 ;; + M68*:*:R3V[567]*:*) + test -r /sysV68 && echo 'm68k-motorola-sysv' && exit 0 ;; + 3[34]??:*:4.0:3.0 | 3[34]??,*:*:4.0:3.0 | 4850:*:4.0:3.0) + OS_REL='' + test -r /etc/.relid \ + && OS_REL=.`sed -n 's/[^ ]* [^ ]* \([0-9][0-9]\).*/\1/p' < /etc/.relid` + /bin/uname -p 2>/dev/null | grep 86 >/dev/null \ + && echo i486-ncr-sysv4.3${OS_REL} && exit 0 + /bin/uname -p 2>/dev/null | /bin/grep entium >/dev/null \ + && echo i586-ncr-sysv4.3${OS_REL} && exit 0 ;; + 3[34]??:*:4.0:* | 3[34]??,*:*:4.0:*) + /bin/uname -p 2>/dev/null | grep 86 >/dev/null \ + && echo i486-ncr-sysv4 && exit 0 ;; + m68*:LynxOS:2.*:*) + echo m68k-unknown-lynxos${UNAME_RELEASE} + exit 0 ;; + mc68030:UNIX_System_V:4.*:*) + echo m68k-atari-sysv4 + exit 0 ;; + i?86:LynxOS:2.*:* | i?86:LynxOS:3.[01]*:*) + echo i386-unknown-lynxos${UNAME_RELEASE} + exit 0 ;; + TSUNAMI:LynxOS:2.*:*) + echo sparc-unknown-lynxos${UNAME_RELEASE} + exit 0 ;; + rs6000:LynxOS:2.*:* | PowerPC:LynxOS:2.*:*) + echo rs6000-unknown-lynxos${UNAME_RELEASE} + exit 0 ;; + SM[BE]S:UNIX_SV:*:*) + echo mips-dde-sysv${UNAME_RELEASE} + exit 0 ;; + RM*:ReliantUNIX-*:*:*) + echo mips-sni-sysv4 + exit 0 ;; + RM*:SINIX-*:*:*) + echo mips-sni-sysv4 + exit 0 ;; + *:SINIX-*:*:*) + if uname -p 2>/dev/null >/dev/null ; then + UNAME_MACHINE=`(uname -p) 2>/dev/null` + echo ${UNAME_MACHINE}-sni-sysv4 + else + echo ns32k-sni-sysv + fi + exit 0 ;; + PENTIUM:CPunix:4.0*:*) # Unisys `ClearPath HMP IX 4000' SVR4/MP effort + # says + echo i586-unisys-sysv4 + exit 0 ;; + *:UNIX_System_V:4*:FTX*) + # From Gerald Hewes . + # How about differentiating between stratus architectures? -djm + echo hppa1.1-stratus-sysv4 + exit 0 ;; + *:*:*:FTX*) + # From seanf@swdc.stratus.com. + echo i860-stratus-sysv4 + exit 0 ;; + mc68*:A/UX:*:*) + echo m68k-apple-aux${UNAME_RELEASE} + exit 0 ;; + news*:NEWS-OS:*:6*) + echo mips-sony-newsos6 + exit 0 ;; + R[34]000:*System_V*:*:* | R4000:UNIX_SYSV:*:* | R*000:UNIX_SV:*:*) + if [ -d /usr/nec ]; then + echo mips-nec-sysv${UNAME_RELEASE} + else + echo mips-unknown-sysv${UNAME_RELEASE} + fi + exit 0 ;; + BeBox:BeOS:*:*) # BeOS running on hardware made by Be, PPC only. + echo powerpc-be-beos + exit 0 ;; + BeMac:BeOS:*:*) # BeOS running on Mac or Mac clone, PPC only. + echo powerpc-apple-beos + exit 0 ;; + BePC:BeOS:*:*) # BeOS running on Intel PC compatible. + echo i586-pc-beos + exit 0 ;; + SX-4:SUPER-UX:*:*) + echo sx4-nec-superux${UNAME_RELEASE} + exit 0 ;; + SX-5:SUPER-UX:*:*) + echo sx5-nec-superux${UNAME_RELEASE} + exit 0 ;; + Power*:Rhapsody:*:*) + echo powerpc-apple-rhapsody${UNAME_RELEASE} + exit 0 ;; + *:Rhapsody:*:*) + echo ${UNAME_MACHINE}-apple-rhapsody${UNAME_RELEASE} + exit 0 ;; +esac + +#echo '(No uname command or uname output not recognized.)' 1>&2 +#echo "${UNAME_MACHINE}:${UNAME_SYSTEM}:${UNAME_RELEASE}:${UNAME_VERSION}" 1>&2 + +cat >$dummy.c < +# include +#endif +main () +{ +#if defined (sony) +#if defined (MIPSEB) + /* BFD wants "bsd" instead of "newsos". Perhaps BFD should be changed, + I don't know.... */ + printf ("mips-sony-bsd\n"); exit (0); +#else +#include + printf ("m68k-sony-newsos%s\n", +#ifdef NEWSOS4 + "4" +#else + "" +#endif + ); exit (0); +#endif +#endif + +#if defined (__arm) && defined (__acorn) && defined (__unix) + printf ("arm-acorn-riscix"); exit (0); +#endif + +#if defined (hp300) && !defined (hpux) + printf ("m68k-hp-bsd\n"); exit (0); +#endif + +#if defined (NeXT) +#if !defined (__ARCHITECTURE__) +#define __ARCHITECTURE__ "m68k" +#endif + int version; + version=`(hostinfo | sed -n 's/.*NeXT Mach \([0-9]*\).*/\1/p') 2>/dev/null`; + if (version < 4) + printf ("%s-next-nextstep%d\n", __ARCHITECTURE__, version); + else + printf ("%s-next-openstep%d\n", __ARCHITECTURE__, version); + exit (0); +#endif + +#if defined (MULTIMAX) || defined (n16) +#if defined (UMAXV) + printf ("ns32k-encore-sysv\n"); exit (0); +#else +#if defined (CMU) + printf ("ns32k-encore-mach\n"); exit (0); +#else + printf ("ns32k-encore-bsd\n"); exit (0); +#endif +#endif +#endif + +#if defined (__386BSD__) + printf ("i386-pc-bsd\n"); exit (0); +#endif + +#if defined (sequent) +#if defined (i386) + printf ("i386-sequent-dynix\n"); exit (0); +#endif +#if defined (ns32000) + printf ("ns32k-sequent-dynix\n"); exit (0); +#endif +#endif + +#if defined (_SEQUENT_) + struct utsname un; + + uname(&un); + + if (strncmp(un.version, "V2", 2) == 0) { + printf ("i386-sequent-ptx2\n"); exit (0); + } + if (strncmp(un.version, "V1", 2) == 0) { /* XXX is V1 correct? */ + printf ("i386-sequent-ptx1\n"); exit (0); + } + printf ("i386-sequent-ptx\n"); exit (0); + +#endif + +#if defined (vax) +#if !defined (ultrix) + printf ("vax-dec-bsd\n"); exit (0); +#else + printf ("vax-dec-ultrix\n"); exit (0); +#endif +#endif + +#if defined (alliant) && defined (i860) + printf ("i860-alliant-bsd\n"); exit (0); +#endif + + exit (1); +} +EOF + +$CC_FOR_BUILD $dummy.c -o $dummy 2>/dev/null && ./$dummy && rm $dummy.c $dummy && exit 0 +rm -f $dummy.c $dummy + +# Apollos put the system type in the environment. + +test -d /usr/apollo && { echo ${ISP}-apollo-${SYSTYPE}; exit 0; } + +# Convex versions that predate uname can use getsysinfo(1) + +if [ -x /usr/convex/getsysinfo ] +then + case `getsysinfo -f cpu_type` in + c1*) + echo c1-convex-bsd + exit 0 ;; + c2*) + if getsysinfo -f scalar_acc + then echo c32-convex-bsd + else echo c2-convex-bsd + fi + exit 0 ;; + c34*) + echo c34-convex-bsd + exit 0 ;; + c38*) + echo c38-convex-bsd + exit 0 ;; + c4*) + echo c4-convex-bsd + exit 0 ;; + esac +fi + +#echo '(Unable to guess system type)' 1>&2 + +exit 1 diff --git a/nufxlib-0/config.h.in b/nufxlib-0/config.h.in new file mode 100644 index 0000000..dac1c9d --- /dev/null +++ b/nufxlib-0/config.h.in @@ -0,0 +1,125 @@ +/* config.h.in. Generated automatically from configure.in by autoheader. */ + +/* Define to empty if the keyword does not work. */ +#undef const + +/* Define to empty if the keyword does not work. */ +#undef inline + +/* Define to `int' if doesn't define. */ +#undef mode_t + +/* Define to `long' if doesn't define. */ +#undef off_t + +/* Define to `unsigned' if doesn't define. */ +#undef size_t + +/* Define if you have the ANSI C header files. */ +#undef STDC_HEADERS + +/* Define if your declares struct tm. */ +#undef TM_IN_SYS_TIME + +/* Define to `unsigned char' if doesn't define. */ +#undef uchar + +/* Define to `unsigned short' if doesn't define. */ +#undef ushort + +/* Define to `unsigned int' if doesn't define. */ +#undef uint + +/* Define to `unsigned long' if doesn't define. */ +#undef ulong + +/* Define to `int' if doesn't define. */ +#undef mode_t + +/* Define to `long' if doesn't define. */ +#undef off_t + +/* Define to `unsigned' if doesn't define. */ +#undef size_t + +/* Define if you have the fdopen function. */ +#undef HAVE_FDOPEN + +/* Define if you have the ftruncate function. */ +#undef HAVE_FTRUNCATE + +/* Define if you have the localtime_r function. */ +#undef HAVE_LOCALTIME_R + +/* Define if you have the memmove function. */ +#undef HAVE_MEMMOVE + +/* Define if you have the mkdir function. */ +#undef HAVE_MKDIR + +/* Define if you have the mkstemp function. */ +#undef HAVE_MKSTEMP + +/* Define if you have the mktime function. */ +#undef HAVE_MKTIME + +/* Define if you have the snprintf function. */ +#undef HAVE_SNPRINTF + +/* Define if you have the strcasecmp function. */ +#undef HAVE_STRCASECMP + +/* Define if you have the strncasecmp function. */ +#undef HAVE_STRNCASECMP + +/* Define if you have the strerror function. */ +#undef HAVE_STRERROR + +/* Define if you have the strtoul function. */ +#undef HAVE_STRTOUL + +/* Define if you have the timelocal function. */ +#undef HAVE_TIMELOCAL + +/* Define if you have the vsnprintf function. */ +#undef HAVE_VSNPRINTF + +/* Define if you have the header file. */ +#undef HAVE_FCNTL_H + +/* Define if you have the header file. */ +#undef HAVE_MALLOC_H + +/* Define if you have the header file. */ +#undef HAVE_STDLIB_H + +/* Define if you have the header file. */ +#undef HAVE_SYS_STAT_H + +/* Define if you have the header file. */ +#undef HAVE_SYS_TIME_H + +/* Define if you have the header file. */ +#undef HAVE_SYS_TYPES_H + +/* Define if you have the header file. */ +#undef HAVE_SYS_UTIME_H + +/* Define if you have the header file. */ +#undef HAVE_UNISTD_H + +/* Define if you have the header file. */ +#undef HAVE_UTIME_H + +/* Define if sprintf returns an int. */ +#undef SPRINTF_RETURNS_INT + +/* Define if SNPRINTF is declared in stdio.h. */ +#undef SNPRINTF_DECLARED + +/* Define if VSNPRINTF is declared in stdio.h. */ +#undef VSNPRINTF_DECLARED + +/* Define if we want to use the dmalloc library (--enable-dmalloc). */ +#undef USE_DMALLOC + diff --git a/nufxlib-0/config.sub b/nufxlib-0/config.sub new file mode 100644 index 0000000..ec508a5 --- /dev/null +++ b/nufxlib-0/config.sub @@ -0,0 +1,1220 @@ +#! /bin/sh +# Configuration validation subroutine script, version 1.1. +# Copyright (C) 1991, 92-97, 1998, 1999 Free Software Foundation, Inc. +# This file is (in principle) common to ALL GNU software. +# The presence of a machine in this file suggests that SOME GNU software +# can handle that machine. It does not imply ALL GNU software can. +# +# This file is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, +# Boston, MA 02111-1307, USA. + +# As a special exception to the GNU General Public License, if you +# distribute this file as part of a program that contains a +# configuration script generated by Autoconf, you may include it under +# the same distribution terms that you use for the rest of that program. + +# Configuration subroutine to validate and canonicalize a configuration type. +# Supply the specified configuration type as an argument. +# If it is invalid, we print an error message on stderr and exit with code 1. +# Otherwise, we print the canonical config type on stdout and succeed. + +# This file is supposed to be the same for all GNU packages +# and recognize all the CPU types, system types and aliases +# that are meaningful with *any* GNU software. +# Each package is responsible for reporting which valid configurations +# it does not support. The user should be able to distinguish +# a failure to support a valid configuration from a meaningless +# configuration. + +# The goal of this file is to map all the various variations of a given +# machine specification into a single specification in the form: +# CPU_TYPE-MANUFACTURER-OPERATING_SYSTEM +# or in some cases, the newer four-part form: +# CPU_TYPE-MANUFACTURER-KERNEL-OPERATING_SYSTEM +# It is wrong to echo any other type of specification. + +if [ x$1 = x ] +then + echo Configuration name missing. 1>&2 + echo "Usage: $0 CPU-MFR-OPSYS" 1>&2 + echo "or $0 ALIAS" 1>&2 + echo where ALIAS is a recognized configuration type. 1>&2 + exit 1 +fi + +# First pass through any local machine types. +case $1 in + *local*) + echo $1 + exit 0 + ;; + *) + ;; +esac + +# Separate what the user gave into CPU-COMPANY and OS or KERNEL-OS (if any). +# Here we must recognize all the valid KERNEL-OS combinations. +maybe_os=`echo $1 | sed 's/^\(.*\)-\([^-]*-[^-]*\)$/\2/'` +case $maybe_os in + linux-gnu*) + os=-$maybe_os + basic_machine=`echo $1 | sed 's/^\(.*\)-\([^-]*-[^-]*\)$/\1/'` + ;; + *) + basic_machine=`echo $1 | sed 's/-[^-]*$//'` + if [ $basic_machine != $1 ] + then os=`echo $1 | sed 's/.*-/-/'` + else os=; fi + ;; +esac + +### Let's recognize common machines as not being operating systems so +### that things like config.sub decstation-3100 work. We also +### recognize some manufacturers as not being operating systems, so we +### can provide default operating systems below. +case $os in + -sun*os*) + # Prevent following clause from handling this invalid input. + ;; + -dec* | -mips* | -sequent* | -encore* | -pc532* | -sgi* | -sony* | \ + -att* | -7300* | -3300* | -delta* | -motorola* | -sun[234]* | \ + -unicom* | -ibm* | -next | -hp | -isi* | -apollo | -altos* | \ + -convergent* | -ncr* | -news | -32* | -3600* | -3100* | -hitachi* |\ + -c[123]* | -convex* | -sun | -crds | -omron* | -dg | -ultra | -tti* | \ + -harris | -dolphin | -highlevel | -gould | -cbm | -ns | -masscomp | \ + -apple) + os= + basic_machine=$1 + ;; + -sim | -cisco | -oki | -wec | -winbond) + os= + basic_machine=$1 + ;; + -scout) + ;; + -wrs) + os=-vxworks + basic_machine=$1 + ;; + -hiux*) + os=-hiuxwe2 + ;; + -sco5) + os=-sco3.2v5 + basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` + ;; + -sco4) + os=-sco3.2v4 + basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` + ;; + -sco3.2.[4-9]*) + os=`echo $os | sed -e 's/sco3.2./sco3.2v/'` + basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` + ;; + -sco3.2v[4-9]*) + # Don't forget version if it is 3.2v4 or newer. + basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` + ;; + -sco*) + os=-sco3.2v2 + basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` + ;; + -udk*) + basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` + ;; + -isc) + os=-isc2.2 + basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` + ;; + -clix*) + basic_machine=clipper-intergraph + ;; + -isc*) + basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` + ;; + -lynx*) + os=-lynxos + ;; + -ptx*) + basic_machine=`echo $1 | sed -e 's/86-.*/86-sequent/'` + ;; + -windowsnt*) + os=`echo $os | sed -e 's/windowsnt/winnt/'` + ;; + -psos*) + os=-psos + ;; + -mint | -mint[0-9]*) + basic_machine=m68k-atari + os=-mint + ;; +esac + +# Decode aliases for certain CPU-COMPANY combinations. +case $basic_machine in + # Recognize the basic CPU types without company name. + # Some are omitted here because they have special meanings below. + tahoe | i860 | ia64 | m32r | m68k | m68000 | m88k | ns32k | arc | arm \ + | arme[lb] | pyramid | mn10200 | mn10300 | tron | a29k \ + | 580 | i960 | h8300 \ + | hppa | hppa1.0 | hppa1.1 | hppa2.0 | hppa2.0w | hppa2.0n \ + | alpha | alphaev[4-7] | alphaev56 | alphapca5[67] \ + | we32k | ns16k | clipper | i370 | sh | powerpc | powerpcle \ + | 1750a | dsp16xx | pdp11 | mips16 | mips64 | mipsel | mips64el \ + | mips64orion | mips64orionel | mipstx39 | mipstx39el \ + | mips64vr4300 | mips64vr4300el | mips64vr4100 | mips64vr4100el \ + | mips64vr5000 | miprs64vr5000el | mcore \ + | sparc | sparclet | sparclite | sparc64 | sparcv9 | v850 | c4x \ + | thumb | d10v) + basic_machine=$basic_machine-unknown + ;; + m88110 | m680[12346]0 | m683?2 | m68360 | m5200 | z8k | v70 | h8500 | w65) + ;; + + # We use `pc' rather than `unknown' + # because (1) that's what they normally are, and + # (2) the word "unknown" tends to confuse beginning users. + i[34567]86) + basic_machine=$basic_machine-pc + ;; + # Object if more than one company name word. + *-*-*) + echo Invalid configuration \`$1\': machine \`$basic_machine\' not recognized 1>&2 + exit 1 + ;; + # Recognize the basic CPU types with company name. + # FIXME: clean up the formatting here. + vax-* | tahoe-* | i[34567]86-* | i860-* | ia64-* | m32r-* | m68k-* | m68000-* \ + | m88k-* | sparc-* | ns32k-* | fx80-* | arc-* | arm-* | c[123]* \ + | mips-* | pyramid-* | tron-* | a29k-* | romp-* | rs6000-* \ + | power-* | none-* | 580-* | cray2-* | h8300-* | h8500-* | i960-* \ + | xmp-* | ymp-* \ + | hppa-* | hppa1.0-* | hppa1.1-* | hppa2.0-* | hppa2.0w-* | hppa2.0n-* \ + | alpha-* | alphaev[4-7]-* | alphaev56-* | alphapca5[67]-* \ + | we32k-* | cydra-* | ns16k-* | pn-* | np1-* | xps100-* \ + | clipper-* | orion-* \ + | sparclite-* | pdp11-* | sh-* | powerpc-* | powerpcle-* \ + | sparc64-* | sparcv9-* | sparc86x-* | mips16-* | mips64-* | mipsel-* \ + | mips64el-* | mips64orion-* | mips64orionel-* \ + | mips64vr4100-* | mips64vr4100el-* | mips64vr4300-* | mips64vr4300el-* \ + | mipstx39-* | mipstx39el-* | mcore-* \ + | f301-* | armv*-* | t3e-* \ + | m88110-* | m680[01234]0-* | m683?2-* | m68360-* | z8k-* | d10v-* \ + | thumb-* | v850-* | d30v-* | tic30-* | c30-* ) + ;; + # Recognize the various machine names and aliases which stand + # for a CPU type and a company and sometimes even an OS. + 386bsd) + basic_machine=i386-unknown + os=-bsd + ;; + 3b1 | 7300 | 7300-att | att-7300 | pc7300 | safari | unixpc) + basic_machine=m68000-att + ;; + 3b*) + basic_machine=we32k-att + ;; + a29khif) + basic_machine=a29k-amd + os=-udi + ;; + adobe68k) + basic_machine=m68010-adobe + os=-scout + ;; + alliant | fx80) + basic_machine=fx80-alliant + ;; + altos | altos3068) + basic_machine=m68k-altos + ;; + am29k) + basic_machine=a29k-none + os=-bsd + ;; + amdahl) + basic_machine=580-amdahl + os=-sysv + ;; + amiga | amiga-*) + basic_machine=m68k-cbm + ;; + amigaos | amigados) + basic_machine=m68k-cbm + os=-amigaos + ;; + amigaunix | amix) + basic_machine=m68k-cbm + os=-sysv4 + ;; + apollo68) + basic_machine=m68k-apollo + os=-sysv + ;; + apollo68bsd) + basic_machine=m68k-apollo + os=-bsd + ;; + aux) + basic_machine=m68k-apple + os=-aux + ;; + balance) + basic_machine=ns32k-sequent + os=-dynix + ;; + convex-c1) + basic_machine=c1-convex + os=-bsd + ;; + convex-c2) + basic_machine=c2-convex + os=-bsd + ;; + convex-c32) + basic_machine=c32-convex + os=-bsd + ;; + convex-c34) + basic_machine=c34-convex + os=-bsd + ;; + convex-c38) + basic_machine=c38-convex + os=-bsd + ;; + cray | ymp) + basic_machine=ymp-cray + os=-unicos + ;; + cray2) + basic_machine=cray2-cray + os=-unicos + ;; + [ctj]90-cray) + basic_machine=c90-cray + os=-unicos + ;; + crds | unos) + basic_machine=m68k-crds + ;; + da30 | da30-*) + basic_machine=m68k-da30 + ;; + decstation | decstation-3100 | pmax | pmax-* | pmin | dec3100 | decstatn) + basic_machine=mips-dec + ;; + delta | 3300 | motorola-3300 | motorola-delta \ + | 3300-motorola | delta-motorola) + basic_machine=m68k-motorola + ;; + delta88) + basic_machine=m88k-motorola + os=-sysv3 + ;; + dpx20 | dpx20-*) + basic_machine=rs6000-bull + os=-bosx + ;; + dpx2* | dpx2*-bull) + basic_machine=m68k-bull + os=-sysv3 + ;; + ebmon29k) + basic_machine=a29k-amd + os=-ebmon + ;; + elxsi) + basic_machine=elxsi-elxsi + os=-bsd + ;; + encore | umax | mmax) + basic_machine=ns32k-encore + ;; + es1800 | OSE68k | ose68k | ose | OSE) + basic_machine=m68k-ericsson + os=-ose + ;; + fx2800) + basic_machine=i860-alliant + ;; + genix) + basic_machine=ns32k-ns + ;; + gmicro) + basic_machine=tron-gmicro + os=-sysv + ;; + h3050r* | hiux*) + basic_machine=hppa1.1-hitachi + os=-hiuxwe2 + ;; + h8300hms) + basic_machine=h8300-hitachi + os=-hms + ;; + h8300xray) + basic_machine=h8300-hitachi + os=-xray + ;; + h8500hms) + basic_machine=h8500-hitachi + os=-hms + ;; + harris) + basic_machine=m88k-harris + os=-sysv3 + ;; + hp300-*) + basic_machine=m68k-hp + ;; + hp300bsd) + basic_machine=m68k-hp + os=-bsd + ;; + hp300hpux) + basic_machine=m68k-hp + os=-hpux + ;; + hp3k9[0-9][0-9] | hp9[0-9][0-9]) + basic_machine=hppa1.0-hp + ;; + hp9k2[0-9][0-9] | hp9k31[0-9]) + basic_machine=m68000-hp + ;; + hp9k3[2-9][0-9]) + basic_machine=m68k-hp + ;; + hp9k6[0-9][0-9] | hp6[0-9][0-9]) + basic_machine=hppa1.0-hp + ;; + hp9k7[0-79][0-9] | hp7[0-79][0-9]) + basic_machine=hppa1.1-hp + ;; + hp9k78[0-9] | hp78[0-9]) + # FIXME: really hppa2.0-hp + basic_machine=hppa1.1-hp + ;; + hp9k8[67]1 | hp8[67]1 | hp9k80[24] | hp80[24] | hp9k8[78]9 | hp8[78]9 | hp9k893 | hp893) + # FIXME: really hppa2.0-hp + basic_machine=hppa1.1-hp + ;; + hp9k8[0-9][13679] | hp8[0-9][13679]) + basic_machine=hppa1.1-hp + ;; + hp9k8[0-9][0-9] | hp8[0-9][0-9]) + basic_machine=hppa1.0-hp + ;; + hppa-next) + os=-nextstep3 + ;; + hppaosf) + basic_machine=hppa1.1-hp + os=-osf + ;; + hppro) + basic_machine=hppa1.1-hp + os=-proelf + ;; + i370-ibm* | ibm*) + basic_machine=i370-ibm + os=-mvs + ;; +# I'm not sure what "Sysv32" means. Should this be sysv3.2? + i[34567]86v32) + basic_machine=`echo $1 | sed -e 's/86.*/86-pc/'` + os=-sysv32 + ;; + i[34567]86v4*) + basic_machine=`echo $1 | sed -e 's/86.*/86-pc/'` + os=-sysv4 + ;; + i[34567]86v) + basic_machine=`echo $1 | sed -e 's/86.*/86-pc/'` + os=-sysv + ;; + i[34567]86sol2) + basic_machine=`echo $1 | sed -e 's/86.*/86-pc/'` + os=-solaris2 + ;; + i386mach) + basic_machine=i386-mach + os=-mach + ;; + i386-vsta | vsta) + basic_machine=i386-unknown + os=-vsta + ;; + i386-go32 | go32) + basic_machine=i386-unknown + os=-go32 + ;; + i386-mingw32 | mingw32) + basic_machine=i386-unknown + os=-mingw32 + ;; + iris | iris4d) + basic_machine=mips-sgi + case $os in + -irix*) + ;; + *) + os=-irix4 + ;; + esac + ;; + isi68 | isi) + basic_machine=m68k-isi + os=-sysv + ;; + m88k-omron*) + basic_machine=m88k-omron + ;; + magnum | m3230) + basic_machine=mips-mips + os=-sysv + ;; + merlin) + basic_machine=ns32k-utek + os=-sysv + ;; + miniframe) + basic_machine=m68000-convergent + ;; + *mint | -mint[0-9]* | *MiNT | *MiNT[0-9]*) + basic_machine=m68k-atari + os=-mint + ;; + mipsel*-linux*) + basic_machine=mipsel-unknown + os=-linux-gnu + ;; + mips*-linux*) + basic_machine=mips-unknown + os=-linux-gnu + ;; + mips3*-*) + basic_machine=`echo $basic_machine | sed -e 's/mips3/mips64/'` + ;; + mips3*) + basic_machine=`echo $basic_machine | sed -e 's/mips3/mips64/'`-unknown + ;; + monitor) + basic_machine=m68k-rom68k + os=-coff + ;; + msdos) + basic_machine=i386-unknown + os=-msdos + ;; + ncr3000) + basic_machine=i486-ncr + os=-sysv4 + ;; + netbsd386) + basic_machine=i386-unknown + os=-netbsd + ;; + netwinder) + basic_machine=armv4l-corel + os=-linux + ;; + news | news700 | news800 | news900) + basic_machine=m68k-sony + os=-newsos + ;; + news1000) + basic_machine=m68030-sony + os=-newsos + ;; + news-3600 | risc-news) + basic_machine=mips-sony + os=-newsos + ;; + necv70) + basic_machine=v70-nec + os=-sysv + ;; + next | m*-next ) + basic_machine=m68k-next + case $os in + -nextstep* ) + ;; + -ns2*) + os=-nextstep2 + ;; + *) + os=-nextstep3 + ;; + esac + ;; + nh3000) + basic_machine=m68k-harris + os=-cxux + ;; + nh[45]000) + basic_machine=m88k-harris + os=-cxux + ;; + nindy960) + basic_machine=i960-intel + os=-nindy + ;; + mon960) + basic_machine=i960-intel + os=-mon960 + ;; + np1) + basic_machine=np1-gould + ;; + op50n-* | op60c-*) + basic_machine=hppa1.1-oki + os=-proelf + ;; + OSE68000 | ose68000) + basic_machine=m68000-ericsson + os=-ose + ;; + os68k) + basic_machine=m68k-none + os=-os68k + ;; + pa-hitachi) + basic_machine=hppa1.1-hitachi + os=-hiuxwe2 + ;; + paragon) + basic_machine=i860-intel + os=-osf + ;; + pbd) + basic_machine=sparc-tti + ;; + pbb) + basic_machine=m68k-tti + ;; + pc532 | pc532-*) + basic_machine=ns32k-pc532 + ;; + pentium | p5 | k5 | k6 | nexen) + basic_machine=i586-pc + ;; + pentiumpro | p6 | 6x86) + basic_machine=i686-pc + ;; + pentiumii | pentium2) + basic_machine=i786-pc + ;; + pentium-* | p5-* | k5-* | k6-* | nexen-*) + basic_machine=i586-`echo $basic_machine | sed 's/^[^-]*-//'` + ;; + pentiumpro-* | p6-* | 6x86-*) + basic_machine=i686-`echo $basic_machine | sed 's/^[^-]*-//'` + ;; + pentiumii-* | pentium2-*) + basic_machine=i786-`echo $basic_machine | sed 's/^[^-]*-//'` + ;; + pn) + basic_machine=pn-gould + ;; + power) basic_machine=rs6000-ibm + ;; + ppc) basic_machine=powerpc-unknown + ;; + ppc-*) basic_machine=powerpc-`echo $basic_machine | sed 's/^[^-]*-//'` + ;; + ppcle | powerpclittle | ppc-le | powerpc-little) + basic_machine=powerpcle-unknown + ;; + ppcle-* | powerpclittle-*) + basic_machine=powerpcle-`echo $basic_machine | sed 's/^[^-]*-//'` + ;; + ps2) + basic_machine=i386-ibm + ;; + rom68k) + basic_machine=m68k-rom68k + os=-coff + ;; + rm[46]00) + basic_machine=mips-siemens + ;; + rtpc | rtpc-*) + basic_machine=romp-ibm + ;; + sa29200) + basic_machine=a29k-amd + os=-udi + ;; + sequent) + basic_machine=i386-sequent + ;; + sh) + basic_machine=sh-hitachi + os=-hms + ;; + sparclite-wrs) + basic_machine=sparclite-wrs + os=-vxworks + ;; + sps7) + basic_machine=m68k-bull + os=-sysv2 + ;; + spur) + basic_machine=spur-unknown + ;; + st2000) + basic_machine=m68k-tandem + ;; + stratus) + basic_machine=i860-stratus + os=-sysv4 + ;; + sun2) + basic_machine=m68000-sun + ;; + sun2os3) + basic_machine=m68000-sun + os=-sunos3 + ;; + sun2os4) + basic_machine=m68000-sun + os=-sunos4 + ;; + sun3os3) + basic_machine=m68k-sun + os=-sunos3 + ;; + sun3os4) + basic_machine=m68k-sun + os=-sunos4 + ;; + sun4os3) + basic_machine=sparc-sun + os=-sunos3 + ;; + sun4os4) + basic_machine=sparc-sun + os=-sunos4 + ;; + sun4sol2) + basic_machine=sparc-sun + os=-solaris2 + ;; + sun3 | sun3-*) + basic_machine=m68k-sun + ;; + sun4) + basic_machine=sparc-sun + ;; + sun386 | sun386i | roadrunner) + basic_machine=i386-sun + ;; + symmetry) + basic_machine=i386-sequent + os=-dynix + ;; + t3e) + basic_machine=t3e-cray + os=-unicos + ;; + tx39) + basic_machine=mipstx39-unknown + ;; + tx39el) + basic_machine=mipstx39el-unknown + ;; + tower | tower-32) + basic_machine=m68k-ncr + ;; + udi29k) + basic_machine=a29k-amd + os=-udi + ;; + ultra3) + basic_machine=a29k-nyu + os=-sym1 + ;; + v810 | necv810) + basic_machine=v810-nec + os=-none + ;; + vaxv) + basic_machine=vax-dec + os=-sysv + ;; + vms) + basic_machine=vax-dec + os=-vms + ;; + vpp*|vx|vx-*) + basic_machine=f301-fujitsu + ;; + vxworks960) + basic_machine=i960-wrs + os=-vxworks + ;; + vxworks68) + basic_machine=m68k-wrs + os=-vxworks + ;; + vxworks29k) + basic_machine=a29k-wrs + os=-vxworks + ;; + w65*) + basic_machine=w65-wdc + os=-none + ;; + w89k-*) + basic_machine=hppa1.1-winbond + os=-proelf + ;; + xmp) + basic_machine=xmp-cray + os=-unicos + ;; + xps | xps100) + basic_machine=xps100-honeywell + ;; + z8k-*-coff) + basic_machine=z8k-unknown + os=-sim + ;; + none) + basic_machine=none-none + os=-none + ;; + +# Here we handle the default manufacturer of certain CPU types. It is in +# some cases the only manufacturer, in others, it is the most popular. + w89k) + basic_machine=hppa1.1-winbond + ;; + op50n) + basic_machine=hppa1.1-oki + ;; + op60c) + basic_machine=hppa1.1-oki + ;; + mips) + if [ x$os = x-linux-gnu ]; then + basic_machine=mips-unknown + else + basic_machine=mips-mips + fi + ;; + romp) + basic_machine=romp-ibm + ;; + rs6000) + basic_machine=rs6000-ibm + ;; + vax) + basic_machine=vax-dec + ;; + pdp11) + basic_machine=pdp11-dec + ;; + we32k) + basic_machine=we32k-att + ;; + sparc | sparcv9) + basic_machine=sparc-sun + ;; + cydra) + basic_machine=cydra-cydrome + ;; + orion) + basic_machine=orion-highlevel + ;; + orion105) + basic_machine=clipper-highlevel + ;; + mac | mpw | mac-mpw) + basic_machine=m68k-apple + ;; + pmac | pmac-mpw) + basic_machine=powerpc-apple + ;; + c4x*) + basic_machine=c4x-none + os=-coff + ;; + *) + echo Invalid configuration \`$1\': machine \`$basic_machine\' not recognized 1>&2 + exit 1 + ;; +esac + +# Here we canonicalize certain aliases for manufacturers. +case $basic_machine in + *-digital*) + basic_machine=`echo $basic_machine | sed 's/digital.*/dec/'` + ;; + *-commodore*) + basic_machine=`echo $basic_machine | sed 's/commodore.*/cbm/'` + ;; + *) + ;; +esac + +# Decode manufacturer-specific aliases for certain operating systems. + +if [ x"$os" != x"" ] +then +case $os in + # First match some system type aliases + # that might get confused with valid system types. + # -solaris* is a basic system type, with this one exception. + -solaris1 | -solaris1.*) + os=`echo $os | sed -e 's|solaris1|sunos4|'` + ;; + -solaris) + os=-solaris2 + ;; + -svr4*) + os=-sysv4 + ;; + -unixware*) + os=-sysv4.2uw + ;; + -gnu/linux*) + os=`echo $os | sed -e 's|gnu/linux|linux-gnu|'` + ;; + # First accept the basic system types. + # The portable systems comes first. + # Each alternative MUST END IN A *, to match a version number. + # -sysv* is not here because it comes later, after sysvr4. + -gnu* | -bsd* | -mach* | -minix* | -genix* | -ultrix* | -irix* \ + | -*vms* | -sco* | -esix* | -isc* | -aix* | -sunos | -sunos[34]*\ + | -hpux* | -unos* | -osf* | -luna* | -dgux* | -solaris* | -sym* \ + | -amigaos* | -amigados* | -msdos* | -newsos* | -unicos* | -aof* \ + | -aos* \ + | -nindy* | -vxsim* | -vxworks* | -ebmon* | -hms* | -mvs* \ + | -clix* | -riscos* | -uniplus* | -iris* | -rtu* | -xenix* \ + | -hiux* | -386bsd* | -netbsd* | -openbsd* | -freebsd* | -riscix* \ + | -lynxos* | -bosx* | -nextstep* | -cxux* | -aout* | -elf* | -oabi* \ + | -ptx* | -coff* | -ecoff* | -winnt* | -domain* | -vsta* \ + | -udi* | -eabi* | -lites* | -ieee* | -go32* | -aux* \ + | -cygwin* | -pe* | -psos* | -moss* | -proelf* | -rtems* \ + | -mingw32* | -linux-gnu* | -uxpv* | -beos* | -mpeix* | -udk* \ + | -interix* | -uwin* | -rhapsody* | -openstep* | -oskit*) + # Remember, each alternative MUST END IN *, to match a version number. + ;; + -sim | -es1800* | -hms* | -xray | -os68k* | -none* | -v88r* \ + | -windows* | -osx | -abug | -netware* | -os9* | -beos* \ + | -macos* | -mpw* | -magic* | -mon960* | -lnews*) + ;; + -mac*) + os=`echo $os | sed -e 's|mac|macos|'` + ;; + -linux*) + os=`echo $os | sed -e 's|linux|linux-gnu|'` + ;; + -sunos5*) + os=`echo $os | sed -e 's|sunos5|solaris2|'` + ;; + -sunos6*) + os=`echo $os | sed -e 's|sunos6|solaris3|'` + ;; + -osfrose*) + os=-osfrose + ;; + -osf*) + os=-osf + ;; + -utek*) + os=-bsd + ;; + -dynix*) + os=-bsd + ;; + -acis*) + os=-aos + ;; + -386bsd) + os=-bsd + ;; + -ctix* | -uts*) + os=-sysv + ;; + -ns2 ) + os=-nextstep2 + ;; + # Preserve the version number of sinix5. + -sinix5.*) + os=`echo $os | sed -e 's|sinix|sysv|'` + ;; + -sinix*) + os=-sysv4 + ;; + -triton*) + os=-sysv3 + ;; + -oss*) + os=-sysv3 + ;; + -svr4) + os=-sysv4 + ;; + -svr3) + os=-sysv3 + ;; + -sysvr4) + os=-sysv4 + ;; + # This must come after -sysvr4. + -sysv*) + ;; + -ose*) + os=-ose + ;; + -es1800*) + os=-ose + ;; + -xenix) + os=-xenix + ;; + -*mint | -*MiNT) + os=-mint + ;; + -none) + ;; + *) + # Get rid of the `-' at the beginning of $os. + os=`echo $os | sed 's/[^-]*-//'` + echo Invalid configuration \`$1\': system \`$os\' not recognized 1>&2 + exit 1 + ;; +esac +else + +# Here we handle the default operating systems that come with various machines. +# The value should be what the vendor currently ships out the door with their +# machine or put another way, the most popular os provided with the machine. + +# Note that if you're going to try to match "-MANUFACTURER" here (say, +# "-sun"), then you have to tell the case statement up towards the top +# that MANUFACTURER isn't an operating system. Otherwise, code above +# will signal an error saying that MANUFACTURER isn't an operating +# system, and we'll never get to this point. + +case $basic_machine in + *-acorn) + os=-riscix1.2 + ;; + arm*-corel) + os=-linux + ;; + arm*-semi) + os=-aout + ;; + pdp11-*) + os=-none + ;; + *-dec | vax-*) + os=-ultrix4.2 + ;; + m68*-apollo) + os=-domain + ;; + i386-sun) + os=-sunos4.0.2 + ;; + m68000-sun) + os=-sunos3 + # This also exists in the configure program, but was not the + # default. + # os=-sunos4 + ;; + m68*-cisco) + os=-aout + ;; + mips*-cisco) + os=-elf + ;; + mips*-*) + os=-elf + ;; + *-tti) # must be before sparc entry or we get the wrong os. + os=-sysv3 + ;; + sparc-* | *-sun) + os=-sunos4.1.1 + ;; + *-be) + os=-beos + ;; + *-ibm) + os=-aix + ;; + *-wec) + os=-proelf + ;; + *-winbond) + os=-proelf + ;; + *-oki) + os=-proelf + ;; + *-hp) + os=-hpux + ;; + *-hitachi) + os=-hiux + ;; + i860-* | *-att | *-ncr | *-altos | *-motorola | *-convergent) + os=-sysv + ;; + *-cbm) + os=-amigaos + ;; + *-dg) + os=-dgux + ;; + *-dolphin) + os=-sysv3 + ;; + m68k-ccur) + os=-rtu + ;; + m88k-omron*) + os=-luna + ;; + *-next ) + os=-nextstep + ;; + *-sequent) + os=-ptx + ;; + *-crds) + os=-unos + ;; + *-ns) + os=-genix + ;; + i370-*) + os=-mvs + ;; + *-next) + os=-nextstep3 + ;; + *-gould) + os=-sysv + ;; + *-highlevel) + os=-bsd + ;; + *-encore) + os=-bsd + ;; + *-sgi) + os=-irix + ;; + *-siemens) + os=-sysv4 + ;; + *-masscomp) + os=-rtu + ;; + f301-fujitsu) + os=-uxpv + ;; + *-rom68k) + os=-coff + ;; + *-*bug) + os=-coff + ;; + *-apple) + os=-macos + ;; + *-atari*) + os=-mint + ;; + *) + os=-none + ;; +esac +fi + +# Here we handle the case where we know the os, and the CPU type, but not the +# manufacturer. We pick the logical manufacturer. +vendor=unknown +case $basic_machine in + *-unknown) + case $os in + -riscix*) + vendor=acorn + ;; + -sunos*) + vendor=sun + ;; + -aix*) + vendor=ibm + ;; + -beos*) + vendor=be + ;; + -hpux*) + vendor=hp + ;; + -mpeix*) + vendor=hp + ;; + -hiux*) + vendor=hitachi + ;; + -unos*) + vendor=crds + ;; + -dgux*) + vendor=dg + ;; + -luna*) + vendor=omron + ;; + -genix*) + vendor=ns + ;; + -mvs*) + vendor=ibm + ;; + -ptx*) + vendor=sequent + ;; + -vxsim* | -vxworks*) + vendor=wrs + ;; + -aux*) + vendor=apple + ;; + -hms*) + vendor=hitachi + ;; + -mpw* | -macos*) + vendor=apple + ;; + -*mint | -*MiNT) + vendor=atari + ;; + esac + basic_machine=`echo $basic_machine | sed "s/unknown/$vendor/"` + ;; +esac + +echo $basic_machine$os diff --git a/nufxlib-0/configure b/nufxlib-0/configure new file mode 100755 index 0000000..08c2fb4 --- /dev/null +++ b/nufxlib-0/configure @@ -0,0 +1,2115 @@ +#! /bin/sh + +# Guess values for system-dependent variables and create Makefiles. +# Generated automatically using autoconf version 2.13 +# Copyright (C) 1992, 93, 94, 95, 96 Free Software Foundation, Inc. +# +# This configure script is free software; the Free Software Foundation +# gives unlimited permission to copy, distribute and modify it. + +# Defaults: +ac_help= +ac_default_prefix=/usr/local +# Any additions from configure.in: +ac_help="$ac_help + --enable-dmalloc: do dmalloc stuff" + +# Initialize some variables set by options. +# The variables have the same names as the options, with +# dashes changed to underlines. +build=NONE +cache_file=./config.cache +exec_prefix=NONE +host=NONE +no_create= +nonopt=NONE +no_recursion= +prefix=NONE +program_prefix=NONE +program_suffix=NONE +program_transform_name=s,x,x, +silent= +site= +srcdir= +target=NONE +verbose= +x_includes=NONE +x_libraries=NONE +bindir='${exec_prefix}/bin' +sbindir='${exec_prefix}/sbin' +libexecdir='${exec_prefix}/libexec' +datadir='${prefix}/share' +sysconfdir='${prefix}/etc' +sharedstatedir='${prefix}/com' +localstatedir='${prefix}/var' +libdir='${exec_prefix}/lib' +includedir='${prefix}/include' +oldincludedir='/usr/include' +infodir='${prefix}/info' +mandir='${prefix}/man' + +# Initialize some other variables. +subdirs= +MFLAGS= MAKEFLAGS= +SHELL=${CONFIG_SHELL-/bin/sh} +# Maximum number of lines to put in a shell here document. +ac_max_here_lines=12 + +ac_prev= +for ac_option +do + + # If the previous option needs an argument, assign it. + if test -n "$ac_prev"; then + eval "$ac_prev=\$ac_option" + ac_prev= + continue + fi + + case "$ac_option" in + -*=*) ac_optarg=`echo "$ac_option" | sed 's/[-_a-zA-Z0-9]*=//'` ;; + *) ac_optarg= ;; + esac + + # Accept the important Cygnus configure options, so we can diagnose typos. + + case "$ac_option" in + + -bindir | --bindir | --bindi | --bind | --bin | --bi) + ac_prev=bindir ;; + -bindir=* | --bindir=* | --bindi=* | --bind=* | --bin=* | --bi=*) + bindir="$ac_optarg" ;; + + -build | --build | --buil | --bui | --bu) + ac_prev=build ;; + -build=* | --build=* | --buil=* | --bui=* | --bu=*) + build="$ac_optarg" ;; + + -cache-file | --cache-file | --cache-fil | --cache-fi \ + | --cache-f | --cache- | --cache | --cach | --cac | --ca | --c) + ac_prev=cache_file ;; + -cache-file=* | --cache-file=* | --cache-fil=* | --cache-fi=* \ + | --cache-f=* | --cache-=* | --cache=* | --cach=* | --cac=* | --ca=* | --c=*) + cache_file="$ac_optarg" ;; + + -datadir | --datadir | --datadi | --datad | --data | --dat | --da) + ac_prev=datadir ;; + -datadir=* | --datadir=* | --datadi=* | --datad=* | --data=* | --dat=* \ + | --da=*) + datadir="$ac_optarg" ;; + + -disable-* | --disable-*) + ac_feature=`echo $ac_option|sed -e 's/-*disable-//'` + # Reject names that are not valid shell variable names. + if test -n "`echo $ac_feature| sed 's/[-a-zA-Z0-9_]//g'`"; then + { echo "configure: error: $ac_feature: invalid feature name" 1>&2; exit 1; } + fi + ac_feature=`echo $ac_feature| sed 's/-/_/g'` + eval "enable_${ac_feature}=no" ;; + + -enable-* | --enable-*) + ac_feature=`echo $ac_option|sed -e 's/-*enable-//' -e 's/=.*//'` + # Reject names that are not valid shell variable names. + if test -n "`echo $ac_feature| sed 's/[-_a-zA-Z0-9]//g'`"; then + { echo "configure: error: $ac_feature: invalid feature name" 1>&2; exit 1; } + fi + ac_feature=`echo $ac_feature| sed 's/-/_/g'` + case "$ac_option" in + *=*) ;; + *) ac_optarg=yes ;; + esac + eval "enable_${ac_feature}='$ac_optarg'" ;; + + -exec-prefix | --exec_prefix | --exec-prefix | --exec-prefi \ + | --exec-pref | --exec-pre | --exec-pr | --exec-p | --exec- \ + | --exec | --exe | --ex) + ac_prev=exec_prefix ;; + -exec-prefix=* | --exec_prefix=* | --exec-prefix=* | --exec-prefi=* \ + | --exec-pref=* | --exec-pre=* | --exec-pr=* | --exec-p=* | --exec-=* \ + | --exec=* | --exe=* | --ex=*) + exec_prefix="$ac_optarg" ;; + + -gas | --gas | --ga | --g) + # Obsolete; use --with-gas. + with_gas=yes ;; + + -help | --help | --hel | --he) + # Omit some internal or obsolete options to make the list less imposing. + # This message is too long to be a string in the A/UX 3.1 sh. + cat << EOF +Usage: configure [options] [host] +Options: [defaults in brackets after descriptions] +Configuration: + --cache-file=FILE cache test results in FILE + --help print this message + --no-create do not create output files + --quiet, --silent do not print \`checking...' messages + --version print the version of autoconf that created configure +Directory and file names: + --prefix=PREFIX install architecture-independent files in PREFIX + [$ac_default_prefix] + --exec-prefix=EPREFIX install architecture-dependent files in EPREFIX + [same as prefix] + --bindir=DIR user executables in DIR [EPREFIX/bin] + --sbindir=DIR system admin executables in DIR [EPREFIX/sbin] + --libexecdir=DIR program executables in DIR [EPREFIX/libexec] + --datadir=DIR read-only architecture-independent data in DIR + [PREFIX/share] + --sysconfdir=DIR read-only single-machine data in DIR [PREFIX/etc] + --sharedstatedir=DIR modifiable architecture-independent data in DIR + [PREFIX/com] + --localstatedir=DIR modifiable single-machine data in DIR [PREFIX/var] + --libdir=DIR object code libraries in DIR [EPREFIX/lib] + --includedir=DIR C header files in DIR [PREFIX/include] + --oldincludedir=DIR C header files for non-gcc in DIR [/usr/include] + --infodir=DIR info documentation in DIR [PREFIX/info] + --mandir=DIR man documentation in DIR [PREFIX/man] + --srcdir=DIR find the sources in DIR [configure dir or ..] + --program-prefix=PREFIX prepend PREFIX to installed program names + --program-suffix=SUFFIX append SUFFIX to installed program names + --program-transform-name=PROGRAM + run sed PROGRAM on installed program names +EOF + cat << EOF +Host type: + --build=BUILD configure for building on BUILD [BUILD=HOST] + --host=HOST configure for HOST [guessed] + --target=TARGET configure for TARGET [TARGET=HOST] +Features and packages: + --disable-FEATURE do not include FEATURE (same as --enable-FEATURE=no) + --enable-FEATURE[=ARG] include FEATURE [ARG=yes] + --with-PACKAGE[=ARG] use PACKAGE [ARG=yes] + --without-PACKAGE do not use PACKAGE (same as --with-PACKAGE=no) + --x-includes=DIR X include files are in DIR + --x-libraries=DIR X library files are in DIR +EOF + if test -n "$ac_help"; then + echo "--enable and --with options recognized:$ac_help" + fi + exit 0 ;; + + -host | --host | --hos | --ho) + ac_prev=host ;; + -host=* | --host=* | --hos=* | --ho=*) + host="$ac_optarg" ;; + + -includedir | --includedir | --includedi | --included | --include \ + | --includ | --inclu | --incl | --inc) + ac_prev=includedir ;; + -includedir=* | --includedir=* | --includedi=* | --included=* | --include=* \ + | --includ=* | --inclu=* | --incl=* | --inc=*) + includedir="$ac_optarg" ;; + + -infodir | --infodir | --infodi | --infod | --info | --inf) + ac_prev=infodir ;; + -infodir=* | --infodir=* | --infodi=* | --infod=* | --info=* | --inf=*) + infodir="$ac_optarg" ;; + + -libdir | --libdir | --libdi | --libd) + ac_prev=libdir ;; + -libdir=* | --libdir=* | --libdi=* | --libd=*) + libdir="$ac_optarg" ;; + + -libexecdir | --libexecdir | --libexecdi | --libexecd | --libexec \ + | --libexe | --libex | --libe) + ac_prev=libexecdir ;; + -libexecdir=* | --libexecdir=* | --libexecdi=* | --libexecd=* | --libexec=* \ + | --libexe=* | --libex=* | --libe=*) + libexecdir="$ac_optarg" ;; + + -localstatedir | --localstatedir | --localstatedi | --localstated \ + | --localstate | --localstat | --localsta | --localst \ + | --locals | --local | --loca | --loc | --lo) + ac_prev=localstatedir ;; + -localstatedir=* | --localstatedir=* | --localstatedi=* | --localstated=* \ + | --localstate=* | --localstat=* | --localsta=* | --localst=* \ + | --locals=* | --local=* | --loca=* | --loc=* | --lo=*) + localstatedir="$ac_optarg" ;; + + -mandir | --mandir | --mandi | --mand | --man | --ma | --m) + ac_prev=mandir ;; + -mandir=* | --mandir=* | --mandi=* | --mand=* | --man=* | --ma=* | --m=*) + mandir="$ac_optarg" ;; + + -nfp | --nfp | --nf) + # Obsolete; use --without-fp. + with_fp=no ;; + + -no-create | --no-create | --no-creat | --no-crea | --no-cre \ + | --no-cr | --no-c) + no_create=yes ;; + + -no-recursion | --no-recursion | --no-recursio | --no-recursi \ + | --no-recurs | --no-recur | --no-recu | --no-rec | --no-re | --no-r) + no_recursion=yes ;; + + -oldincludedir | --oldincludedir | --oldincludedi | --oldincluded \ + | --oldinclude | --oldinclud | --oldinclu | --oldincl | --oldinc \ + | --oldin | --oldi | --old | --ol | --o) + ac_prev=oldincludedir ;; + -oldincludedir=* | --oldincludedir=* | --oldincludedi=* | --oldincluded=* \ + | --oldinclude=* | --oldinclud=* | --oldinclu=* | --oldincl=* | --oldinc=* \ + | --oldin=* | --oldi=* | --old=* | --ol=* | --o=*) + oldincludedir="$ac_optarg" ;; + + -prefix | --prefix | --prefi | --pref | --pre | --pr | --p) + ac_prev=prefix ;; + -prefix=* | --prefix=* | --prefi=* | --pref=* | --pre=* | --pr=* | --p=*) + prefix="$ac_optarg" ;; + + -program-prefix | --program-prefix | --program-prefi | --program-pref \ + | --program-pre | --program-pr | --program-p) + ac_prev=program_prefix ;; + -program-prefix=* | --program-prefix=* | --program-prefi=* \ + | --program-pref=* | --program-pre=* | --program-pr=* | --program-p=*) + program_prefix="$ac_optarg" ;; + + -program-suffix | --program-suffix | --program-suffi | --program-suff \ + | --program-suf | --program-su | --program-s) + ac_prev=program_suffix ;; + -program-suffix=* | --program-suffix=* | --program-suffi=* \ + | --program-suff=* | --program-suf=* | --program-su=* | --program-s=*) + program_suffix="$ac_optarg" ;; + + -program-transform-name | --program-transform-name \ + | --program-transform-nam | --program-transform-na \ + | --program-transform-n | --program-transform- \ + | --program-transform | --program-transfor \ + | --program-transfo | --program-transf \ + | --program-trans | --program-tran \ + | --progr-tra | --program-tr | --program-t) + ac_prev=program_transform_name ;; + -program-transform-name=* | --program-transform-name=* \ + | --program-transform-nam=* | --program-transform-na=* \ + | --program-transform-n=* | --program-transform-=* \ + | --program-transform=* | --program-transfor=* \ + | --program-transfo=* | --program-transf=* \ + | --program-trans=* | --program-tran=* \ + | --progr-tra=* | --program-tr=* | --program-t=*) + program_transform_name="$ac_optarg" ;; + + -q | -quiet | --quiet | --quie | --qui | --qu | --q \ + | -silent | --silent | --silen | --sile | --sil) + silent=yes ;; + + -sbindir | --sbindir | --sbindi | --sbind | --sbin | --sbi | --sb) + ac_prev=sbindir ;; + -sbindir=* | --sbindir=* | --sbindi=* | --sbind=* | --sbin=* \ + | --sbi=* | --sb=*) + sbindir="$ac_optarg" ;; + + -sharedstatedir | --sharedstatedir | --sharedstatedi \ + | --sharedstated | --sharedstate | --sharedstat | --sharedsta \ + | --sharedst | --shareds | --shared | --share | --shar \ + | --sha | --sh) + ac_prev=sharedstatedir ;; + -sharedstatedir=* | --sharedstatedir=* | --sharedstatedi=* \ + | --sharedstated=* | --sharedstate=* | --sharedstat=* | --sharedsta=* \ + | --sharedst=* | --shareds=* | --shared=* | --share=* | --shar=* \ + | --sha=* | --sh=*) + sharedstatedir="$ac_optarg" ;; + + -site | --site | --sit) + ac_prev=site ;; + -site=* | --site=* | --sit=*) + site="$ac_optarg" ;; + + -srcdir | --srcdir | --srcdi | --srcd | --src | --sr) + ac_prev=srcdir ;; + -srcdir=* | --srcdir=* | --srcdi=* | --srcd=* | --src=* | --sr=*) + srcdir="$ac_optarg" ;; + + -sysconfdir | --sysconfdir | --sysconfdi | --sysconfd | --sysconf \ + | --syscon | --sysco | --sysc | --sys | --sy) + ac_prev=sysconfdir ;; + -sysconfdir=* | --sysconfdir=* | --sysconfdi=* | --sysconfd=* | --sysconf=* \ + | --syscon=* | --sysco=* | --sysc=* | --sys=* | --sy=*) + sysconfdir="$ac_optarg" ;; + + -target | --target | --targe | --targ | --tar | --ta | --t) + ac_prev=target ;; + -target=* | --target=* | --targe=* | --targ=* | --tar=* | --ta=* | --t=*) + target="$ac_optarg" ;; + + -v | -verbose | --verbose | --verbos | --verbo | --verb) + verbose=yes ;; + + -version | --version | --versio | --versi | --vers) + echo "configure generated by autoconf version 2.13" + exit 0 ;; + + -with-* | --with-*) + ac_package=`echo $ac_option|sed -e 's/-*with-//' -e 's/=.*//'` + # Reject names that are not valid shell variable names. + if test -n "`echo $ac_package| sed 's/[-_a-zA-Z0-9]//g'`"; then + { echo "configure: error: $ac_package: invalid package name" 1>&2; exit 1; } + fi + ac_package=`echo $ac_package| sed 's/-/_/g'` + case "$ac_option" in + *=*) ;; + *) ac_optarg=yes ;; + esac + eval "with_${ac_package}='$ac_optarg'" ;; + + -without-* | --without-*) + ac_package=`echo $ac_option|sed -e 's/-*without-//'` + # Reject names that are not valid shell variable names. + if test -n "`echo $ac_package| sed 's/[-a-zA-Z0-9_]//g'`"; then + { echo "configure: error: $ac_package: invalid package name" 1>&2; exit 1; } + fi + ac_package=`echo $ac_package| sed 's/-/_/g'` + eval "with_${ac_package}=no" ;; + + --x) + # Obsolete; use --with-x. + with_x=yes ;; + + -x-includes | --x-includes | --x-include | --x-includ | --x-inclu \ + | --x-incl | --x-inc | --x-in | --x-i) + ac_prev=x_includes ;; + -x-includes=* | --x-includes=* | --x-include=* | --x-includ=* | --x-inclu=* \ + | --x-incl=* | --x-inc=* | --x-in=* | --x-i=*) + x_includes="$ac_optarg" ;; + + -x-libraries | --x-libraries | --x-librarie | --x-librari \ + | --x-librar | --x-libra | --x-libr | --x-lib | --x-li | --x-l) + ac_prev=x_libraries ;; + -x-libraries=* | --x-libraries=* | --x-librarie=* | --x-librari=* \ + | --x-librar=* | --x-libra=* | --x-libr=* | --x-lib=* | --x-li=* | --x-l=*) + x_libraries="$ac_optarg" ;; + + -*) { echo "configure: error: $ac_option: invalid option; use --help to show usage" 1>&2; exit 1; } + ;; + + *) + if test -n "`echo $ac_option| sed 's/[-a-z0-9.]//g'`"; then + echo "configure: warning: $ac_option: invalid host type" 1>&2 + fi + if test "x$nonopt" != xNONE; then + { echo "configure: error: can only configure for one host and one target at a time" 1>&2; exit 1; } + fi + nonopt="$ac_option" + ;; + + esac +done + +if test -n "$ac_prev"; then + { echo "configure: error: missing argument to --`echo $ac_prev | sed 's/_/-/g'`" 1>&2; exit 1; } +fi + +trap 'rm -fr conftest* confdefs* core core.* *.core $ac_clean_files; exit 1' 1 2 15 + +# File descriptor usage: +# 0 standard input +# 1 file creation +# 2 errors and warnings +# 3 some systems may open it to /dev/tty +# 4 used on the Kubota Titan +# 6 checking for... messages and results +# 5 compiler messages saved in config.log +if test "$silent" = yes; then + exec 6>/dev/null +else + exec 6>&1 +fi +exec 5>./config.log + +echo "\ +This file contains any messages produced by compilers while +running configure, to aid debugging if configure makes a mistake. +" 1>&5 + +# Strip out --no-create and --no-recursion so they do not pile up. +# Also quote any args containing shell metacharacters. +ac_configure_args= +for ac_arg +do + case "$ac_arg" in + -no-create | --no-create | --no-creat | --no-crea | --no-cre \ + | --no-cr | --no-c) ;; + -no-recursion | --no-recursion | --no-recursio | --no-recursi \ + | --no-recurs | --no-recur | --no-recu | --no-rec | --no-re | --no-r) ;; + *" "*|*" "*|*[\[\]\~\#\$\^\&\*\(\)\{\}\\\|\;\<\>\?]*) + ac_configure_args="$ac_configure_args '$ac_arg'" ;; + *) ac_configure_args="$ac_configure_args $ac_arg" ;; + esac +done + +# NLS nuisances. +# Only set these to C if already set. These must not be set unconditionally +# because not all systems understand e.g. LANG=C (notably SCO). +# Fixing LC_MESSAGES prevents Solaris sh from translating var values in `set'! +# Non-C LC_CTYPE values break the ctype check. +if test "${LANG+set}" = set; then LANG=C; export LANG; fi +if test "${LC_ALL+set}" = set; then LC_ALL=C; export LC_ALL; fi +if test "${LC_MESSAGES+set}" = set; then LC_MESSAGES=C; export LC_MESSAGES; fi +if test "${LC_CTYPE+set}" = set; then LC_CTYPE=C; export LC_CTYPE; fi + +# confdefs.h avoids OS command line length limits that DEFS can exceed. +rm -rf conftest* confdefs.h +# AIX cpp loses on an empty file, so make sure it contains at least a newline. +echo > confdefs.h + +# A filename unique to this package, relative to the directory that +# configure is in, which we can look for to find out if srcdir is correct. +ac_unique_file=NufxLibPriv.h + +# Find the source files, if location was not specified. +if test -z "$srcdir"; then + ac_srcdir_defaulted=yes + # Try the directory containing this script, then its parent. + ac_prog=$0 + ac_confdir=`echo $ac_prog|sed 's%/[^/][^/]*$%%'` + test "x$ac_confdir" = "x$ac_prog" && ac_confdir=. + srcdir=$ac_confdir + if test ! -r $srcdir/$ac_unique_file; then + srcdir=.. + fi +else + ac_srcdir_defaulted=no +fi +if test ! -r $srcdir/$ac_unique_file; then + if test "$ac_srcdir_defaulted" = yes; then + { echo "configure: error: can not find sources in $ac_confdir or .." 1>&2; exit 1; } + else + { echo "configure: error: can not find sources in $srcdir" 1>&2; exit 1; } + fi +fi +srcdir=`echo "${srcdir}" | sed 's%\([^/]\)/*$%\1%'` + +# Prefer explicitly selected file to automatically selected ones. +if test -z "$CONFIG_SITE"; then + if test "x$prefix" != xNONE; then + CONFIG_SITE="$prefix/share/config.site $prefix/etc/config.site" + else + CONFIG_SITE="$ac_default_prefix/share/config.site $ac_default_prefix/etc/config.site" + fi +fi +for ac_site_file in $CONFIG_SITE; do + if test -r "$ac_site_file"; then + echo "loading site script $ac_site_file" + . "$ac_site_file" + fi +done + +if test -r "$cache_file"; then + echo "loading cache $cache_file" + . $cache_file +else + echo "creating cache $cache_file" + > $cache_file +fi + +ac_ext=c +# CFLAGS is not in ac_cpp because -g, -O, etc. are not valid cpp options. +ac_cpp='$CPP $CPPFLAGS' +ac_compile='${CC-cc} -c $CFLAGS $CPPFLAGS conftest.$ac_ext 1>&5' +ac_link='${CC-cc} -o conftest${ac_exeext} $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS 1>&5' +cross_compiling=$ac_cv_prog_cc_cross + +ac_exeext= +ac_objext=o +if (echo "testing\c"; echo 1,2,3) | grep c >/dev/null; then + # Stardent Vistra SVR4 grep lacks -e, says ghazi@caip.rutgers.edu. + if (echo -n testing; echo 1,2,3) | sed s/-n/xn/ | grep xn >/dev/null; then + ac_n= ac_c=' +' ac_t=' ' + else + ac_n=-n ac_c= ac_t= + fi +else + ac_n= ac_c='\c' ac_t= +fi + + + + +ac_aux_dir= +for ac_dir in $srcdir $srcdir/.. $srcdir/../..; do + if test -f $ac_dir/install-sh; then + ac_aux_dir=$ac_dir + ac_install_sh="$ac_aux_dir/install-sh -c" + break + elif test -f $ac_dir/install.sh; then + ac_aux_dir=$ac_dir + ac_install_sh="$ac_aux_dir/install.sh -c" + break + fi +done +if test -z "$ac_aux_dir"; then + { echo "configure: error: can not find install-sh or install.sh in $srcdir $srcdir/.. $srcdir/../.." 1>&2; exit 1; } +fi +ac_config_guess=$ac_aux_dir/config.guess +ac_config_sub=$ac_aux_dir/config.sub +ac_configure=$ac_aux_dir/configure # This should be Cygnus configure. + + +# Make sure we can run config.sub. +if ${CONFIG_SHELL-/bin/sh} $ac_config_sub sun4 >/dev/null 2>&1; then : +else { echo "configure: error: can not run $ac_config_sub" 1>&2; exit 1; } +fi + +echo $ac_n "checking host system type""... $ac_c" 1>&6 +echo "configure:555: checking host system type" >&5 + +host_alias=$host +case "$host_alias" in +NONE) + case $nonopt in + NONE) + if host_alias=`${CONFIG_SHELL-/bin/sh} $ac_config_guess`; then : + else { echo "configure: error: can not guess host type; you must specify one" 1>&2; exit 1; } + fi ;; + *) host_alias=$nonopt ;; + esac ;; +esac + +host=`${CONFIG_SHELL-/bin/sh} $ac_config_sub $host_alias` +host_cpu=`echo $host | sed 's/^\([^-]*\)-\([^-]*\)-\(.*\)$/\1/'` +host_vendor=`echo $host | sed 's/^\([^-]*\)-\([^-]*\)-\(.*\)$/\2/'` +host_os=`echo $host | sed 's/^\([^-]*\)-\([^-]*\)-\(.*\)$/\3/'` +echo "$ac_t""$host" 1>&6 + +# Extract the first word of "gcc", so it can be a program name with args. +set dummy gcc; ac_word=$2 +echo $ac_n "checking for $ac_word""... $ac_c" 1>&6 +echo "configure:578: checking for $ac_word" >&5 +if eval "test \"`echo '$''{'ac_cv_prog_CC'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + if test -n "$CC"; then + ac_cv_prog_CC="$CC" # Let the user override the test. +else + IFS="${IFS= }"; ac_save_ifs="$IFS"; IFS=":" + ac_dummy="$PATH" + for ac_dir in $ac_dummy; do + test -z "$ac_dir" && ac_dir=. + if test -f $ac_dir/$ac_word; then + ac_cv_prog_CC="gcc" + break + fi + done + IFS="$ac_save_ifs" +fi +fi +CC="$ac_cv_prog_CC" +if test -n "$CC"; then + echo "$ac_t""$CC" 1>&6 +else + echo "$ac_t""no" 1>&6 +fi + +if test -z "$CC"; then + # Extract the first word of "cc", so it can be a program name with args. +set dummy cc; ac_word=$2 +echo $ac_n "checking for $ac_word""... $ac_c" 1>&6 +echo "configure:608: checking for $ac_word" >&5 +if eval "test \"`echo '$''{'ac_cv_prog_CC'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + if test -n "$CC"; then + ac_cv_prog_CC="$CC" # Let the user override the test. +else + IFS="${IFS= }"; ac_save_ifs="$IFS"; IFS=":" + ac_prog_rejected=no + ac_dummy="$PATH" + for ac_dir in $ac_dummy; do + test -z "$ac_dir" && ac_dir=. + if test -f $ac_dir/$ac_word; then + if test "$ac_dir/$ac_word" = "/usr/ucb/cc"; then + ac_prog_rejected=yes + continue + fi + ac_cv_prog_CC="cc" + break + fi + done + IFS="$ac_save_ifs" +if test $ac_prog_rejected = yes; then + # We found a bogon in the path, so make sure we never use it. + set dummy $ac_cv_prog_CC + shift + if test $# -gt 0; then + # We chose a different compiler from the bogus one. + # However, it has the same basename, so the bogon will be chosen + # first if we set CC to just the basename; use the full file name. + shift + set dummy "$ac_dir/$ac_word" "$@" + shift + ac_cv_prog_CC="$@" + fi +fi +fi +fi +CC="$ac_cv_prog_CC" +if test -n "$CC"; then + echo "$ac_t""$CC" 1>&6 +else + echo "$ac_t""no" 1>&6 +fi + + if test -z "$CC"; then + case "`uname -s`" in + *win32* | *WIN32*) + # Extract the first word of "cl", so it can be a program name with args. +set dummy cl; ac_word=$2 +echo $ac_n "checking for $ac_word""... $ac_c" 1>&6 +echo "configure:659: checking for $ac_word" >&5 +if eval "test \"`echo '$''{'ac_cv_prog_CC'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + if test -n "$CC"; then + ac_cv_prog_CC="$CC" # Let the user override the test. +else + IFS="${IFS= }"; ac_save_ifs="$IFS"; IFS=":" + ac_dummy="$PATH" + for ac_dir in $ac_dummy; do + test -z "$ac_dir" && ac_dir=. + if test -f $ac_dir/$ac_word; then + ac_cv_prog_CC="cl" + break + fi + done + IFS="$ac_save_ifs" +fi +fi +CC="$ac_cv_prog_CC" +if test -n "$CC"; then + echo "$ac_t""$CC" 1>&6 +else + echo "$ac_t""no" 1>&6 +fi + ;; + esac + fi + test -z "$CC" && { echo "configure: error: no acceptable cc found in \$PATH" 1>&2; exit 1; } +fi + +echo $ac_n "checking whether the C compiler ($CC $CFLAGS $LDFLAGS) works""... $ac_c" 1>&6 +echo "configure:691: checking whether the C compiler ($CC $CFLAGS $LDFLAGS) works" >&5 + +ac_ext=c +# CFLAGS is not in ac_cpp because -g, -O, etc. are not valid cpp options. +ac_cpp='$CPP $CPPFLAGS' +ac_compile='${CC-cc} -c $CFLAGS $CPPFLAGS conftest.$ac_ext 1>&5' +ac_link='${CC-cc} -o conftest${ac_exeext} $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS 1>&5' +cross_compiling=$ac_cv_prog_cc_cross + +cat > conftest.$ac_ext << EOF + +#line 702 "configure" +#include "confdefs.h" + +main(){return(0);} +EOF +if { (eval echo configure:707: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then + ac_cv_prog_cc_works=yes + # If we can't run a trivial program, we are probably using a cross compiler. + if (./conftest; exit) 2>/dev/null; then + ac_cv_prog_cc_cross=no + else + ac_cv_prog_cc_cross=yes + fi +else + echo "configure: failed program was:" >&5 + cat conftest.$ac_ext >&5 + ac_cv_prog_cc_works=no +fi +rm -fr conftest* +ac_ext=c +# CFLAGS is not in ac_cpp because -g, -O, etc. are not valid cpp options. +ac_cpp='$CPP $CPPFLAGS' +ac_compile='${CC-cc} -c $CFLAGS $CPPFLAGS conftest.$ac_ext 1>&5' +ac_link='${CC-cc} -o conftest${ac_exeext} $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS 1>&5' +cross_compiling=$ac_cv_prog_cc_cross + +echo "$ac_t""$ac_cv_prog_cc_works" 1>&6 +if test $ac_cv_prog_cc_works = no; then + { echo "configure: error: installation or configuration problem: C compiler cannot create executables." 1>&2; exit 1; } +fi +echo $ac_n "checking whether the C compiler ($CC $CFLAGS $LDFLAGS) is a cross-compiler""... $ac_c" 1>&6 +echo "configure:733: checking whether the C compiler ($CC $CFLAGS $LDFLAGS) is a cross-compiler" >&5 +echo "$ac_t""$ac_cv_prog_cc_cross" 1>&6 +cross_compiling=$ac_cv_prog_cc_cross + +echo $ac_n "checking whether we are using GNU C""... $ac_c" 1>&6 +echo "configure:738: checking whether we are using GNU C" >&5 +if eval "test \"`echo '$''{'ac_cv_prog_gcc'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + cat > conftest.c <&5; (eval $ac_try) 2>&5; }; } | egrep yes >/dev/null 2>&1; then + ac_cv_prog_gcc=yes +else + ac_cv_prog_gcc=no +fi +fi + +echo "$ac_t""$ac_cv_prog_gcc" 1>&6 + +if test $ac_cv_prog_gcc = yes; then + GCC=yes +else + GCC= +fi + +ac_test_CFLAGS="${CFLAGS+set}" +ac_save_CFLAGS="$CFLAGS" +CFLAGS= +echo $ac_n "checking whether ${CC-cc} accepts -g""... $ac_c" 1>&6 +echo "configure:766: checking whether ${CC-cc} accepts -g" >&5 +if eval "test \"`echo '$''{'ac_cv_prog_cc_g'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + echo 'void f(){}' > conftest.c +if test -z "`${CC-cc} -g -c conftest.c 2>&1`"; then + ac_cv_prog_cc_g=yes +else + ac_cv_prog_cc_g=no +fi +rm -f conftest* + +fi + +echo "$ac_t""$ac_cv_prog_cc_g" 1>&6 +if test "$ac_test_CFLAGS" = set; then + CFLAGS="$ac_save_CFLAGS" +elif test $ac_cv_prog_cc_g = yes; then + if test "$GCC" = yes; then + CFLAGS="-g -O2" + else + CFLAGS="-g" + fi +else + if test "$GCC" = yes; then + CFLAGS="-O2" + else + CFLAGS= + fi +fi + +# Find a good install program. We prefer a C program (faster), +# so one script is as good as another. But avoid the broken or +# incompatible versions: +# SysV /etc/install, /usr/sbin/install +# SunOS /usr/etc/install +# IRIX /sbin/install +# AIX /bin/install +# AIX 4 /usr/bin/installbsd, which doesn't work without a -g flag +# AFS /usr/afsws/bin/install, which mishandles nonexistent args +# SVR4 /usr/ucb/install, which tries to use the nonexistent group "staff" +# ./install, which can be erroneously created by make from ./install.sh. +echo $ac_n "checking for a BSD compatible install""... $ac_c" 1>&6 +echo "configure:809: checking for a BSD compatible install" >&5 +if test -z "$INSTALL"; then +if eval "test \"`echo '$''{'ac_cv_path_install'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + IFS="${IFS= }"; ac_save_IFS="$IFS"; IFS=":" + for ac_dir in $PATH; do + # Account for people who put trailing slashes in PATH elements. + case "$ac_dir/" in + /|./|.//|/etc/*|/usr/sbin/*|/usr/etc/*|/sbin/*|/usr/afsws/bin/*|/usr/ucb/*) ;; + *) + # OSF1 and SCO ODT 3.0 have their own names for install. + # Don't use installbsd from OSF since it installs stuff as root + # by default. + for ac_prog in ginstall scoinst install; do + if test -f $ac_dir/$ac_prog; then + if test $ac_prog = install && + grep dspmsg $ac_dir/$ac_prog >/dev/null 2>&1; then + # AIX install. It has an incompatible calling convention. + : + else + ac_cv_path_install="$ac_dir/$ac_prog -c" + break 2 + fi + fi + done + ;; + esac + done + IFS="$ac_save_IFS" + +fi + if test "${ac_cv_path_install+set}" = set; then + INSTALL="$ac_cv_path_install" + else + # As a last resort, use the slow shell script. We don't cache a + # path for INSTALL within a source directory, because that will + # break other packages using the cache if that directory is + # removed, or if the path is relative. + INSTALL="$ac_install_sh" + fi +fi +echo "$ac_t""$INSTALL" 1>&6 + +# Use test -z because SunOS4 sh mishandles braces in ${var-val}. +# It thinks the first close brace ends the variable substitution. +test -z "$INSTALL_PROGRAM" && INSTALL_PROGRAM='${INSTALL}' + +test -z "$INSTALL_SCRIPT" && INSTALL_SCRIPT='${INSTALL_PROGRAM}' + +test -z "$INSTALL_DATA" && INSTALL_DATA='${INSTALL} -m 644' + +echo $ac_n "checking whether ${MAKE-make} sets \${MAKE}""... $ac_c" 1>&6 +echo "configure:862: checking whether ${MAKE-make} sets \${MAKE}" >&5 +set dummy ${MAKE-make}; ac_make=`echo "$2" | sed 'y%./+-%__p_%'` +if eval "test \"`echo '$''{'ac_cv_prog_make_${ac_make}_set'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + cat > conftestmake <<\EOF +all: + @echo 'ac_maketemp="${MAKE}"' +EOF +# GNU make sometimes prints "make[1]: Entering...", which would confuse us. +eval `${MAKE-make} -f conftestmake 2>/dev/null | grep temp=` +if test -n "$ac_maketemp"; then + eval ac_cv_prog_make_${ac_make}_set=yes +else + eval ac_cv_prog_make_${ac_make}_set=no +fi +rm -f conftestmake +fi +if eval "test \"`echo '$ac_cv_prog_make_'${ac_make}_set`\" = yes"; then + echo "$ac_t""yes" 1>&6 + SET_MAKE= +else + echo "$ac_t""no" 1>&6 + SET_MAKE="MAKE=${MAKE-make}" +fi + +# Extract the first word of "ranlib", so it can be a program name with args. +set dummy ranlib; ac_word=$2 +echo $ac_n "checking for $ac_word""... $ac_c" 1>&6 +echo "configure:891: checking for $ac_word" >&5 +if eval "test \"`echo '$''{'ac_cv_prog_RANLIB'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + if test -n "$RANLIB"; then + ac_cv_prog_RANLIB="$RANLIB" # Let the user override the test. +else + IFS="${IFS= }"; ac_save_ifs="$IFS"; IFS=":" + ac_dummy="$PATH" + for ac_dir in $ac_dummy; do + test -z "$ac_dir" && ac_dir=. + if test -f $ac_dir/$ac_word; then + ac_cv_prog_RANLIB="ranlib" + break + fi + done + IFS="$ac_save_ifs" + test -z "$ac_cv_prog_RANLIB" && ac_cv_prog_RANLIB=":" +fi +fi +RANLIB="$ac_cv_prog_RANLIB" +if test -n "$RANLIB"; then + echo "$ac_t""$RANLIB" 1>&6 +else + echo "$ac_t""no" 1>&6 +fi + + + +echo $ac_n "checking how to run the C preprocessor""... $ac_c" 1>&6 +echo "configure:921: checking how to run the C preprocessor" >&5 +# On Suns, sometimes $CPP names a directory. +if test -n "$CPP" && test -d "$CPP"; then + CPP= +fi +if test -z "$CPP"; then +if eval "test \"`echo '$''{'ac_cv_prog_CPP'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + # This must be in double quotes, not single quotes, because CPP may get + # substituted into the Makefile and "${CC-cc}" will confuse make. + CPP="${CC-cc} -E" + # On the NeXT, cc -E runs the code through the compiler's parser, + # not just through cpp. + cat > conftest.$ac_ext < +Syntax Error +EOF +ac_try="$ac_cpp conftest.$ac_ext >/dev/null 2>conftest.out" +{ (eval echo configure:942: \"$ac_try\") 1>&5; (eval $ac_try) 2>&5; } +ac_err=`grep -v '^ *+' conftest.out | grep -v "^conftest.${ac_ext}\$"` +if test -z "$ac_err"; then + : +else + echo "$ac_err" >&5 + echo "configure: failed program was:" >&5 + cat conftest.$ac_ext >&5 + rm -rf conftest* + CPP="${CC-cc} -E -traditional-cpp" + cat > conftest.$ac_ext < +Syntax Error +EOF +ac_try="$ac_cpp conftest.$ac_ext >/dev/null 2>conftest.out" +{ (eval echo configure:959: \"$ac_try\") 1>&5; (eval $ac_try) 2>&5; } +ac_err=`grep -v '^ *+' conftest.out | grep -v "^conftest.${ac_ext}\$"` +if test -z "$ac_err"; then + : +else + echo "$ac_err" >&5 + echo "configure: failed program was:" >&5 + cat conftest.$ac_ext >&5 + rm -rf conftest* + CPP="${CC-cc} -nologo -E" + cat > conftest.$ac_ext < +Syntax Error +EOF +ac_try="$ac_cpp conftest.$ac_ext >/dev/null 2>conftest.out" +{ (eval echo configure:976: \"$ac_try\") 1>&5; (eval $ac_try) 2>&5; } +ac_err=`grep -v '^ *+' conftest.out | grep -v "^conftest.${ac_ext}\$"` +if test -z "$ac_err"; then + : +else + echo "$ac_err" >&5 + echo "configure: failed program was:" >&5 + cat conftest.$ac_ext >&5 + rm -rf conftest* + CPP=/lib/cpp +fi +rm -f conftest* +fi +rm -f conftest* +fi +rm -f conftest* + ac_cv_prog_CPP="$CPP" +fi + CPP="$ac_cv_prog_CPP" +else + ac_cv_prog_CPP="$CPP" +fi +echo "$ac_t""$CPP" 1>&6 + +for ac_hdr in fcntl.h malloc.h stdlib.h sys/stat.h sys/time.h sys/types.h \ + sys/utime.h unistd.h utime.h +do +ac_safe=`echo "$ac_hdr" | sed 'y%./+-%__p_%'` +echo $ac_n "checking for $ac_hdr""... $ac_c" 1>&6 +echo "configure:1005: checking for $ac_hdr" >&5 +if eval "test \"`echo '$''{'ac_cv_header_$ac_safe'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + cat > conftest.$ac_ext < +EOF +ac_try="$ac_cpp conftest.$ac_ext >/dev/null 2>conftest.out" +{ (eval echo configure:1015: \"$ac_try\") 1>&5; (eval $ac_try) 2>&5; } +ac_err=`grep -v '^ *+' conftest.out | grep -v "^conftest.${ac_ext}\$"` +if test -z "$ac_err"; then + rm -rf conftest* + eval "ac_cv_header_$ac_safe=yes" +else + echo "$ac_err" >&5 + echo "configure: failed program was:" >&5 + cat conftest.$ac_ext >&5 + rm -rf conftest* + eval "ac_cv_header_$ac_safe=no" +fi +rm -f conftest* +fi +if eval "test \"`echo '$ac_cv_header_'$ac_safe`\" = yes"; then + echo "$ac_t""yes" 1>&6 + ac_tr_hdr=HAVE_`echo $ac_hdr | sed 'y%abcdefghijklmnopqrstuvwxyz./-%ABCDEFGHIJKLMNOPQRSTUVWXYZ___%'` + cat >> confdefs.h <&6 +fi +done + + +echo $ac_n "checking for working const""... $ac_c" 1>&6 +echo "configure:1043: checking for working const" >&5 +if eval "test \"`echo '$''{'ac_cv_c_const'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + cat > conftest.$ac_ext <j = 5; +} +{ /* ULTRIX-32 V3.1 (Rev 9) vcc rejects this */ + const int foo = 10; +} + +; return 0; } +EOF +if { (eval echo configure:1097: \"$ac_compile\") 1>&5; (eval $ac_compile) 2>&5; }; then + rm -rf conftest* + ac_cv_c_const=yes +else + echo "configure: failed program was:" >&5 + cat conftest.$ac_ext >&5 + rm -rf conftest* + ac_cv_c_const=no +fi +rm -f conftest* +fi + +echo "$ac_t""$ac_cv_c_const" 1>&6 +if test $ac_cv_c_const = no; then + cat >> confdefs.h <<\EOF +#define const +EOF + +fi + +echo $ac_n "checking for inline""... $ac_c" 1>&6 +echo "configure:1118: checking for inline" >&5 +if eval "test \"`echo '$''{'ac_cv_c_inline'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + ac_cv_c_inline=no +for ac_kw in inline __inline__ __inline; do + cat > conftest.$ac_ext <&5; (eval $ac_compile) 2>&5; }; then + rm -rf conftest* + ac_cv_c_inline=$ac_kw; break +else + echo "configure: failed program was:" >&5 + cat conftest.$ac_ext >&5 +fi +rm -f conftest* +done + +fi + +echo "$ac_t""$ac_cv_c_inline" 1>&6 +case "$ac_cv_c_inline" in + inline | yes) ;; + no) cat >> confdefs.h <<\EOF +#define inline +EOF + ;; + *) cat >> confdefs.h <&6 +echo "configure:1158: checking for ANSI C header files" >&5 +if eval "test \"`echo '$''{'ac_cv_header_stdc'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + cat > conftest.$ac_ext < +#include +#include +#include +EOF +ac_try="$ac_cpp conftest.$ac_ext >/dev/null 2>conftest.out" +{ (eval echo configure:1171: \"$ac_try\") 1>&5; (eval $ac_try) 2>&5; } +ac_err=`grep -v '^ *+' conftest.out | grep -v "^conftest.${ac_ext}\$"` +if test -z "$ac_err"; then + rm -rf conftest* + ac_cv_header_stdc=yes +else + echo "$ac_err" >&5 + echo "configure: failed program was:" >&5 + cat conftest.$ac_ext >&5 + rm -rf conftest* + ac_cv_header_stdc=no +fi +rm -f conftest* + +if test $ac_cv_header_stdc = yes; then + # SunOS 4.x string.h does not declare mem*, contrary to ANSI. +cat > conftest.$ac_ext < +EOF +if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | + egrep "memchr" >/dev/null 2>&1; then + : +else + rm -rf conftest* + ac_cv_header_stdc=no +fi +rm -f conftest* + +fi + +if test $ac_cv_header_stdc = yes; then + # ISC 2.0.2 stdlib.h does not declare free, contrary to ANSI. +cat > conftest.$ac_ext < +EOF +if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | + egrep "free" >/dev/null 2>&1; then + : +else + rm -rf conftest* + ac_cv_header_stdc=no +fi +rm -f conftest* + +fi + +if test $ac_cv_header_stdc = yes; then + # /bin/cc in Irix-4.0.5 gets non-ANSI ctype macros unless using -ansi. +if test "$cross_compiling" = yes; then + : +else + cat > conftest.$ac_ext < +#define ISLOWER(c) ('a' <= (c) && (c) <= 'z') +#define TOUPPER(c) (ISLOWER(c) ? 'A' + ((c) - 'a') : (c)) +#define XOR(e, f) (((e) && !(f)) || (!(e) && (f))) +int main () { int i; for (i = 0; i < 256; i++) +if (XOR (islower (i), ISLOWER (i)) || toupper (i) != TOUPPER (i)) exit(2); +exit (0); } + +EOF +if { (eval echo configure:1238: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext} && (./conftest; exit) 2>/dev/null +then + : +else + echo "configure: failed program was:" >&5 + cat conftest.$ac_ext >&5 + rm -fr conftest* + ac_cv_header_stdc=no +fi +rm -fr conftest* +fi + +fi +fi + +echo "$ac_t""$ac_cv_header_stdc" 1>&6 +if test $ac_cv_header_stdc = yes; then + cat >> confdefs.h <<\EOF +#define STDC_HEADERS 1 +EOF + +fi + +echo $ac_n "checking for mode_t""... $ac_c" 1>&6 +echo "configure:1262: checking for mode_t" >&5 +if eval "test \"`echo '$''{'ac_cv_type_mode_t'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + cat > conftest.$ac_ext < +#if STDC_HEADERS +#include +#include +#endif +EOF +if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | + egrep "(^|[^a-zA-Z_0-9])mode_t[^a-zA-Z_0-9]" >/dev/null 2>&1; then + rm -rf conftest* + ac_cv_type_mode_t=yes +else + rm -rf conftest* + ac_cv_type_mode_t=no +fi +rm -f conftest* + +fi +echo "$ac_t""$ac_cv_type_mode_t" 1>&6 +if test $ac_cv_type_mode_t = no; then + cat >> confdefs.h <<\EOF +#define mode_t int +EOF + +fi + +echo $ac_n "checking for off_t""... $ac_c" 1>&6 +echo "configure:1295: checking for off_t" >&5 +if eval "test \"`echo '$''{'ac_cv_type_off_t'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + cat > conftest.$ac_ext < +#if STDC_HEADERS +#include +#include +#endif +EOF +if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | + egrep "(^|[^a-zA-Z_0-9])off_t[^a-zA-Z_0-9]" >/dev/null 2>&1; then + rm -rf conftest* + ac_cv_type_off_t=yes +else + rm -rf conftest* + ac_cv_type_off_t=no +fi +rm -f conftest* + +fi +echo "$ac_t""$ac_cv_type_off_t" 1>&6 +if test $ac_cv_type_off_t = no; then + cat >> confdefs.h <<\EOF +#define off_t long +EOF + +fi + +echo $ac_n "checking for size_t""... $ac_c" 1>&6 +echo "configure:1328: checking for size_t" >&5 +if eval "test \"`echo '$''{'ac_cv_type_size_t'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + cat > conftest.$ac_ext < +#if STDC_HEADERS +#include +#include +#endif +EOF +if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | + egrep "(^|[^a-zA-Z_0-9])size_t[^a-zA-Z_0-9]" >/dev/null 2>&1; then + rm -rf conftest* + ac_cv_type_size_t=yes +else + rm -rf conftest* + ac_cv_type_size_t=no +fi +rm -f conftest* + +fi +echo "$ac_t""$ac_cv_type_size_t" 1>&6 +if test $ac_cv_type_size_t = no; then + cat >> confdefs.h <<\EOF +#define size_t unsigned +EOF + +fi + +echo $ac_n "checking whether struct tm is in sys/time.h or time.h""... $ac_c" 1>&6 +echo "configure:1361: checking whether struct tm is in sys/time.h or time.h" >&5 +if eval "test \"`echo '$''{'ac_cv_struct_tm'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + cat > conftest.$ac_ext < +#include +int main() { +struct tm *tp; tp->tm_sec; +; return 0; } +EOF +if { (eval echo configure:1374: \"$ac_compile\") 1>&5; (eval $ac_compile) 2>&5; }; then + rm -rf conftest* + ac_cv_struct_tm=time.h +else + echo "configure: failed program was:" >&5 + cat conftest.$ac_ext >&5 + rm -rf conftest* + ac_cv_struct_tm=sys/time.h +fi +rm -f conftest* +fi + +echo "$ac_t""$ac_cv_struct_tm" 1>&6 +if test $ac_cv_struct_tm = sys/time.h; then + cat >> confdefs.h <<\EOF +#define TM_IN_SYS_TIME 1 +EOF + +fi + +echo $ac_n "checking for uchar""... $ac_c" 1>&6 +echo "configure:1395: checking for uchar" >&5 +if eval "test \"`echo '$''{'ac_cv_type_uchar'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + cat > conftest.$ac_ext < +#if STDC_HEADERS +#include +#include +#endif +EOF +if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | + egrep "(^|[^a-zA-Z_0-9])uchar[^a-zA-Z_0-9]" >/dev/null 2>&1; then + rm -rf conftest* + ac_cv_type_uchar=yes +else + rm -rf conftest* + ac_cv_type_uchar=no +fi +rm -f conftest* + +fi +echo "$ac_t""$ac_cv_type_uchar" 1>&6 +if test $ac_cv_type_uchar = no; then + cat >> confdefs.h <<\EOF +#define uchar unsigned char +EOF + +fi + +echo $ac_n "checking for ushort""... $ac_c" 1>&6 +echo "configure:1428: checking for ushort" >&5 +if eval "test \"`echo '$''{'ac_cv_type_ushort'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + cat > conftest.$ac_ext < +#if STDC_HEADERS +#include +#include +#endif +EOF +if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | + egrep "(^|[^a-zA-Z_0-9])ushort[^a-zA-Z_0-9]" >/dev/null 2>&1; then + rm -rf conftest* + ac_cv_type_ushort=yes +else + rm -rf conftest* + ac_cv_type_ushort=no +fi +rm -f conftest* + +fi +echo "$ac_t""$ac_cv_type_ushort" 1>&6 +if test $ac_cv_type_ushort = no; then + cat >> confdefs.h <<\EOF +#define ushort unsigned short +EOF + +fi + +echo $ac_n "checking for uint""... $ac_c" 1>&6 +echo "configure:1461: checking for uint" >&5 +if eval "test \"`echo '$''{'ac_cv_type_uint'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + cat > conftest.$ac_ext < +#if STDC_HEADERS +#include +#include +#endif +EOF +if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | + egrep "(^|[^a-zA-Z_0-9])uint[^a-zA-Z_0-9]" >/dev/null 2>&1; then + rm -rf conftest* + ac_cv_type_uint=yes +else + rm -rf conftest* + ac_cv_type_uint=no +fi +rm -f conftest* + +fi +echo "$ac_t""$ac_cv_type_uint" 1>&6 +if test $ac_cv_type_uint = no; then + cat >> confdefs.h <<\EOF +#define uint unsigned int +EOF + +fi + +echo $ac_n "checking for ulong""... $ac_c" 1>&6 +echo "configure:1494: checking for ulong" >&5 +if eval "test \"`echo '$''{'ac_cv_type_ulong'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + cat > conftest.$ac_ext < +#if STDC_HEADERS +#include +#include +#endif +EOF +if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | + egrep "(^|[^a-zA-Z_0-9])ulong[^a-zA-Z_0-9]" >/dev/null 2>&1; then + rm -rf conftest* + ac_cv_type_ulong=yes +else + rm -rf conftest* + ac_cv_type_ulong=no +fi +rm -f conftest* + +fi +echo "$ac_t""$ac_cv_type_ulong" 1>&6 +if test $ac_cv_type_ulong = no; then + cat >> confdefs.h <<\EOF +#define ulong unsigned long +EOF + +fi + + +for ac_func in fdopen ftruncate memmove mkdir mkstemp mktime timelocal \ + localtime_r snprintf strcasecmp strncasecmp strtoul strerror vsnprintf +do +echo $ac_n "checking for $ac_func""... $ac_c" 1>&6 +echo "configure:1531: checking for $ac_func" >&5 +if eval "test \"`echo '$''{'ac_cv_func_$ac_func'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + cat > conftest.$ac_ext < +/* Override any gcc2 internal prototype to avoid an error. */ +/* We use char because int might match the return type of a gcc2 + builtin and then its argument prototype would still apply. */ +char $ac_func(); + +int main() { + +/* The GNU C library defines this for functions which it implements + to always fail with ENOSYS. Some functions are actually named + something starting with __ and the normal name is an alias. */ +#if defined (__stub_$ac_func) || defined (__stub___$ac_func) +choke me +#else +$ac_func(); +#endif + +; return 0; } +EOF +if { (eval echo configure:1559: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then + rm -rf conftest* + eval "ac_cv_func_$ac_func=yes" +else + echo "configure: failed program was:" >&5 + cat conftest.$ac_ext >&5 + rm -rf conftest* + eval "ac_cv_func_$ac_func=no" +fi +rm -f conftest* +fi + +if eval "test \"`echo '$ac_cv_func_'$ac_func`\" = yes"; then + echo "$ac_t""yes" 1>&6 + ac_tr_func=HAVE_`echo $ac_func | tr 'abcdefghijklmnopqrstuvwxyz' 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'` + cat >> confdefs.h <&6 +fi +done + + +echo $ac_n "checking if snprintf is declared""... $ac_c" 1>&6 +echo "configure:1585: checking if snprintf is declared" >&5 +if eval "test \"`echo '$''{'nufxlib_cv_snprintf_in_header'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + + cat > conftest.$ac_ext < +EOF +if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | + egrep "snprintf" >/dev/null 2>&1; then + rm -rf conftest* + nufxlib_cv_snprintf_in_header=yes +else + rm -rf conftest* + nufxlib_cv_snprintf_in_header=no +fi +rm -f conftest* + + +fi + +if test $nufxlib_cv_snprintf_in_header = "yes"; then + cat >> confdefs.h <<\EOF +#define SNPRINTF_DECLARED 1 +EOF + +fi +echo "$ac_t""$nufxlib_cv_snprintf_in_header" 1>&6 + +echo $ac_n "checking if vsnprintf is declared""... $ac_c" 1>&6 +echo "configure:1617: checking if vsnprintf is declared" >&5 +if eval "test \"`echo '$''{'nufxlib_cv_vsnprintf_in_header'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + + cat > conftest.$ac_ext < +EOF +if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | + egrep "vsnprintf" >/dev/null 2>&1; then + rm -rf conftest* + nufxlib_cv_vsnprintf_in_header=yes +else + rm -rf conftest* + nufxlib_cv_vsnprintf_in_header=no +fi +rm -f conftest* + + +fi + +if test $nufxlib_cv_vsnprintf_in_header = "yes"; then + cat >> confdefs.h <<\EOF +#define VSNPRINTF_DECLARED 1 +EOF + +fi +echo "$ac_t""$nufxlib_cv_vsnprintf_in_header" 1>&6 + +SHARE_FLAGS='-shared' +if test "$host_cpu" = "powerpc" -a "$host_os" = "beos"; then + CC=cc + GCC= + CFLAGS='-proc 603 -opt full' + SHARE_FLAGS='-shared -nostdlib' + echo "forcing CC to \"$CC\" and CFLAGS to \"$CFLAGS\"" +elif test "$host_os" = "beos"; then + SHARE_FLAGS='-nostartfiles -Xlinker -soname="$@"' +fi + +if test -z "$GCC"; then + BUILD_FLAGS='$(OPT)' +else + BUILD_FLAGS='$(OPT) $(GCC_FLAGS)' +fi + + + + + +if test "$host_os" = "beos"; then + if test "$prefix" = "NONE" -a \ + "$includedir" = '${prefix}/include' -a \ + "$libdir" = '${exec_prefix}/lib' -a \ + "$bindir" = '${exec_prefix}/bin' -a \ + "$mandir" = '${prefix}/man' + then + echo replacing install locations with BeOS values + prefix=/boot + includedir='${prefix}/develop/headers' + libdir='${exec_prefix}/home/config/lib' + bindir='${exec_prefix}/home/config/bin' + mandir='/tmp' + + + + + + fi +fi + +echo $ac_n "checking if sprintf returns int""... $ac_c" 1>&6 +echo "configure:1691: checking if sprintf returns int" >&5 +if eval "test \"`echo '$''{'nufxlib_cv_sprintf_returns_int'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + + if test "$cross_compiling" = yes; then + nufxlib_cv_sprintf_returns_int=no +else + cat > conftest.$ac_ext < + int main(void) + { + int count; + char buf[8]; + count = sprintf(buf, "123"); /* should return three */ + exit(count != 3); + } + +EOF +if { (eval echo configure:1713: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext} && (./conftest; exit) 2>/dev/null +then + nufxlib_cv_sprintf_returns_int=yes +else + echo "configure: failed program was:" >&5 + cat conftest.$ac_ext >&5 + rm -fr conftest* + nufxlib_cv_sprintf_returns_int=no +fi +rm -fr conftest* +fi + + +fi + +if test $nufxlib_cv_sprintf_returns_int = "yes"; then + cat >> confdefs.h <<\EOF +#define SPRINTF_RETURNS_INT 1 +EOF + +fi +echo "$ac_t""$nufxlib_cv_sprintf_returns_int" 1>&6 + +DMALLOC= +# Check whether --enable-dmalloc or --disable-dmalloc was given. +if test "${enable_dmalloc+set}" = set; then + enableval="$enable_dmalloc" + \ + echo "--- enabling dmalloc"; \ + DMALLOC="-L/usr/local/lib -ldmalloc"; cat >> confdefs.h <<\EOF +#define USE_DMALLOC 1 +EOF + +fi + + + +trap '' 1 2 15 +cat > confcache <<\EOF +# This file is a shell script that caches the results of configure +# tests run on this system so they can be shared between configure +# scripts and configure runs. It is not useful on other systems. +# If it contains results you don't want to keep, you may remove or edit it. +# +# By default, configure uses ./config.cache as the cache file, +# creating it if it does not exist already. You can give configure +# the --cache-file=FILE option to use a different cache file; that is +# what configure does when it calls configure scripts in +# subdirectories, so they share the cache. +# Giving --cache-file=/dev/null disables caching, for debugging configure. +# config.status only pays attention to the cache file if you give it the +# --recheck option to rerun configure. +# +EOF +# The following way of writing the cache mishandles newlines in values, +# but we know of no workaround that is simple, portable, and efficient. +# So, don't put newlines in cache variables' values. +# Ultrix sh set writes to stderr and can't be redirected directly, +# and sets the high bit in the cache file unless we assign to the vars. +(set) 2>&1 | + case `(ac_space=' '; set | grep ac_space) 2>&1` in + *ac_space=\ *) + # `set' does not quote correctly, so add quotes (double-quote substitution + # turns \\\\ into \\, and sed turns \\ into \). + sed -n \ + -e "s/'/'\\\\''/g" \ + -e "s/^\\([a-zA-Z0-9_]*_cv_[a-zA-Z0-9_]*\\)=\\(.*\\)/\\1=\${\\1='\\2'}/p" + ;; + *) + # `set' quotes correctly as required by POSIX, so do not add quotes. + sed -n -e 's/^\([a-zA-Z0-9_]*_cv_[a-zA-Z0-9_]*\)=\(.*\)/\1=${\1=\2}/p' + ;; + esac >> confcache +if cmp -s $cache_file confcache; then + : +else + if test -w $cache_file; then + echo "updating cache $cache_file" + cat confcache > $cache_file + else + echo "not updating unwritable cache $cache_file" + fi +fi +rm -f confcache + +trap 'rm -fr conftest* confdefs* core core.* *.core $ac_clean_files; exit 1' 1 2 15 + +test "x$prefix" = xNONE && prefix=$ac_default_prefix +# Let make expand exec_prefix. +test "x$exec_prefix" = xNONE && exec_prefix='${prefix}' + +# Any assignment to VPATH causes Sun make to only execute +# the first set of double-colon rules, so remove it if not needed. +# If there is a colon in the path, we need to keep it. +if test "x$srcdir" = x.; then + ac_vpsub='/^[ ]*VPATH[ ]*=[^:]*$/d' +fi + +trap 'rm -f $CONFIG_STATUS conftest*; exit 1' 1 2 15 + +DEFS=-DHAVE_CONFIG_H + +# Without the "./", some shells look in PATH for config.status. +: ${CONFIG_STATUS=./config.status} + +echo creating $CONFIG_STATUS +rm -f $CONFIG_STATUS +cat > $CONFIG_STATUS </dev/null | sed 1q`: +# +# $0 $ac_configure_args +# +# Compiler output produced by configure, useful for debugging +# configure, is in ./config.log if it exists. + +ac_cs_usage="Usage: $CONFIG_STATUS [--recheck] [--version] [--help]" +for ac_option +do + case "\$ac_option" in + -recheck | --recheck | --rechec | --reche | --rech | --rec | --re | --r) + echo "running \${CONFIG_SHELL-/bin/sh} $0 $ac_configure_args --no-create --no-recursion" + exec \${CONFIG_SHELL-/bin/sh} $0 $ac_configure_args --no-create --no-recursion ;; + -version | --version | --versio | --versi | --vers | --ver | --ve | --v) + echo "$CONFIG_STATUS generated by autoconf version 2.13" + exit 0 ;; + -help | --help | --hel | --he | --h) + echo "\$ac_cs_usage"; exit 0 ;; + *) echo "\$ac_cs_usage"; exit 1 ;; + esac +done + +ac_given_srcdir=$srcdir +ac_given_INSTALL="$INSTALL" + +trap 'rm -fr `echo "Makefile samples/Makefile config.h" | sed "s/:[^ ]*//g"` conftest*; exit 1' 1 2 15 +EOF +cat >> $CONFIG_STATUS < conftest.subs <<\\CEOF +$ac_vpsub +$extrasub +s%@SHELL@%$SHELL%g +s%@CFLAGS@%$CFLAGS%g +s%@CPPFLAGS@%$CPPFLAGS%g +s%@CXXFLAGS@%$CXXFLAGS%g +s%@FFLAGS@%$FFLAGS%g +s%@DEFS@%$DEFS%g +s%@LDFLAGS@%$LDFLAGS%g +s%@LIBS@%$LIBS%g +s%@exec_prefix@%$exec_prefix%g +s%@prefix@%$prefix%g +s%@program_transform_name@%$program_transform_name%g +s%@bindir@%$bindir%g +s%@sbindir@%$sbindir%g +s%@libexecdir@%$libexecdir%g +s%@datadir@%$datadir%g +s%@sysconfdir@%$sysconfdir%g +s%@sharedstatedir@%$sharedstatedir%g +s%@localstatedir@%$localstatedir%g +s%@libdir@%$libdir%g +s%@includedir@%$includedir%g +s%@oldincludedir@%$oldincludedir%g +s%@infodir@%$infodir%g +s%@mandir@%$mandir%g +s%@host@%$host%g +s%@host_alias@%$host_alias%g +s%@host_cpu@%$host_cpu%g +s%@host_vendor@%$host_vendor%g +s%@host_os@%$host_os%g +s%@CC@%$CC%g +s%@INSTALL_PROGRAM@%$INSTALL_PROGRAM%g +s%@INSTALL_SCRIPT@%$INSTALL_SCRIPT%g +s%@INSTALL_DATA@%$INSTALL_DATA%g +s%@SET_MAKE@%$SET_MAKE%g +s%@RANLIB@%$RANLIB%g +s%@CPP@%$CPP%g +s%@BUILD_FLAGS@%$BUILD_FLAGS%g +s%@SHARE_FLAGS@%$SHARE_FLAGS%g +s%@DMALLOC@%$DMALLOC%g + +CEOF +EOF + +cat >> $CONFIG_STATUS <<\EOF + +# Split the substitutions into bite-sized pieces for seds with +# small command number limits, like on Digital OSF/1 and HP-UX. +ac_max_sed_cmds=90 # Maximum number of lines to put in a sed script. +ac_file=1 # Number of current file. +ac_beg=1 # First line for current file. +ac_end=$ac_max_sed_cmds # Line after last line for current file. +ac_more_lines=: +ac_sed_cmds="" +while $ac_more_lines; do + if test $ac_beg -gt 1; then + sed "1,${ac_beg}d; ${ac_end}q" conftest.subs > conftest.s$ac_file + else + sed "${ac_end}q" conftest.subs > conftest.s$ac_file + fi + if test ! -s conftest.s$ac_file; then + ac_more_lines=false + rm -f conftest.s$ac_file + else + if test -z "$ac_sed_cmds"; then + ac_sed_cmds="sed -f conftest.s$ac_file" + else + ac_sed_cmds="$ac_sed_cmds | sed -f conftest.s$ac_file" + fi + ac_file=`expr $ac_file + 1` + ac_beg=$ac_end + ac_end=`expr $ac_end + $ac_max_sed_cmds` + fi +done +if test -z "$ac_sed_cmds"; then + ac_sed_cmds=cat +fi +EOF + +cat >> $CONFIG_STATUS <> $CONFIG_STATUS <<\EOF +for ac_file in .. $CONFIG_FILES; do if test "x$ac_file" != x..; then + # Support "outfile[:infile[:infile...]]", defaulting infile="outfile.in". + case "$ac_file" in + *:*) ac_file_in=`echo "$ac_file"|sed 's%[^:]*:%%'` + ac_file=`echo "$ac_file"|sed 's%:.*%%'` ;; + *) ac_file_in="${ac_file}.in" ;; + esac + + # Adjust a relative srcdir, top_srcdir, and INSTALL for subdirectories. + + # Remove last slash and all that follows it. Not all systems have dirname. + ac_dir=`echo $ac_file|sed 's%/[^/][^/]*$%%'` + if test "$ac_dir" != "$ac_file" && test "$ac_dir" != .; then + # The file is in a subdirectory. + test ! -d "$ac_dir" && mkdir "$ac_dir" + ac_dir_suffix="/`echo $ac_dir|sed 's%^\./%%'`" + # A "../" for each directory in $ac_dir_suffix. + ac_dots=`echo $ac_dir_suffix|sed 's%/[^/]*%../%g'` + else + ac_dir_suffix= ac_dots= + fi + + case "$ac_given_srcdir" in + .) srcdir=. + if test -z "$ac_dots"; then top_srcdir=. + else top_srcdir=`echo $ac_dots|sed 's%/$%%'`; fi ;; + /*) srcdir="$ac_given_srcdir$ac_dir_suffix"; top_srcdir="$ac_given_srcdir" ;; + *) # Relative path. + srcdir="$ac_dots$ac_given_srcdir$ac_dir_suffix" + top_srcdir="$ac_dots$ac_given_srcdir" ;; + esac + + case "$ac_given_INSTALL" in + [/$]*) INSTALL="$ac_given_INSTALL" ;; + *) INSTALL="$ac_dots$ac_given_INSTALL" ;; + esac + + echo creating "$ac_file" + rm -f "$ac_file" + configure_input="Generated automatically from `echo $ac_file_in|sed 's%.*/%%'` by configure." + case "$ac_file" in + *Makefile*) ac_comsub="1i\\ +# $configure_input" ;; + *) ac_comsub= ;; + esac + + ac_file_inputs=`echo $ac_file_in|sed -e "s%^%$ac_given_srcdir/%" -e "s%:% $ac_given_srcdir/%g"` + sed -e "$ac_comsub +s%@configure_input@%$configure_input%g +s%@srcdir@%$srcdir%g +s%@top_srcdir@%$top_srcdir%g +s%@INSTALL@%$INSTALL%g +" $ac_file_inputs | (eval "$ac_sed_cmds") > $ac_file +fi; done +rm -f conftest.s* + +# These sed commands are passed to sed as "A NAME B NAME C VALUE D", where +# NAME is the cpp macro being defined and VALUE is the value it is being given. +# +# ac_d sets the value in "#define NAME VALUE" lines. +ac_dA='s%^\([ ]*\)#\([ ]*define[ ][ ]*\)' +ac_dB='\([ ][ ]*\)[^ ]*%\1#\2' +ac_dC='\3' +ac_dD='%g' +# ac_u turns "#undef NAME" with trailing blanks into "#define NAME VALUE". +ac_uA='s%^\([ ]*\)#\([ ]*\)undef\([ ][ ]*\)' +ac_uB='\([ ]\)%\1#\2define\3' +ac_uC=' ' +ac_uD='\4%g' +# ac_e turns "#undef NAME" without trailing blanks into "#define NAME VALUE". +ac_eA='s%^\([ ]*\)#\([ ]*\)undef\([ ][ ]*\)' +ac_eB='$%\1#\2define\3' +ac_eC=' ' +ac_eD='%g' + +if test "${CONFIG_HEADERS+set}" != set; then +EOF +cat >> $CONFIG_STATUS <> $CONFIG_STATUS <<\EOF +fi +for ac_file in .. $CONFIG_HEADERS; do if test "x$ac_file" != x..; then + # Support "outfile[:infile[:infile...]]", defaulting infile="outfile.in". + case "$ac_file" in + *:*) ac_file_in=`echo "$ac_file"|sed 's%[^:]*:%%'` + ac_file=`echo "$ac_file"|sed 's%:.*%%'` ;; + *) ac_file_in="${ac_file}.in" ;; + esac + + echo creating $ac_file + + rm -f conftest.frag conftest.in conftest.out + ac_file_inputs=`echo $ac_file_in|sed -e "s%^%$ac_given_srcdir/%" -e "s%:% $ac_given_srcdir/%g"` + cat $ac_file_inputs > conftest.in + +EOF + +# Transform confdefs.h into a sed script conftest.vals that substitutes +# the proper values into config.h.in to produce config.h. And first: +# Protect against being on the right side of a sed subst in config.status. +# Protect against being in an unquoted here document in config.status. +rm -f conftest.vals +cat > conftest.hdr <<\EOF +s/[\\&%]/\\&/g +s%[\\$`]%\\&%g +s%#define \([A-Za-z_][A-Za-z0-9_]*\) *\(.*\)%${ac_dA}\1${ac_dB}\1${ac_dC}\2${ac_dD}%gp +s%ac_d%ac_u%gp +s%ac_u%ac_e%gp +EOF +sed -n -f conftest.hdr confdefs.h > conftest.vals +rm -f conftest.hdr + +# This sed command replaces #undef with comments. This is necessary, for +# example, in the case of _POSIX_SOURCE, which is predefined and required +# on some systems where configure will not decide to define it. +cat >> conftest.vals <<\EOF +s%^[ ]*#[ ]*undef[ ][ ]*[a-zA-Z_][a-zA-Z_0-9]*%/* & */% +EOF + +# Break up conftest.vals because some shells have a limit on +# the size of here documents, and old seds have small limits too. + +rm -f conftest.tail +while : +do + ac_lines=`grep -c . conftest.vals` + # grep -c gives empty output for an empty file on some AIX systems. + if test -z "$ac_lines" || test "$ac_lines" -eq 0; then break; fi + # Write a limited-size here document to conftest.frag. + echo ' cat > conftest.frag <> $CONFIG_STATUS + sed ${ac_max_here_lines}q conftest.vals >> $CONFIG_STATUS + echo 'CEOF + sed -f conftest.frag conftest.in > conftest.out + rm -f conftest.in + mv conftest.out conftest.in +' >> $CONFIG_STATUS + sed 1,${ac_max_here_lines}d conftest.vals > conftest.tail + rm -f conftest.vals + mv conftest.tail conftest.vals +done +rm -f conftest.vals + +cat >> $CONFIG_STATUS <<\EOF + rm -f conftest.frag conftest.h + echo "/* $ac_file. Generated automatically by configure. */" > conftest.h + cat conftest.in >> conftest.h + rm -f conftest.in + if cmp -s $ac_file conftest.h 2>/dev/null; then + echo "$ac_file is unchanged" + rm -f conftest.h + else + # Remove last slash and all that follows it. Not all systems have dirname. + ac_dir=`echo $ac_file|sed 's%/[^/][^/]*$%%'` + if test "$ac_dir" != "$ac_file" && test "$ac_dir" != .; then + # The file is in a subdirectory. + test ! -d "$ac_dir" && mkdir "$ac_dir" + fi + rm -f $ac_file + mv conftest.h $ac_file + fi +fi; done + +EOF +cat >> $CONFIG_STATUS <> $CONFIG_STATUS <<\EOF + +exit 0 +EOF +chmod +x $CONFIG_STATUS +rm -fr confdefs* $ac_clean_files +test "$no_create" = yes || ${CONFIG_SHELL-/bin/sh} $CONFIG_STATUS || exit 1 + diff --git a/nufxlib-0/configure.in b/nufxlib-0/configure.in new file mode 100644 index 0000000..18da94b --- /dev/null +++ b/nufxlib-0/configure.in @@ -0,0 +1,136 @@ +dnl Process this file with autoconf to produce a configure script. +AC_INIT(NufxLibPriv.h) +AC_CONFIG_HEADER(config.h) + +dnl Checks for programs. +AC_CANONICAL_HOST +dnl AC_PROG_AWK +AC_PROG_CC +AC_PROG_INSTALL +AC_PROG_MAKE_SET +AC_PROG_RANLIB + +dnl Checks for libraries. + +dnl Checks for header files. +AC_CHECK_HEADERS(fcntl.h malloc.h stdlib.h sys/stat.h sys/time.h sys/types.h \ + sys/utime.h unistd.h utime.h) + +dnl Checks for typedefs, structures, and compiler characteristics. +AC_C_CONST +AC_C_INLINE +AC_TYPE_MODE_T +AC_TYPE_OFF_T +AC_TYPE_SIZE_T +AC_STRUCT_TM +AC_CHECK_TYPE(uchar, unsigned char) +AC_CHECK_TYPE(ushort, unsigned short) +AC_CHECK_TYPE(uint, unsigned int) +AC_CHECK_TYPE(ulong, unsigned long) + +dnl Checks for library functions. +AC_CHECK_FUNCS(fdopen ftruncate memmove mkdir mkstemp mktime timelocal \ + localtime_r snprintf strcasecmp strncasecmp strtoul strerror vsnprintf) + +dnl Kent says: snprintf doesn't always have a declaration +AC_MSG_CHECKING(if snprintf is declared) +AC_CACHE_VAL(nufxlib_cv_snprintf_in_header, [ + AC_EGREP_HEADER(snprintf, stdio.h, + [nufxlib_cv_snprintf_in_header=yes], + [nufxlib_cv_snprintf_in_header=no]) +]) +if test $nufxlib_cv_snprintf_in_header = "yes"; then + AC_DEFINE(SNPRINTF_DECLARED) +fi +AC_MSG_RESULT($nufxlib_cv_snprintf_in_header) + +dnl Kent says: vsnprintf doesn't always have a declaration +AC_MSG_CHECKING(if vsnprintf is declared) +AC_CACHE_VAL(nufxlib_cv_vsnprintf_in_header, [ + AC_EGREP_HEADER(vsnprintf, stdio.h, + [nufxlib_cv_vsnprintf_in_header=yes], + [nufxlib_cv_vsnprintf_in_header=no]) +]) +if test $nufxlib_cv_vsnprintf_in_header = "yes"; then + AC_DEFINE(VSNPRINTF_DECLARED) +fi +AC_MSG_RESULT($nufxlib_cv_vsnprintf_in_header) + +dnl Figure out what the build and link flags should be +SHARE_FLAGS='-shared' +if test "$host_cpu" = "powerpc" -a "$host_os" = "beos"; then + dnl BeOS/PPC, with Metrowerks compiler + CC=cc + GCC= + CFLAGS='-proc 603 -opt full' + SHARE_FLAGS='-shared -nostdlib' + echo "forcing CC to \"$CC\" and CFLAGS to \"$CFLAGS\"" +elif test "$host_os" = "beos"; then + dnl BeOS/x86 + SHARE_FLAGS='-nostartfiles -Xlinker -soname="$@"' +fi + +dnl if we're using gcc, include gcc-specific warning flags +if test -z "$GCC"; then + BUILD_FLAGS='$(OPT)' +else + BUILD_FLAGS='$(OPT) $(GCC_FLAGS)' +fi + +AC_SUBST(BUILD_FLAGS) +AC_SUBST(SHARE_FLAGS) + + +dnl BeOS doesn't like /usr/local/include, and gets feisty about it. If libdir +dnl and includedir are set to defaults, replace them with BeOS values. This +dnl might be going a little too far... +if test "$host_os" = "beos"; then + if test "$prefix" = "NONE" -a \ + "$includedir" = '${prefix}/include' -a \ + "$libdir" = '${exec_prefix}/lib' -a \ + "$bindir" = '${exec_prefix}/bin' -a \ + "$mandir" = '${prefix}/man' + then + echo replacing install locations with BeOS values + prefix=/boot + includedir='${prefix}/develop/headers' + libdir='${exec_prefix}/home/config/lib' + bindir='${exec_prefix}/home/config/bin' + mandir='/tmp' + AC_SUBST(prefix) + AC_SUBST(includedir) + AC_SUBST(libdir) + AC_SUBST(bindir) + AC_SUBST(mandir) + fi +fi + +dnl Test to see if sprintf does something reasonable. I'm going to assume +dnl that vsprintf and (if available) vsnprintf return the same thing. +AC_MSG_CHECKING(if sprintf returns int) +AC_CACHE_VAL(nufxlib_cv_sprintf_returns_int, [ + AC_TRY_RUN([ + #include + int main(void) + { + int count; + char buf[8]; + count = sprintf(buf, "123"); /* should return three */ + exit(count != 3); + } + ], + [nufxlib_cv_sprintf_returns_int=yes], [nufxlib_cv_sprintf_returns_int=no], + [nufxlib_cv_sprintf_returns_int=no]) +]) +if test $nufxlib_cv_sprintf_returns_int = "yes"; then + AC_DEFINE(SPRINTF_RETURNS_INT) +fi +AC_MSG_RESULT($nufxlib_cv_sprintf_returns_int) + +DMALLOC= +AC_ARG_ENABLE(dmalloc, [ --enable-dmalloc: do dmalloc stuff], \ + [ echo "--- enabling dmalloc"; \ + DMALLOC="-L/usr/local/lib -ldmalloc"; AC_DEFINE(USE_DMALLOC) ]) +AC_SUBST(DMALLOC) + +AC_OUTPUT(Makefile samples/Makefile) diff --git a/nufxlib-0/install-sh b/nufxlib-0/install-sh new file mode 100755 index 0000000..e9de238 --- /dev/null +++ b/nufxlib-0/install-sh @@ -0,0 +1,251 @@ +#!/bin/sh +# +# install - install a program, script, or datafile +# This comes from X11R5 (mit/util/scripts/install.sh). +# +# Copyright 1991 by the Massachusetts Institute of Technology +# +# Permission to use, copy, modify, distribute, and sell this software and its +# documentation for any purpose is hereby granted without fee, provided that +# the above copyright notice appear in all copies and that both that +# copyright notice and this permission notice appear in supporting +# documentation, and that the name of M.I.T. not be used in advertising or +# publicity pertaining to distribution of the software without specific, +# written prior permission. M.I.T. makes no representations about the +# suitability of this software for any purpose. It is provided "as is" +# without express or implied warranty. +# +# Calling this script install-sh is preferred over install.sh, to prevent +# `make' implicit rules from creating a file called install from it +# when there is no Makefile. +# +# This script is compatible with the BSD install script, but was written +# from scratch. It can only install one file at a time, a restriction +# shared with many OS's install programs. + + +# set DOITPROG to echo to test this script + +# Don't use :- since 4.3BSD and earlier shells don't like it. +doit="${DOITPROG-}" + + +# put in absolute paths if you don't have them in your path; or use env. vars. + +mvprog="${MVPROG-mv}" +cpprog="${CPPROG-cp}" +chmodprog="${CHMODPROG-chmod}" +chownprog="${CHOWNPROG-chown}" +chgrpprog="${CHGRPPROG-chgrp}" +stripprog="${STRIPPROG-strip}" +rmprog="${RMPROG-rm}" +mkdirprog="${MKDIRPROG-mkdir}" + +transformbasename="" +transform_arg="" +instcmd="$mvprog" +chmodcmd="$chmodprog 0755" +chowncmd="" +chgrpcmd="" +stripcmd="" +rmcmd="$rmprog -f" +mvcmd="$mvprog" +src="" +dst="" +dir_arg="" + +while [ x"$1" != x ]; do + case $1 in + -c) instcmd="$cpprog" + shift + continue;; + + -d) dir_arg=true + shift + continue;; + + -m) chmodcmd="$chmodprog $2" + shift + shift + continue;; + + -o) chowncmd="$chownprog $2" + shift + shift + continue;; + + -g) chgrpcmd="$chgrpprog $2" + shift + shift + continue;; + + -s) stripcmd="$stripprog" + shift + continue;; + + -t=*) transformarg=`echo $1 | sed 's/-t=//'` + shift + continue;; + + -b=*) transformbasename=`echo $1 | sed 's/-b=//'` + shift + continue;; + + *) if [ x"$src" = x ] + then + src=$1 + else + # this colon is to work around a 386BSD /bin/sh bug + : + dst=$1 + fi + shift + continue;; + esac +done + +if [ x"$src" = x ] +then + echo "install: no input file specified" + exit 1 +else + true +fi + +if [ x"$dir_arg" != x ]; then + dst=$src + src="" + + if [ -d $dst ]; then + instcmd=: + chmodcmd="" + else + instcmd=mkdir + fi +else + +# Waiting for this to be detected by the "$instcmd $src $dsttmp" command +# might cause directories to be created, which would be especially bad +# if $src (and thus $dsttmp) contains '*'. + + if [ -f $src -o -d $src ] + then + true + else + echo "install: $src does not exist" + exit 1 + fi + + if [ x"$dst" = x ] + then + echo "install: no destination specified" + exit 1 + else + true + fi + +# If destination is a directory, append the input filename; if your system +# does not like double slashes in filenames, you may need to add some logic + + if [ -d $dst ] + then + dst="$dst"/`basename $src` + else + true + fi +fi + +## this sed command emulates the dirname command +dstdir=`echo $dst | sed -e 's,[^/]*$,,;s,/$,,;s,^$,.,'` + +# Make sure that the destination directory exists. +# this part is taken from Noah Friedman's mkinstalldirs script + +# Skip lots of stat calls in the usual case. +if [ ! -d "$dstdir" ]; then +defaultIFS=' +' +IFS="${IFS-${defaultIFS}}" + +oIFS="${IFS}" +# Some sh's can't handle IFS=/ for some reason. +IFS='%' +set - `echo ${dstdir} | sed -e 's@/@%@g' -e 's@^%@/@'` +IFS="${oIFS}" + +pathcomp='' + +while [ $# -ne 0 ] ; do + pathcomp="${pathcomp}${1}" + shift + + if [ ! -d "${pathcomp}" ] ; + then + $mkdirprog "${pathcomp}" + else + true + fi + + pathcomp="${pathcomp}/" +done +fi + +if [ x"$dir_arg" != x ] +then + $doit $instcmd $dst && + + if [ x"$chowncmd" != x ]; then $doit $chowncmd $dst; else true ; fi && + if [ x"$chgrpcmd" != x ]; then $doit $chgrpcmd $dst; else true ; fi && + if [ x"$stripcmd" != x ]; then $doit $stripcmd $dst; else true ; fi && + if [ x"$chmodcmd" != x ]; then $doit $chmodcmd $dst; else true ; fi +else + +# If we're going to rename the final executable, determine the name now. + + if [ x"$transformarg" = x ] + then + dstfile=`basename $dst` + else + dstfile=`basename $dst $transformbasename | + sed $transformarg`$transformbasename + fi + +# don't allow the sed command to completely eliminate the filename + + if [ x"$dstfile" = x ] + then + dstfile=`basename $dst` + else + true + fi + +# Make a temp file name in the proper directory. + + dsttmp=$dstdir/#inst.$$# + +# Move or copy the file name to the temp name + + $doit $instcmd $src $dsttmp && + + trap "rm -f ${dsttmp}" 0 && + +# and set any options; do chmod last to preserve setuid bits + +# If any of these fail, we abort the whole thing. If we want to +# ignore errors from any of these, just make sure not to ignore +# errors from the above "$doit $instcmd $src $dsttmp" command. + + if [ x"$chowncmd" != x ]; then $doit $chowncmd $dsttmp; else true;fi && + if [ x"$chgrpcmd" != x ]; then $doit $chgrpcmd $dsttmp; else true;fi && + if [ x"$stripcmd" != x ]; then $doit $stripcmd $dsttmp; else true;fi && + if [ x"$chmodcmd" != x ]; then $doit $chmodcmd $dsttmp; else true;fi && + +# Now rename the file to the real destination. + + $doit $rmcmd -f $dstdir/$dstfile && + $doit $mvcmd $dsttmp $dstdir/$dstfile + +fi && + + +exit 0 diff --git a/nufxlib-0/mkinstalldirs b/nufxlib-0/mkinstalldirs new file mode 100755 index 0000000..936cf34 --- /dev/null +++ b/nufxlib-0/mkinstalldirs @@ -0,0 +1,34 @@ +#! /bin/sh +# mkinstalldirs --- make directory hierarchy +# Author: Noah Friedman +# Created: 1993-05-16 +# Last modified: 1995-03-05 +# Public domain + +errstatus=0 + +for file in ${1+"$@"} ; do + set fnord `echo ":$file" | sed -ne 's/^:\//#/;s/^://;s/\// /g;s/^#/\//;p'` + shift + + pathcomp= + for d in ${1+"$@"} ; do + pathcomp="$pathcomp$d" + case "$pathcomp" in + -* ) pathcomp=./$pathcomp ;; + esac + + if test ! -d "$pathcomp"; then + echo "mkdir $pathcomp" 1>&2 + mkdir "$pathcomp" > /dev/null 2>&1 || lasterr=$? + fi + + if test ! -d "$pathcomp"; then + errstatus=$lasterr + fi + + pathcomp="$pathcomp/" + done +done + +exit $errstatus diff --git a/nufxlib-0/samples/Common.h b/nufxlib-0/samples/Common.h new file mode 100644 index 0000000..e6d1366 --- /dev/null +++ b/nufxlib-0/samples/Common.h @@ -0,0 +1,66 @@ +/* + * NuFX archive manipulation library + * 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 Library General Public License, see the file COPYING.LIB. + * + * Common functions for NuLib tests + */ +#ifndef __Common__ +#define __Common__ + +#include "SysDefs.h" /* might as well draft off the autoconf */ +#include "NufxLib.h" + +#ifdef USE_DMALLOC +# include "dmalloc.h" +#endif + +#define nil NULL /* this is seriously habit-forming */ + +#define NELEM(x) (sizeof(x) / sizeof((x)[0])) + +#ifndef __cplusplus + #define false 0 + #define true (!false) +#endif + + +#ifdef FOPEN_WANTS_B +# define kNuFileOpenReadOnly "rb" +# define kNuFileOpenReadWrite "r+b" +# define kNuFileOpenWriteTrunc "wb" +# define kNuFileOpenReadWriteCreat "w+b" +#else +# define kNuFileOpenReadOnly "r" +# define kNuFileOpenReadWrite "r+" +# define kNuFileOpenWriteTrunc "w" +# define kNuFileOpenReadWriteCreat "w+" +#endif + + +/* + * Figure out what path separator to use. + */ + +#ifdef MSDOS +# define PATH_SEP '\\' +#endif + +#ifdef WIN32 +# define PATH_SEP '\\' +#endif + +#ifdef MACOS +# define PATH_SEP ':' +#endif + +#if defined(APW) || defined(__ORCAC__) +# define PATH_SEP ':' +#endif + +#ifndef PATH_SEP +# define PATH_SEP '/' +#endif + +#endif /*__Common__*/ diff --git a/nufxlib-0/samples/Exerciser.c b/nufxlib-0/samples/Exerciser.c new file mode 100644 index 0000000..4bb3bbd --- /dev/null +++ b/nufxlib-0/samples/Exerciser.c @@ -0,0 +1,1360 @@ +/* + * NuFX archive manipulation library + * 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 Library General Public License, see the file COPYING.LIB. + * + * NufxLib exerciser. Most library functions can be invoked directly from + * the exerciser command line. + * + * This was written in C++ to evaluate the interaction between NufxLib and + * the C++ language, i.e. to make sure that all type definitions and + * function calls can be used without giving the compiler fits. This + * file will compile as either "Exerciser.c" or "Exerciser.cpp". + */ +#include "NufxLib.h" +#include "Common.h" +#include + +/* not portable to other OSs, but not all that important anyway */ +static const char kFssep = PATH_SEP; + +/* ProDOS access permissions */ +#define kUnlocked 0xe3 + +#define kTempFile "exer-temp" + + +/* + * =========================================================================== + * ExerciserState object + * =========================================================================== + */ + +/* + * Exerciser state. + * + * In case it isn't immediately apparent, this was written in C++ and + * then converted back to C. + */ +typedef struct ExerciserState { + NuArchive* pArchive; + char* archivePath; + const char* archiveFile; +} ExerciserState; + + +ExerciserState* +ExerciserState_New(void) +{ + ExerciserState* pExerState; + + pExerState = (ExerciserState*) malloc(sizeof(*pExerState)); + if (pExerState == nil) + return nil; + + pExerState->pArchive = nil; + pExerState->archivePath = nil; + pExerState->archiveFile = nil; + + return pExerState; +} + +void +ExerciserState_Free(ExerciserState* pExerState) +{ + if (pExerState == nil) + return; + + if (pExerState->pArchive != nil) { + printf("Exerciser: aborting open archive\n"); + (void) NuAbort(pExerState->pArchive); + (void) NuClose(pExerState->pArchive); + } + if (pExerState->archivePath != nil) + free(pExerState->archivePath); + + free(pExerState); +} + +inline NuArchive* +ExerciserState_GetNuArchive(const ExerciserState* pExerState) +{ + return pExerState->pArchive; +} + +inline void +ExerciserState_SetNuArchive(ExerciserState* pExerState, NuArchive* newArchive) +{ + pExerState->pArchive = newArchive; +} + +inline char* +ExerciserState_GetArchivePath(const ExerciserState* pExerState) +{ + return pExerState->archivePath; +} + +inline void +ExerciserState_SetArchivePath(ExerciserState* pExerState, char* newPath) +{ + if (pExerState->archivePath != nil) + free(pExerState->archivePath); + + if (newPath == nil) { + pExerState->archivePath = nil; + pExerState->archiveFile = nil; + } else { + pExerState->archivePath = strdup(newPath); + pExerState->archiveFile = strrchr(newPath, kFssep); + if (pExerState->archiveFile != nil) + pExerState->archiveFile++; + + if (pExerState->archiveFile == nil || *pExerState->archiveFile == '\0') + pExerState->archiveFile = pExerState->archivePath; + } +} + +inline const char* +ExerciserState_GetArchiveFile(const ExerciserState* pExerState) +{ + if (pExerState->archiveFile == nil) + return "[no archive open]"; + else + return pExerState->archiveFile; +} + + +/* + * =========================================================================== + * Utility functions + * =========================================================================== + */ + +/* + * NuContents callback function. Print the contents of an individual record. + */ +NuResult +PrintEntry(NuArchive* pArchive, void* vpRecord) +{ + const NuRecord* pRecord = (const NuRecord*) vpRecord; + int idx; + + (void)pArchive; /* shut up, gcc */ + + printf("RecordIdx %ld: '%s'\n", + pRecord->recordIdx, pRecord->filename); + + for (idx = 0; idx < (int) pRecord->recTotalThreads; idx++) { + const NuThread* pThread; + NuThreadID threadID; + const char* threadLabel; + + pThread = NuGetThread(pRecord, idx); + assert(pThread != nil); + + threadID = NuGetThreadID(pThread); + switch (NuThreadIDGetClass(threadID)) { + case kNuThreadClassMessage: + threadLabel = "message class"; + break; + case kNuThreadClassControl: + threadLabel = "control class"; + break; + case kNuThreadClassData: + threadLabel = "data class"; + break; + case kNuThreadClassFilename: + threadLabel = "filename class"; + break; + default: + threadLabel = "(unknown class)"; + break; + } + + switch (threadID) { + case kNuThreadIDComment: + threadLabel = "comment"; + break; + case kNuThreadIDIcon: + threadLabel = "icon"; + break; + case kNuThreadIDMkdir: + threadLabel = "mkdir"; + break; + case kNuThreadIDDataFork: + threadLabel = "data fork"; + break; + case kNuThreadIDDiskImage: + threadLabel = "disk image"; + break; + case kNuThreadIDRsrcFork: + threadLabel = "rsrc fork"; + break; + case kNuThreadIDFilename: + threadLabel = "filename"; + break; + default: + break; + } + + printf(" ThreadIdx %ld - 0x%08lx (%s)\n", pThread->threadIdx, + threadID, threadLabel); + } + + return kNuOK; +} + + +#define kNiceLineLen 256 + +/* + * Get a line of input, stripping the '\n' off the end. + */ +static NuError +GetLine(const char* prompt, char* buffer, int bufferSize) +{ + printf("%s> ", prompt); + fflush(stdout); + + if (fgets(buffer, bufferSize, stdin) == nil) + return kNuErrGeneric; + + if (buffer[strlen(buffer)-1] == '\n') + buffer[strlen(buffer)-1] = '\0'; + + return kNuErrNone; +} + + +/* + * Selection filter for mass "extract" and "delete" operations. + */ +NuResult +SelectionFilter(NuArchive* pArchive, void* vselFilt) +{ + const NuSelectionProposal* selProposal = (NuSelectionProposal*) vselFilt; + char buffer[8]; + + printf("%s (N/y)? ", selProposal->pRecord->filename); + fflush(stdout); + + if (fgets(buffer, sizeof(buffer), stdin) == nil) + return kNuAbort; + + if (tolower(buffer[0]) == 'y') + return kNuOK; + else + return kNuSkip; +} + + +/* + * General-purpose error handler. + */ +NuResult +ErrorHandler(NuArchive* pArchive, void* vErrorStatus) +{ + const NuErrorStatus* pErrorStatus = (const NuErrorStatus*) vErrorStatus; + char buffer[8]; + NuResult result = kNuSkip; + + printf("Exerciser: error handler op=%d err=%d sysErr=%d message='%s'\n" + "\tfilename='%s' '%c'(0x%02x)\n", + pErrorStatus->operation, pErrorStatus->err, pErrorStatus->sysErr, + pErrorStatus->message == nil ? "(nil)" : pErrorStatus->message, + pErrorStatus->pathname, pErrorStatus->filenameSeparator, + pErrorStatus->filenameSeparator); + printf("\tValid options are:"); + if (pErrorStatus->canAbort) + printf(" a)bort"); + if (pErrorStatus->canRetry) + printf(" r)etry"); + if (pErrorStatus->canIgnore) + printf(" i)gnore"); + if (pErrorStatus->canSkip) + printf(" s)kip"); + if (pErrorStatus->canRename) + printf(" re)name"); + if (pErrorStatus->canOverwrite) + printf(" o)verwrite"); + putc('\n', stdout); + + printf("Return what (a/r/i/s/e/o)? "); + fflush(stdout); + + if (fgets(buffer, sizeof(buffer), stdin) == nil) { + printf("Returning kNuSkip\n"); + } else switch (buffer[0]) { + case 'a': result = kNuAbort; break; + case 'r': result = kNuRetry; break; + case 'i': result = kNuIgnore; break; + case 's': result = kNuSkip; break; + case 'e': result = kNuRename; break; + case 'o': result = kNuOverwrite; break; + default: + printf("Unknown value '%c', returning kNuSkip\n", buffer[0]); + break; + } + + return result; +} + + +/* + * =========================================================================== + * Command handlers + * =========================================================================== + */ + +typedef NuError (*CommandFunc)(ExerciserState* pState, int argc, + char** argv); + +static NuError HelpFunc(ExerciserState* pState, int argc, char** argv); + +#if 0 +static NuError +GenericFunc(ExerciserState* pState, int argc, char** argv) +{ + (void) pState, (void) argc, (void) argv; /* shut up, gcc */ + printf("Generic! argc=%d\n", argc); + return kNuErrNone; +} +#endif + +/* + * Do nothing. Useful when the user just hits on a blank line. + */ +static NuError +NothingFunc(ExerciserState* pState, int argc, char** argv) +{ + (void) pState, (void) argc, (void) argv; /* shut up, gcc */ + return kNuErrNone; +} + +/* + * q - quit + * + * Do nothing. This is used as a trigger for quitting the program. In + * practice, we catch this earlier, and won't actually call here. + */ +static NuError +QuitFunc(ExerciserState* pState, int argc, char** argv) +{ + (void) pState, (void) argc, (void) argv; /* shut up, gcc */ + assert(0); + return kNuErrNone; +} + + + +/* + * ab - abort current changes + */ +static NuError +AbortFunc(ExerciserState* pState, int argc, char** argv) +{ + (void) pState, (void) argc, (void) argv; /* shut up, gcc */ + assert(ExerciserState_GetNuArchive(pState) != nil); + assert(argc == 1); + + return NuAbort(ExerciserState_GetNuArchive(pState)); +} + +/* + * af - add file to archive + */ +static NuError +AddFileFunc(ExerciserState* pState, int argc, char** argv) +{ + NuFileDetails nuFileDetails; + + (void) pState, (void) argc, (void) argv; /* shut up, gcc */ + assert(ExerciserState_GetNuArchive(pState) != nil); + assert(argc == 2); + + memset(&nuFileDetails, 0, sizeof(nuFileDetails)); + nuFileDetails.threadID = kNuThreadIDDataFork; + nuFileDetails.storageName = argv[1]; + nuFileDetails.fileSysID = kNuFileSysUnknown; + nuFileDetails.fileSysInfo = (short) kFssep; + nuFileDetails.access = kUnlocked; + /* fileType, extraType, storageType, dates */ + + return NuAddFile(ExerciserState_GetNuArchive(pState), argv[1], + &nuFileDetails, false, nil); +} + +/* + * ar - add an empty record + */ +static NuError +AddRecordFunc(ExerciserState* pState, int argc, char** argv) +{ + NuError err; + NuRecordIdx recordIdx; + NuFileDetails nuFileDetails; + + (void) pState, (void) argc, (void) argv; /* shut up, gcc */ + assert(ExerciserState_GetNuArchive(pState) != nil); + assert(argc == 2); + + memset(&nuFileDetails, 0, sizeof(nuFileDetails)); + nuFileDetails.threadID = 0; /* irrelevant */ + nuFileDetails.storageName = argv[1]; + nuFileDetails.fileSysID = kNuFileSysUnknown; + nuFileDetails.fileSysInfo = (short) kFssep; + nuFileDetails.access = kUnlocked; + /* fileType, extraType, storageType, dates */ + + err = NuAddRecord(ExerciserState_GetNuArchive(pState), + &nuFileDetails, &recordIdx); + if (err == kNuErrNone) + printf("Exerciser: success, new recordIdx=%ld\n", recordIdx); + return err; +} + +/* + * at - add thread to record + */ +static NuError +AddThreadFunc(ExerciserState* pState, int argc, char** argv) +{ + NuError err; + NuDataSource* pDataSource = nil; + char* lineBuf = nil; + long ourLen, maxLen; + NuThreadID threadID; + NuThreadIdx threadIdx; + + (void) pState, (void) argc, (void) argv; /* shut up, gcc */ + assert(ExerciserState_GetNuArchive(pState) != nil); + assert(argc == 3); + + lineBuf = (char*)malloc(kNiceLineLen); + assert(lineBuf != nil); + + threadID = strtol(argv[2], nil, 0); + if (NuThreadIDGetClass(threadID) == kNuThreadClassData) { + /* load data from a file on disk */ + maxLen = 0; + err = GetLine("Enter filename", lineBuf, kNiceLineLen); + if (err != kNuErrNone) + goto bail; + if (!lineBuf[0]) { + fprintf(stderr, "Invalid filename\n"); + err = kNuErrInvalidArg; + goto bail; + } + + err = NuCreateDataSourceForFile(kNuThreadFormatUncompressed, + true, 0, lineBuf, false, &pDataSource); + if (err != kNuErrNone) { + fprintf(stderr, + "Exerciser: file data source create failed (err=%d)\n", err); + goto bail; + } + } else { + if (threadID == kNuThreadIDFilename || threadID == kNuThreadIDComment) { + /* select the buffer pre-size */ + err = GetLine("Enter max buffer size", lineBuf, kNiceLineLen); + if (err != kNuErrNone) + goto bail; + maxLen = strtol(lineBuf, nil, 0); + if (maxLen <= 0) { + fprintf(stderr, "Bad length\n"); + err = kNuErrInvalidArg; + goto bail; + } + } else { + maxLen = 0; + } + + err = GetLine("Enter the thread contents", lineBuf, kNiceLineLen); + if (err != kNuErrNone) + goto bail; + ourLen = strlen(lineBuf); + + /* create a data source from the buffer */ + err = NuCreateDataSourceForBuffer(kNuThreadFormatUncompressed, + true, maxLen, (unsigned char*)lineBuf, 0, ourLen, &pDataSource); + if (err != kNuErrNone) { + fprintf(stderr, + "Exerciser: buffer data source create failed (err=%d)\n", err); + goto bail; + } + lineBuf = nil; /* now owned by the library */ + } + + + err = NuAddThread(ExerciserState_GetNuArchive(pState), + strtol(argv[1], nil, 0), threadID, pDataSource, &threadIdx); + if (err == kNuErrNone) { + pDataSource = nil; /* library owns it now */ + printf("Exerciser: success; function returned threadIdx=%ld\n", + threadIdx); + } + +bail: + NuFreeDataSource(pDataSource); + if (lineBuf != nil) + free(lineBuf); + return err; +} + +/* + * cl - close archive + */ +static NuError +CloseFunc(ExerciserState* pState, int argc, char** argv) +{ + NuError err; + + (void) pState, (void) argc, (void) argv; /* shut up, gcc */ + assert(ExerciserState_GetNuArchive(pState) != nil); + assert(argc == 1); + + err = NuClose(ExerciserState_GetNuArchive(pState)); + if (err == kNuErrNone) { + ExerciserState_SetNuArchive(pState, nil); + ExerciserState_SetArchivePath(pState, nil); + } + + return err; +} + +/* + * d - delete all records (selection-filtered) + */ +static NuError +DeleteFunc(ExerciserState* pState, int argc, char** argv) +{ + NuError err; + + (void) pState, (void) argc, (void) argv; /* shut up, gcc */ + assert(ExerciserState_GetNuArchive(pState) != nil); + assert(argc == 1); + + err = NuSetSelectionFilter(ExerciserState_GetNuArchive(pState), + SelectionFilter); + if (err != kNuErrNone) + return err; + + return NuDelete(ExerciserState_GetNuArchive(pState)); +} + +/* + * dr - delete record + */ +static NuError +DeleteRecordFunc(ExerciserState* pState, int argc, char** argv) +{ + (void) pState, (void) argc, (void) argv; /* shut up, gcc */ + assert(ExerciserState_GetNuArchive(pState) != nil); + assert(argc == 2); + + return NuDeleteRecord(ExerciserState_GetNuArchive(pState), + strtol(argv[1], nil, 0)); +} + +/* + * dt - delete thread + */ +static NuError +DeleteThreadFunc(ExerciserState* pState, int argc, char** argv) +{ + (void) pState, (void) argc, (void) argv; /* shut up, gcc */ + assert(ExerciserState_GetNuArchive(pState) != nil); + assert(argc == 2); + + return NuDeleteThread(ExerciserState_GetNuArchive(pState), + strtol(argv[1], nil, 0)); +} + +/* + * e - extract all files (selection-filtered) + */ +static NuError +ExtractFunc(ExerciserState* pState, int argc, char** argv) +{ + NuError err; + + (void) pState, (void) argc, (void) argv; /* shut up, gcc */ + assert(ExerciserState_GetNuArchive(pState) != nil); + assert(argc == 1); + + err = NuSetSelectionFilter(ExerciserState_GetNuArchive(pState), + SelectionFilter); + if (err != kNuErrNone) + return err; + + return NuExtract(ExerciserState_GetNuArchive(pState)); +} + +/* + * er - extract record + */ +static NuError +ExtractRecordFunc(ExerciserState* pState, int argc, char** argv) +{ + (void) pState, (void) argc, (void) argv; /* shut up, gcc */ + assert(ExerciserState_GetNuArchive(pState) != nil); + assert(argc == 2); + + return NuExtractRecord(ExerciserState_GetNuArchive(pState), + strtol(argv[1], nil, 0)); +} + +/* + * et - extract thread + */ +static NuError +ExtractThreadFunc(ExerciserState* pState, int argc, char** argv) +{ + NuError err; + NuDataSink* pDataSink = nil; + + (void) pState, (void) argc, (void) argv; /* shut up, gcc */ + assert(ExerciserState_GetNuArchive(pState) != nil); + assert(argc == 3); + + err = NuCreateDataSinkForFile(true, kNuConvertOff, argv[2], kFssep, + &pDataSink); + if (err != kNuErrNone) { + fprintf(stderr, "Exerciser: data sink create failed\n"); + goto bail; + } + + err = NuExtractThread(ExerciserState_GetNuArchive(pState), + strtol(argv[1], nil, 0), pDataSink); + /* fall through with err */ + +bail: + NuFreeDataSink(pDataSink); + return err; +} + +/* + * fl - flush changes to archive + */ +static NuError +FlushFunc(ExerciserState* pState, int argc, char** argv) +{ + NuError err; + long flushStatus; + + (void) pState, (void) argc, (void) argv; /* shut up, gcc */ + assert(ExerciserState_GetNuArchive(pState) != nil); + assert(argc == 1); + + err = NuFlush(ExerciserState_GetNuArchive(pState), &flushStatus); + if (err != kNuErrNone) + printf("Exerciser: flush failed, status flags=0x%04lx\n", flushStatus); + return err; +} + +/* + * gev - get value + * + * Currently takes numeric arguments. We could be nice and accept the + * things like "IgnoreCRC" for kNuValueIgnoreCRC, but not yet. + */ +static NuError +GetValueFunc(ExerciserState* pState, int argc, char** argv) +{ + NuError err; + NuValue value; + + (void) pState, (void) argc, (void) argv; /* shut up, gcc */ + assert(ExerciserState_GetNuArchive(pState) != nil); + assert(argc == 2); + + err = NuGetValue(ExerciserState_GetNuArchive(pState), + (NuValueID) strtol(argv[1], nil, 0), &value); + if (err == kNuErrNone) + printf(" --> %ld\n", value); + return err; +} + +/* + * gmh - get master header + */ +static NuError +GetMasterHeaderFunc(ExerciserState* pState, int argc, char** argv) +{ + NuError err; + const NuMasterHeader* pMasterHeader; + + (void) pState, (void) argc, (void) argv; /* shut up, gcc */ + assert(ExerciserState_GetNuArchive(pState) != nil); + assert(argc == 1); + + err = NuGetMasterHeader(ExerciserState_GetNuArchive(pState), + &pMasterHeader); + if (err == kNuErrNone) { + printf("Exerciser: success (version=%u, totalRecords=%lu, EOF=%lu)\n", + pMasterHeader->mhMasterVersion, pMasterHeader->mhTotalRecords, + pMasterHeader->mhMasterEOF); + } + return err; +} + +/* + * gr - get record attributes + */ +static NuError +GetRecordFunc(ExerciserState* pState, int argc, char** argv) +{ + NuError err; + const NuRecord* pRecord; + + (void) pState, (void) argc, (void) argv; /* shut up, gcc */ + assert(ExerciserState_GetNuArchive(pState) != nil); + assert(argc == 2); + + err = NuGetRecord(ExerciserState_GetNuArchive(pState), + strtol(argv[1], nil, 0), &pRecord); + if (err == kNuErrNone) { + printf("Exerciser: success, call returned:\n"); + printf("\tfileSysID : %d\n", pRecord->recFileSysID); + printf("\tfileSysInfo : 0x%04x ('%c')\n", pRecord->recFileSysInfo, + NuGetSepFromSysInfo(pRecord->recFileSysInfo)); + printf("\taccess : 0x%02lx\n", pRecord->recAccess); + printf("\tfileType : 0x%04lx\n", pRecord->recFileType); + printf("\textraType : 0x%04lx\n", pRecord->recExtraType); + printf("\tcreateWhen : ...\n"); + printf("\tmodWhen : ...\n"); /* too lazy */ + printf("\tarchiveWhen : ...\n"); + } + return err; +} + +/* + * grin - get record idx by name + */ +static NuError +GetRecordIdxByNameFunc(ExerciserState* pState, int argc, char** argv) +{ + NuError err; + NuRecordIdx recIdx; + + (void) pState, (void) argc, (void) argv; /* shut up, gcc */ + assert(ExerciserState_GetNuArchive(pState) != nil); + assert(argc == 2); + + err = NuGetRecordIdxByName(ExerciserState_GetNuArchive(pState), + argv[1], &recIdx); + if (err == kNuErrNone) + printf("Exerciser: success, returned recordIdx=%ld\n", recIdx); + return err; +} + +/* + * grip - get record idx by position + */ +static NuError +GetRecordIdxByPositionFunc(ExerciserState* pState, int argc, char** argv) +{ + NuError err; + NuRecordIdx recIdx; + + (void) pState, (void) argc, (void) argv; /* shut up, gcc */ + assert(ExerciserState_GetNuArchive(pState) != nil); + assert(argc == 2); + + err = NuGetRecordIdxByPosition(ExerciserState_GetNuArchive(pState), + strtol(argv[1], nil, 0), &recIdx); + if (err == kNuErrNone) + printf("Exerciser: success, returned recordIdx=%ld\n", recIdx); + return err; +} + +/* + * ocrw - open/create read-write + */ +static NuError +OpenCreateReadWriteFunc(ExerciserState* pState, int argc, char** argv) +{ + NuError err; + NuArchive* pArchive; + + assert(ExerciserState_GetNuArchive(pState) == nil); + assert(argc == 2); + + err = NuOpenRW(argv[1], kTempFile, kNuOpenCreat|kNuOpenExcl, &pArchive); + if (err == kNuErrNone) { + ExerciserState_SetNuArchive(pState, pArchive); + ExerciserState_SetArchivePath(pState, argv[1]); + } + + return err; +} + +/* + * oro - open read-only + */ +static NuError +OpenReadOnlyFunc(ExerciserState* pState, int argc, char** argv) +{ + NuError err; + NuArchive* pArchive; + + assert(ExerciserState_GetNuArchive(pState) == nil); + assert(argc == 2); + + err = NuOpenRO(argv[1], &pArchive); + if (err == kNuErrNone) { + ExerciserState_SetNuArchive(pState, pArchive); + ExerciserState_SetArchivePath(pState, argv[1]); + } + + return err; +} + +/* + * ors - open streaming read-only + */ +static NuError +OpenStreamingReadOnlyFunc(ExerciserState* pState, int argc, char** argv) +{ + NuError err; + NuArchive* pArchive; + FILE* fp = nil; + + assert(ExerciserState_GetNuArchive(pState) == nil); + assert(argc == 2); + + if ((fp = fopen(argv[1], kNuFileOpenReadOnly)) == nil) { + err = errno ? (NuError)errno : kNuErrGeneric; + fprintf(stderr, "Exerciser: unable to open '%s'\n", argv[1]); + } else { + err = NuStreamOpenRO(fp, &pArchive); + if (err == kNuErrNone) { + ExerciserState_SetNuArchive(pState, pArchive); + ExerciserState_SetArchivePath(pState, argv[1]); + fp = nil; + } + } + + if (fp != nil) + fclose(fp); + + return err; +} + +/* + * orw - open read-write + */ +static NuError +OpenReadWriteFunc(ExerciserState* pState, int argc, char** argv) +{ + NuError err; + NuArchive* pArchive; + + assert(ExerciserState_GetNuArchive(pState) == nil); + assert(argc == 2); + + err = NuOpenRW(argv[1], kTempFile, 0, &pArchive); + if (err == kNuErrNone) { + ExerciserState_SetNuArchive(pState, pArchive); + ExerciserState_SetArchivePath(pState, argv[1]); + } + + return err; +} + +/* + * p - print + */ +static NuError +PrintFunc(ExerciserState* pState, int argc, char** argv) +{ + (void) pState, (void) argc, (void) argv; /* shut up, gcc */ + assert(ExerciserState_GetNuArchive(pState) != nil); + assert(argc == 1); + + return NuContents(ExerciserState_GetNuArchive(pState), PrintEntry); +} + +/* + * pd - print debug + */ +static NuError +PrintDebugFunc(ExerciserState* pState, int argc, char** argv) +{ + (void) pState, (void) argc, (void) argv; /* shut up, gcc */ + assert(ExerciserState_GetNuArchive(pState) != nil); + assert(argc == 1); + + return NuDebugDumpArchive(ExerciserState_GetNuArchive(pState)); +} + +/* + * re - rename record + */ +static NuError +RenameFunc(ExerciserState* pState, int argc, char** argv) +{ + (void) pState, (void) argc, (void) argv; /* shut up, gcc */ + assert(ExerciserState_GetNuArchive(pState) != nil); + assert(argc == 4); + + return NuRename(ExerciserState_GetNuArchive(pState), + strtol(argv[1], nil, 0), argv[2], argv[3][0]); +} + +/* + * sec - set error callback + * + * Use an error handler callback. + */ +static NuError +SetErrorCallbackFunc(ExerciserState* pState, int argc, char** argv) +{ + (void) pState, (void) argc, (void) argv; /* shut up, gcc */ + assert(ExerciserState_GetNuArchive(pState) != nil); + assert(argc == 1); + + return NuSetErrorHandler(ExerciserState_GetNuArchive(pState), ErrorHandler); +} + +/* + * sev - set value + * + * Currently takes numeric arguments. + */ +static NuError +SetValueFunc(ExerciserState* pState, int argc, char** argv) +{ + (void) pState, (void) argc, (void) argv; /* shut up, gcc */ + assert(ExerciserState_GetNuArchive(pState) != nil); + assert(argc == 3); + + return NuSetValue(ExerciserState_GetNuArchive(pState), + (NuValueID) strtol(argv[1], nil, 0), strtol(argv[2], nil, 0)); +} + +/* + * sra - set record attributes + * + * Right now I'm only allowing changes to file type and aux type. This + * could be adapted to do more easily, but the command handler has a + * rigid notion of how many arguments each function should have, so + * you'd need to list all of them every time. + */ +static NuError +SetRecordAttrFunc(ExerciserState* pState, int argc, char** argv) +{ + NuError err; + const NuRecord* pRecord; + NuRecordAttr recordAttr; + + (void) pState, (void) argc, (void) argv; /* shut up, gcc */ + assert(ExerciserState_GetNuArchive(pState) != nil); + assert(argc == 4); + + err = NuGetRecord(ExerciserState_GetNuArchive(pState), + strtol(argv[1], nil, 0), &pRecord); + if (err != kNuErrNone) + return err; + printf("Exerciser: NuGetRecord succeeded, calling NuSetRecordAttr\n"); + NuRecordCopyAttr(&recordAttr, pRecord); + recordAttr.fileType = strtol(argv[2], nil, 0); + recordAttr.extraType = strtol(argv[3], nil, 0); + /*recordAttr.fileSysInfo = ':';*/ + return NuSetRecordAttr(ExerciserState_GetNuArchive(pState), + strtol(argv[1], nil, 0), &recordAttr); +} + +/* + * t - test archive + */ +static NuError +TestFunc(ExerciserState* pState, int argc, char** argv) +{ + (void) pState, (void) argc, (void) argv; /* shut up, gcc */ + assert(ExerciserState_GetNuArchive(pState) != nil); + assert(argc == 1); + + return NuTest(ExerciserState_GetNuArchive(pState)); +} + +/* + * upt - update pre-sized thread + */ +static NuError +UpdatePresizedThreadFunc(ExerciserState* pState, int argc, char** argv) +{ + NuError err; + NuDataSource* pDataSource = nil; + char* lineBuf = nil; + long ourLen, maxLen; + + (void) pState, (void) argc, (void) argv; /* shut up, gcc */ + assert(ExerciserState_GetNuArchive(pState) != nil); + assert(argc == 2); + + lineBuf = (char*)malloc(kNiceLineLen); + assert(lineBuf != nil); + err = GetLine("Enter data for thread", lineBuf, kNiceLineLen); + if (err != kNuErrNone) + goto bail; + + ourLen = strlen(lineBuf); + + /* use "ourLen" for both buffer len and data len */ + err = NuCreateDataSourceForBuffer(kNuThreadFormatUncompressed, + true, ourLen, (unsigned char*)lineBuf, 0, ourLen, &pDataSource); + if (err != kNuErrNone) { + fprintf(stderr, "Exerciser: data source create failed (err=%d)\n", + err); + goto bail; + } + lineBuf = nil; /* now owned by the library */ + + err = NuUpdatePresizedThread(ExerciserState_GetNuArchive(pState), + strtol(argv[1], nil, 0), pDataSource, &maxLen); + if (err == kNuErrNone) + printf("Exerciser: success; function returned maxLen=%ld\n", maxLen); + +bail: + NuFreeDataSource(pDataSource); + if (lineBuf != nil) + free(lineBuf); + return err; +} + + +/* + * Command table. This drives the user interface. + */ + +/* flags for the CommandTable */ +#define kFlagArchiveReq (1L) /* must have archive open */ +#define kFlagNoArchiveReq (1L<<1) /* must NOT have archive open */ + +/* command set */ +static const struct { + const char* commandStr; + CommandFunc func; + int expectedArgCount; + const char* argumentList; + unsigned long flags; + const char* description; +} gCommandTable[] = { + { "--- exerciser commands ---", HelpFunc, 0, "", 0, + "" }, + { "?", HelpFunc, 0, "", 0, + "Show help" }, + { "h", HelpFunc, 0, "", 0, + "Show help" }, + { "q", QuitFunc, 0, "", 0, + "Quit program (will abort un-flushed changes)" }, + + { "--- archive commands ---", HelpFunc, 0, "", 0, + "" }, + + { "ab", AbortFunc, 0, "", kFlagArchiveReq, + "Abort current changes" }, + { "af", AddFileFunc, 1, "filename", kFlagArchiveReq, + "Add file" }, + { "ar", AddRecordFunc, 1, "storageName", kFlagArchiveReq, + "Add record" }, + { "at", AddThreadFunc, 2, "recordIdx threadID", kFlagArchiveReq, + "Add thread to record" }, + { "cl", CloseFunc, 0, "", kFlagArchiveReq, + "Close archive after flushing any changes" }, + { "d", DeleteFunc, 0, "", kFlagArchiveReq, + "Delete all records" }, + { "dr", DeleteRecordFunc, 1, "recordIdx", kFlagArchiveReq, + "Delete record" }, + { "dt", DeleteThreadFunc, 1, "threadIdx", kFlagArchiveReq, + "Delete thread" }, + { "e", ExtractFunc, 0, "", kFlagArchiveReq, + "Extract all files" }, + { "er", ExtractRecordFunc, 1, "recordIdx", kFlagArchiveReq, + "Extract record" }, + { "et", ExtractThreadFunc, 2, "threadIdx filename", kFlagArchiveReq, + "Extract thread" }, + { "fl", FlushFunc, 0, "", kFlagArchiveReq, + "Flush changes" }, + { "gev", GetValueFunc, 1, "ident", kFlagArchiveReq, + "Get value" }, + { "gmh", GetMasterHeaderFunc, 0, "", kFlagArchiveReq, + "Get master header" }, + { "gr", GetRecordFunc, 1, "recordIdx", kFlagArchiveReq, + "Get record" }, + { "grin", GetRecordIdxByNameFunc, 1, "name", kFlagArchiveReq, + "Get recordIdx by name" }, + { "grip", GetRecordIdxByPositionFunc, 1, "position", kFlagArchiveReq, + "Get recordIdx by position" }, + { "ocrw", OpenCreateReadWriteFunc, 1, "filename", kFlagNoArchiveReq, + "Open/create archive read-write" }, + { "oro", OpenReadOnlyFunc, 1, "filename", kFlagNoArchiveReq, + "Open archive read-only" }, + { "ors", OpenStreamingReadOnlyFunc, 1, "filename", kFlagNoArchiveReq, + "Open archive streaming read-only" }, + { "orw", OpenReadWriteFunc, 1, "filename", kFlagNoArchiveReq, + "Open archive read-write" }, + { "p", PrintFunc, 0, "", kFlagArchiveReq, + "Print archive contents" }, + { "pd", PrintDebugFunc, 0, "", kFlagArchiveReq, + "Print debugging output (if available)" }, + { "re", RenameFunc, 3, "recordIdx name sep", kFlagArchiveReq, + "Rename record" }, + { "sec", SetErrorCallbackFunc, 0, "", kFlagArchiveReq, + "Set error callback" }, + { "sev", SetValueFunc, 2, "ident value", kFlagArchiveReq, + "Set value" }, + { "sra", SetRecordAttrFunc, 3, "recordIdx type aux", kFlagArchiveReq, + "Set record attributes" }, + { "t", TestFunc, 0, "", kFlagArchiveReq, + "Test archive" }, + { "upt", UpdatePresizedThreadFunc, 1, "threadIdx", kFlagArchiveReq, + "Update pre-sized thread" }, +}; + +#define kMaxArgs 4 + +/* + * Display a summary of available commands. + */ +static NuError +HelpFunc(ExerciserState* pState, int argc, char** argv) +{ + int i; + + (void) pState, (void) argc, (void) argv; /* shut up, gcc */ + + printf("\nAvailable commands:\n"); + for (i = 0; i < (int)NELEM(gCommandTable); i++) { + printf(" %-4s %-21s %s\n", + gCommandTable[i].commandStr, + gCommandTable[i].argumentList, + gCommandTable[i].description); + } + + return kNuErrNone; +} + + +/* + * =========================================================================== + * Control + * =========================================================================== + */ + +static const char* kWhitespace = " \t\n"; + +/* + * Parse a command from the user. + * + * "lineBuf" will be mangled. On success, "pFunc", "pArgc", and "pArgv" + * will receive the results. + */ +static NuError +ParseLine(char* lineBuf, ExerciserState* pState, CommandFunc* pFunc, int* pArgc, + char*** pArgv) +{ + NuError err = kNuErrSyntax; + char* command; + char* cp; + int i; + + /* + * Parse the strings. + */ + + command = strtok(lineBuf, kWhitespace); + if (command == nil) { + /* no command; the user probably just hit "enter" on a blank line */ + *pFunc = NothingFunc; + *pArgc = 0; + *pArgv = nil; + err = kNuErrNone; + goto bail; + } + + /* no real need to be flexible; add 1 for command and one for nil */ + *pArgv = (char**) malloc(sizeof(char*) * (kMaxArgs+2)); + (*pArgv)[0] = command; + *pArgc = 1; + + cp = strtok(nil, kWhitespace); + while (cp != nil) { + if (*pArgc >= kMaxArgs+1) { + printf("ERROR: too many arguments\n"); + goto bail; + } + (*pArgv)[*pArgc] = cp; + (*pArgc)++; + + cp = strtok(nil, kWhitespace); + } + assert(*pArgc < kMaxArgs+2); + (*pArgv)[*pArgc] = nil; + + /* + * Look up the command. + */ + for (i = 0; i < (int)NELEM(gCommandTable); i++) { + if (strcmp(command, gCommandTable[i].commandStr) == 0) + break; + } + if (i == NELEM(gCommandTable)) { + printf("ERROR: unrecognized command\n"); + goto bail; + } + + *pFunc = gCommandTable[i].func; + + /* + * Check arguments and flags. + */ + if (*pArgc -1 != gCommandTable[i].expectedArgCount) { + printf("ERROR: expected %d args, found %d\n", + gCommandTable[i].expectedArgCount, *pArgc -1); + goto bail; + } + + if (gCommandTable[i].flags & kFlagArchiveReq) { + if (ExerciserState_GetNuArchive(pState) == nil) { + printf("ERROR: must have an archive open\n"); + goto bail; + } + } + if (gCommandTable[i].flags & kFlagNoArchiveReq) { + if (ExerciserState_GetNuArchive(pState) != nil) { + printf("ERROR: an archive is already open\n"); + goto bail; + } + } + + /* + * Looks good! + */ + err = kNuErrNone; + +bail: + return err; +} + + + +/* + * Interpret commands, do clever things. + */ +static NuError +CommandLoop(void) +{ + NuError err = kNuErrNone; + ExerciserState* pState = ExerciserState_New(); + CommandFunc func; + char lineBuf[128]; + int argc; + char** argv = nil; + + while (1) { + printf("\nEnter command (%s)> ", ExerciserState_GetArchiveFile(pState)); + fflush(stdout); + + if (fgets(lineBuf, sizeof(lineBuf), stdin) == nil) { + printf("\n"); + break; + } + + if (argv != nil) { + free(argv); + argv = nil; + } + + func = nil; /* sanity check */ + + err = ParseLine(lineBuf, pState, &func, &argc, &argv); + if (err != kNuErrNone) + continue; + + assert(func != nil); + if (func == QuitFunc) + break; + + err = (*func)(pState, argc, argv); + + if (err < 0) + printf("Exerciser: received error %d (%s)\n", err, NuStrError(err)); + else if (err > 0) + printf("Exerciser: received error %d\n", err); + + if (argv != nil) { + free(argv); + argv = nil; + } + } + + if (ExerciserState_GetNuArchive(pState) != nil) { + /* ought to query the archive before saying something like this... */ + printf("Exerciser: aborting any un-flushed changes in archive %s\n", + ExerciserState_GetArchivePath(pState)); + (void) NuAbort(ExerciserState_GetNuArchive(pState)); + err = NuClose(ExerciserState_GetNuArchive(pState)); + if (err != kNuErrNone) + printf("Exerciser: got error %d closing archive\n", err); + ExerciserState_SetNuArchive(pState, nil); + } + + if (pState != nil) + free(pState); + if (argv != nil) + free(argv); + return kNuErrNone; +} + + +/* + * Main entry point. + * + * We don't currently take any arguments, so this is pretty straightforward. + */ +int +main(void) +{ + NuError result; + long majorVersion, minorVersion, bugVersion; + const char* nufxLibDate; + const char* nufxLibFlags; + + (void) NuGetVersion(&majorVersion, &minorVersion, &bugVersion, + &nufxLibDate, &nufxLibFlags); + printf("NufxLib exerciser, linked with NufxLib v%ld.%ld.%ld [%s]\n\n", + majorVersion, minorVersion, bugVersion, nufxLibFlags); + + /* stuff useful when debugging lots */ + if (unlink(kTempFile) == 0) + fprintf(stderr, "NOTE: whacked exer-temp\n"); + if (unlink("new.shk") == 0) + fprintf(stderr, "NOTE: whacked new.shk\n"); + +#if defined(HAS_MALLOC_CHECK_) && !defined(USE_DMALLOC) + /* + * This is really nice to have on Linux and any other system that + * uses the GNU libc/malloc stuff. It slows things down, but it + * tells you when you do something dumb with malloc/realloc/free. + * (Solaris 2.7 has a similar feature that is enabled by setting the + * environment variable LD_PRELOAD to include watchmalloc.so. Other + * OSs and 3rd-party malloc packages may have similar features.) + * + * This environment variable must be set when the program is launched. + * Tweaking the environment within the program has no effect. + */ + { + char* debugSet = getenv("MALLOC_CHECK_"); + if (debugSet == nil) + printf("WARNING: MALLOC_CHECK_ not enabled\n\n"); + } +#endif + + result = CommandLoop(); + + exit(result != kNuErrNone); +} + diff --git a/nufxlib-0/samples/ImgConv.c b/nufxlib-0/samples/ImgConv.c new file mode 100644 index 0000000..24acf27 --- /dev/null +++ b/nufxlib-0/samples/ImgConv.c @@ -0,0 +1,624 @@ +/* + * NuFX archive manipulation library + * 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 Library General Public License, see the file COPYING.LIB. + * + * 2IMG <-> SHK converter. This is a practical example of using the + * NufxLib Thread functions to add and extract data in the middle of a file. + * + * Conversions from 2IMG files do not work for raw nibble images. + * Conversions from SHK archives only work if the disk image is in the + * first record in the archive. (This is easy to fix, but I'm trying to + * keep it simple.) + */ +#include "NufxLib.h" +#include "Common.h" + +/* we can get this out of NufxLib if the OS doesn't have it */ +#ifndef HAVE_STRCASECMP +#define strcasecmp Nu_strcasecmp +int Nu_strcasecmp(const char *s1, const char *s2); +#endif + + +#define kTempFile "imgconv.tmp" +#define kLocalFssep PATH_SEP +#define false 0 +#define true (!false) + + +/* + * =========================================================================== + * 2IMG stuff + * =========================================================================== + */ + +#define kImgMagic "2IMG" +#define kMyCreator "NFXL" + +#define kImageFormatDOS 0 +#define kImageFormatProDOS 1 +#define kImageFormatNibble 2 + +/* + * 2IMG header definition (http://www.magnet.ch/emutech/Tech/). + */ +typedef struct ImgHeader { + char magic[4]; + char creator[4]; + short headerLen; + short version; + long imageFormat; + unsigned long flags; + long numBlocks; + long dataOffset; + long dataLen; + long cmntOffset; + long cmntLen; + long creatorOffset; + long creatorLen; + long spare[4]; +} ImgHeader; + +/* + * Read a two-byte little-endian value. + */ +void +ReadShortLE(FILE* fp, short* pBuf) +{ + *pBuf = getc(fp); + *pBuf += (short) getc(fp) << 8; +} + +/* + * Write a two-byte little-endian value. + */ +void +WriteShortLE(FILE* fp, unsigned short val) +{ + putc(val, fp); + putc(val >> 8, fp); +} + +/* + * Read a four-byte little-endian value. + */ +void +ReadLongLE(FILE* fp, long* pBuf) +{ + *pBuf = getc(fp); + *pBuf += (long) getc(fp) << 8; + *pBuf += (long) getc(fp) << 16; + *pBuf += (long) getc(fp) << 24; +} + +/* + * Write a four-byte little-endian value. + */ +void +WriteLongLE(FILE* fp, unsigned long val) +{ + putc(val, fp); + putc(val >> 8, fp); + putc(val >> 16, fp); + putc(val >> 24, fp); +} + +/* + * Read the header from a 2IMG file. + */ +int +ReadImgHeader(FILE* fp, ImgHeader* pHeader) +{ + fread(pHeader->magic, 4, 1, fp); + fread(pHeader->creator, 4, 1, fp); + ReadShortLE(fp, &pHeader->headerLen); + ReadShortLE(fp, &pHeader->version); + ReadLongLE(fp, &pHeader->imageFormat); + ReadLongLE(fp, (long*)&pHeader->flags); + ReadLongLE(fp, &pHeader->numBlocks); + ReadLongLE(fp, &pHeader->dataOffset); + ReadLongLE(fp, &pHeader->dataLen); + ReadLongLE(fp, &pHeader->cmntOffset); + ReadLongLE(fp, &pHeader->cmntLen); + ReadLongLE(fp, &pHeader->creatorOffset); + ReadLongLE(fp, &pHeader->creatorLen); + ReadLongLE(fp, &pHeader->spare[0]); + ReadLongLE(fp, &pHeader->spare[1]); + ReadLongLE(fp, &pHeader->spare[2]); + ReadLongLE(fp, &pHeader->spare[3]); + + if (feof(fp) || ferror(fp)) + return -1; + + if (strncmp(pHeader->magic, kImgMagic, 4) != 0) { + fprintf(stderr, "ERROR: bad magic number on 2IMG file\n"); + return -1; + } + + if (pHeader->version > 1) { + fprintf(stderr, "WARNING: might not be able to handle version=%d\n", + pHeader->version); + } + + return 0; +} + +/* + * Write the header to a 2IMG file. + */ +int +WriteImgHeader(FILE* fp, ImgHeader* pHeader) +{ + fwrite(pHeader->magic, 4, 1, fp); + fwrite(pHeader->creator, 4, 1, fp); + WriteShortLE(fp, pHeader->headerLen); + WriteShortLE(fp, pHeader->version); + WriteLongLE(fp, pHeader->imageFormat); + WriteLongLE(fp, pHeader->flags); + WriteLongLE(fp, pHeader->numBlocks); + WriteLongLE(fp, pHeader->dataOffset); + WriteLongLE(fp, pHeader->dataLen); + WriteLongLE(fp, pHeader->cmntOffset); + WriteLongLE(fp, pHeader->cmntLen); + WriteLongLE(fp, pHeader->creatorOffset); + WriteLongLE(fp, pHeader->creatorLen); + WriteLongLE(fp, pHeader->spare[0]); + WriteLongLE(fp, pHeader->spare[1]); + WriteLongLE(fp, pHeader->spare[2]); + WriteLongLE(fp, pHeader->spare[3]); + + if (ferror(fp)) + return -1; + + return 0; +} + + +/* + * Dump the contents of an ImgHeader. + */ +void +DumpImgHeader(ImgHeader* pHeader) +{ + printf("--- header contents:\n"); + printf("\tmagic = '%.4s'\n", pHeader->magic); + printf("\tcreator = '%.4s'\n", pHeader->creator); + printf("\theaderLen = %d\n", pHeader->headerLen); + printf("\tversion = %d\n", pHeader->version); + printf("\timageFormat = %ld\n", pHeader->imageFormat); + printf("\tflags = 0x%08lx\n", pHeader->flags); + printf("\tnumBlocks = %ld\n", pHeader->numBlocks); + printf("\tdataOffset = %ld\n", pHeader->dataOffset); + printf("\tdataLen = %ld\n", pHeader->dataLen); + printf("\tcmntOffset = %ld\n", pHeader->cmntOffset); + printf("\tcmntLen = %ld\n", pHeader->cmntLen); + printf("\tcreatorOffset = %ld\n", pHeader->creatorOffset); + printf("\tcreatorLen = %ld\n", pHeader->creatorLen); + printf("\n"); +} + + +/* + * =========================================================================== + * Main functions + * =========================================================================== + */ + +typedef enum ArchiveKind { kKindUnknown, kKindShk, kKindImg } ArchiveKind; + +/* + * Create a data source for a ProDOS-ordered image. Since this is already + * in the correct format, we just point at the correct offset in the 2MG file. + */ +NuError +CreateProdosSource(const ImgHeader* pHeader, FILE* fp, + NuDataSource** ppDataSource) +{ + return NuCreateDataSourceForFP(kNuThreadFormatUncompressed, false, 0, fp, + pHeader->dataOffset, pHeader->dataLen, ppDataSource); +} + +/* + * Create a data source for a DOS-ordered image. This is a little harder, + * since we have to reorder the blocks into ProDOS ordering for ShrinkIt. + */ +NuError +CreateDosSource(const ImgHeader* pHeader, FILE* fp, + NuDataSource** ppDataSource) +{ + NuError err; + char* diskBuffer = nil; + long offset; + + if (pHeader->dataLen % 4096) { + fprintf(stderr, + "ERROR: image size must be multiple of 4096 (%ld isn't)\n", + pHeader->dataLen); + err = kNuErrGeneric; + goto bail; + } + + if (fseek(fp, pHeader->dataOffset, SEEK_SET) < 0) { + err = errno; + perror("fseek failed"); + goto bail; + } + + diskBuffer = malloc(pHeader->dataLen); + if (diskBuffer == nil) { + fprintf(stderr, "ERROR: malloc(%ld) failed\n", pHeader->dataLen); + err = kNuErrMalloc; + goto bail; + } + + /* + * Run through the image, reordering each track. This is a + * reversible transformation, i.e. if you do this twice you're back + * to ProDOS ordering. + */ + for (offset = 0; offset < pHeader->dataLen; offset += 4096) { + fread(diskBuffer + offset + 0x0000, 256, 1, fp); + fread(diskBuffer + offset + 0x0e00, 256, 1, fp); + fread(diskBuffer + offset + 0x0d00, 256, 1, fp); + fread(diskBuffer + offset + 0x0c00, 256, 1, fp); + fread(diskBuffer + offset + 0x0b00, 256, 1, fp); + fread(diskBuffer + offset + 0x0a00, 256, 1, fp); + fread(diskBuffer + offset + 0x0900, 256, 1, fp); + fread(diskBuffer + offset + 0x0800, 256, 1, fp); + fread(diskBuffer + offset + 0x0700, 256, 1, fp); + fread(diskBuffer + offset + 0x0600, 256, 1, fp); + fread(diskBuffer + offset + 0x0500, 256, 1, fp); + fread(diskBuffer + offset + 0x0400, 256, 1, fp); + fread(diskBuffer + offset + 0x0300, 256, 1, fp); + fread(diskBuffer + offset + 0x0200, 256, 1, fp); + fread(diskBuffer + offset + 0x0100, 256, 1, fp); + fread(diskBuffer + offset + 0x0f00, 256, 1, fp); + } + if (feof(fp) || ferror(fp)) { + err = errno ? errno : -1; + fprintf(stderr, "ERROR: failed while reading source file\n"); + goto bail; + } + + /* + * Create a data source for the buffer. We set the "doClose" flag to + * "true", so NufxLib will free the buffer for us. + */ + err = NuCreateDataSourceForBuffer(kNuThreadFormatUncompressed, true, 0, + (const unsigned char*) diskBuffer, 0, pHeader->dataLen, + ppDataSource); + if (err == kNuErrNone) + diskBuffer = nil; + +bail: + if (diskBuffer != nil) + free(diskBuffer); + return err; +} + + + +/* + * Convert a 2IMG file into a new SHK archive. + * + * This requires opening up the 2IMG file, verifying that it's okay, and + * then creating a new disk image record and thread. + */ +int +ConvertFromImgToShk(const char* srcName, const char* dstName) +{ + NuError err; + NuArchive* pArchive = nil; + NuDataSource* pDataSource = nil; + NuRecordIdx recordIdx; + NuFileDetails fileDetails; + ImgHeader header; + FILE* fp = nil; + long flushStatus; + char* storageName = nil; + char* cp; + + printf("Converting 2IMG file '%s' to ShrinkIt archive '%s'\n\n", + srcName, dstName); + + err = kNuErrGeneric; + + fp = fopen(srcName, kNuFileOpenReadOnly); + if (fp == NULL) { + perror("fopen failed"); + goto bail; + } + + if (ReadImgHeader(fp, &header) < 0) { + fprintf(stderr, "ERROR: header read failed\n"); + goto bail; + } + + DumpImgHeader(&header); + + if (header.imageFormat != kImageFormatDOS && + header.imageFormat != kImageFormatProDOS) + { + fprintf(stderr, "ERROR: can only handle DOS and ProDOS images\n"); + goto bail; + } + + if (header.numBlocks > 1600) + printf("WARNING: that's a big honking image!\n"); + + /* + * Open a new archive read-write. This refuses to overwrite an + * existing file. + */ + (void) unlink(kTempFile); + err = NuOpenRW(dstName, kTempFile, kNuOpenCreat|kNuOpenExcl, &pArchive); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: unable to create archive (err=%d)\n", err); + goto bail; + } + + /* create the name that will be stored in the archive */ + storageName = strdup(dstName); + cp = strrchr(storageName, '.'); + if (cp != nil) + *cp = '\0'; + cp = strrchr(storageName, kLocalFssep); + if (cp != nil && *(cp+1) != '\0') + cp++; + else + cp = storageName; + + /* + * We can't say "add file", because NufxLib doesn't know what a 2MG + * archive is. However, we can point a DataSource at the data in + * the file, and construct the record manually. + */ + + /* set up the contents of the NuFX Record */ + memset(&fileDetails, 0, sizeof(fileDetails)); + fileDetails.storageName = cp; + fileDetails.fileSysID = kNuFileSysUnknown; /* DOS? ProDOS? */ + fileDetails.fileSysInfo = kLocalFssep; + fileDetails.access = kNuAccessUnlocked; + fileDetails.extraType = header.numBlocks; + fileDetails.storageType = 512; + /* FIX - ought to set the file dates */ + + /* add a new record */ + err = NuAddRecord(pArchive, &fileDetails, &recordIdx); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: unable to create record (err=%d)\n", err); + goto bail; + } + + /* + * Create a data source for the 2IMG file. We do this differently + * for DOS and ProDOS, because we have to rearrange the sector + * ordering for DOS-ordered images (ShrinkIt always uses ProDOS order). + */ + switch (header.imageFormat) { + case kImageFormatDOS: + err = CreateDosSource(&header, fp, &pDataSource); + break; + case kImageFormatProDOS: + err = CreateProdosSource(&header, fp, &pDataSource); + break; + default: + fprintf(stderr, "How the heck did I get here?"); + err = kNuErrInternal; + goto bail; + } + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: unable to create data source (err=%d)\n", err); + goto bail; + } + + /* add a disk image thread */ + err = NuAddThread(pArchive, recordIdx, kNuThreadIDDiskImage, pDataSource, + nil); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: unable to create thread (err=%d)\n", err); + goto bail; + } + pDataSource = nil; /* library owns it now */ + + /* nothing happens until we Flush */ + err = NuFlush(pArchive, &flushStatus); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: flush failed (err=%d, status=0x%04lx)\n", + err, flushStatus); + goto bail; + } + err = NuClose(pArchive); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: close failed (err=%d)\n", err); + goto bail; + } + pArchive = nil; + +bail: + if (pArchive != nil) { + (void)NuAbort(pArchive); + (void)NuClose(pArchive); + } + NuFreeDataSource(pDataSource); + if (storageName != nil) + free(storageName); + if (fp != nil) + fclose(fp); + return (err == kNuErrNone) ? 0 : -1; +} + + +/* + * Convert an SHK archive into a 2IMG file. + * + * This takes a simple-minded approach and assumes that the first record + * in the archive has the disk image in it. If it doesn't, we give up. + */ +int +ConvertFromShkToImg(const char* srcName, const char* dstName) +{ + NuError err; + NuArchive* pArchive = nil; + NuDataSink* pDataSink = nil; + NuRecordIdx recordIdx; + const NuRecord* pRecord; + const NuThread* pThread = nil; + ImgHeader header; + FILE* fp = nil; + int idx; + + printf("Converting ShrinkIt archive '%s' to 2IMG file '%s'\n\n", + srcName, dstName); + + /* + * Open the archive. + */ + err = NuOpenRO(srcName, &pArchive); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: unable to open archive (err=%d)\n", err); + goto bail; + } + + /* get the first record */ + err = NuGetRecordIdxByPosition(pArchive, 0, &recordIdx); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: unable to get first recordIdx (err=%d)\n", err); + goto bail; + } + err = NuGetRecord(pArchive, recordIdx, &pRecord); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: unable to get first record (err=%d)\n", err); + goto bail; + } + + /* find a disk image thread */ + for (idx = 0; idx < (int)NuRecordGetNumThreads(pRecord); idx++) { + pThread = NuGetThread(pRecord, idx); + + if (NuGetThreadID(pThread) == kNuThreadIDDiskImage) + break; + } + if (idx == (int)NuRecordGetNumThreads(pRecord)) { + fprintf(stderr, "ERROR: no disk image found in first record\n"); + err = -1; + goto bail; + } + + /* + * Looks good. Open the 2IMG file, and create the header. + */ + if (access(dstName, F_OK) == 0) { + fprintf(stderr, "ERROR: output file already exists\n"); + err = -1; + goto bail; + } + + fp = fopen(dstName, kNuFileOpenWriteTrunc); + if (fp == NULL) { + perror("fopen failed"); + goto bail; + } + + /* set up the 2MG header, based on the NuFX Record */ + memset(&header, 0, sizeof(header)); + memcpy(header.magic, kImgMagic, sizeof(header.magic)); + memcpy(header.creator, kMyCreator, sizeof(header.creator)); + header.headerLen = 64; + header.version = 1; + header.imageFormat = kImageFormatProDOS; /* always ProDOS-order */ + header.numBlocks = pRecord->recExtraType; + header.dataOffset = 64; + /* old versions of ShrinkIt blew the threadEOF, so use NufxLib's "actual" */ + header.dataLen = pThread->actualThreadEOF; + DumpImgHeader(&header); + if (WriteImgHeader(fp, &header) < 0) { + fprintf(stderr, "ERROR: header write failed\n"); + err = -1; + goto bail; + } + + /* + * We want to expand the disk image thread into "fp" at the current + * offset. + */ + err = NuCreateDataSinkForFP(true, kNuConvertOff, fp, &pDataSink); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: unable to create data sink (err=%d)\n", err); + goto bail; + } + + err = NuExtractThread(pArchive, pThread->threadIdx, pDataSink); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: unable to extract thread (err=%d)\n", err); + goto bail; + } + +bail: + if (pArchive != nil) + NuClose(pArchive); + NuFreeDataSink(pDataSink); + if (fp != nil) + fclose(fp); + return (err == kNuErrNone) ? 0 : -1; +} + + +/* + * Figure out what kind of archive this is by looking at the filename. + */ +ArchiveKind +DetermineKind(const char* filename) +{ + const char* dot; + + dot = strrchr(filename, '.'); + if (dot == nil) + return kKindUnknown; + + if (strcasecmp(dot, ".shk") == 0 || strcasecmp(dot, ".sdk") == 0) + return kKindShk; + else if (strcasecmp(dot, ".2mg") == 0) + return kKindImg; + + return kKindUnknown; +} + + + +/* + * Figure out what we want to do. + */ +int +main(int argc, char** argv) +{ + ArchiveKind kind; + int cc; + + if (argc != 3) { + fprintf(stderr, "Usage: %s (input.2mg|input.shk) output\n", argv[0]); + exit(2); + } + + kind = DetermineKind(argv[1]); + if (kind == kKindUnknown) { + fprintf(stderr, "ERROR: input name must end in '.shk' or '.2mg'\n"); + exit(2); + } + + if (kind == kKindShk) + cc = ConvertFromShkToImg(argv[1], argv[2]); + else + cc = ConvertFromImgToShk(argv[1], argv[2]); + + if (cc) + fprintf(stderr, "Failed\n"); + else + printf("Done!\n"); + + exit(cc != 0); +} + diff --git a/nufxlib-0/samples/Launder.c b/nufxlib-0/samples/Launder.c new file mode 100644 index 0000000..32c6a99 --- /dev/null +++ b/nufxlib-0/samples/Launder.c @@ -0,0 +1,587 @@ +/* + * NuFX archive manipulation library + * 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 Library General Public License, see the file COPYING.LIB. + * + * Run an archive through the laundry. The net result is a duplicate + * archive that matches the original in most respects. Extracting the + * files from the duplicate will yield the same results as if they were + * extracted from the original, but the duplicate archive may differ + * in subtle ways (e.g. filename threads may be added, data may be + * recompressed). + * + * This demonstrates copying threads around, both with and without + * recompressing, between two archives that are open simultaneously. This + * also tests NufxLib's thread ordering and verifies that you can abort + * frequently with no adverse effects. + * + * NOTE: depending on the options you select, you have to have enough + * memory to hold the entire uncompressed contents of the original archive. + * The memory requirements are reduced if you use the "copy only" flag, and + * are virtually eliminated if you use "frequent flush". + */ +#include +#include +#include +#include +#include "NufxLib.h" +#include "Common.h" + + +#define kTempFile "tmp-laundry" + +#define kFlagCopyOnly (1) +#define kFlagReverseThreads (1 << 1) +#define kFlagFrequentFlush (1 << 2) +#define kFlagFrequentAbort (1 << 3) /* implies FrequentFlush */ +#define kFlagUseTmp (1 << 4) + + +/* + * Globals. + */ +char gSentRecordWarning = false; + + + +/* + * Copy a thread, expanding and recompressing it. + * + * This assumes the library is configured for compression (it defaults + * to LZW/2, so this is a reasonable assumption). + */ +NuError +CopyThreadRecompressed(NuArchive* pInArchive, NuArchive* pOutArchive, + long flags, const NuThread* pThread, long newRecordIdx) +{ + NuError err = kNuErrNone; + NuDataSource* pDataSource = nil; + NuDataSink* pDataSink = nil; + uchar* buffer = nil; + + /* + * Allocate a buffer large enough to hold all the uncompressed data, and + * wrap a data sink around it. + * + * If the thread is zero bytes long, we can skip this part. + */ + if (pThread->actualThreadEOF) { + buffer = malloc(pThread->actualThreadEOF); + if (buffer == nil) { + err = kNuErrMalloc; + goto bail; + } + err = NuCreateDataSinkForBuffer(true, kNuConvertOff, buffer, + pThread->actualThreadEOF, &pDataSink); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: unable to create data sink (err=%d)\n", + err); + goto bail; + } + + /* + * Expand the data. For a pre-sized thread, this grabs only the + * interesting part of the buffer. + */ + err = NuExtractThread(pInArchive, pThread->threadIdx, pDataSink); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: unable to extract thread %ld (err=%d)\n", + pThread->threadIdx, err); + goto bail; + } + } + + /* + * The expanded data is in the buffer, now create a data source that + * describes it. + * + * This is complicated by the existence of pre-sized threads, which + * require us to set "otherLen". + * + * We always use "actualThreadEOF" because "thThreadEOF" is broken + * for disk archives created by certain versions of ShrinkIt. + * + * It's okay to pass in a nil value for "buffer", so long as the + * amount of data in the buffer is also zero. The library will do + * the right thing. + */ + if (NuIsPresizedThreadID(NuGetThreadID(pThread))) { + err = NuCreateDataSourceForBuffer(kNuThreadFormatUncompressed, true, + pThread->thCompThreadEOF, buffer, 0, + pThread->actualThreadEOF, &pDataSource); + if (err != kNuErrNone) { + fprintf(stderr, + "ERROR: unable to create pre-sized data source (err=%d)\n",err); + goto bail; + } + } else { + err = NuCreateDataSourceForBuffer(kNuThreadFormatUncompressed, true, + 0, buffer, 0, + pThread->actualThreadEOF, &pDataSource); + if (err != kNuErrNone) { + fprintf(stderr, + "ERROR: unable to create data source (err=%d)\n", err); + goto bail; + } + } + buffer = nil; /* doClose was set, so it's owned by the data source */ + + /* + * Schedule the data for addition to the record. + */ + err = NuAddThread(pOutArchive, newRecordIdx, NuGetThreadID(pThread), + pDataSource, nil); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: unable to add thread (err=%d)\n", err); + goto bail; + } + pDataSource = nil; /* library owns it now */ + +bail: + if (pDataSource != nil) + NuFreeDataSource(pDataSource); + if (pDataSink != nil) + NuFreeDataSink(pDataSink); + if (buffer != nil) + free(buffer); + return err; +} + +/* + * Copy a thread from one archive to another without disturbing the + * compression. + * + * There is a much more efficient way to do this: create an FP + * data source using an offset within the archive file itself. + * Since pInArchive->archiveFp isn't exposed, we can't use that, + * but under most operating systems you aren't prevented from + * opening the same file twice in read-only mode. The file offset + * in pThread tells us where the data is. + * + * The method used below is less memory-efficient but more portable. + * + * This always extracts based on the compThreadEOF, which is + * reliable but extracts a little more than we need on pre-sized + * threads (filenames, comments). + */ +NuError +CopyThreadUncompressed(NuArchive* pInArchive, NuArchive* pOutArchive, + long flags, const NuThread* pThread, long newRecordIdx) +{ + NuError err = kNuErrNone; + NuDataSource* pDataSource = nil; + NuDataSink* pDataSink = nil; + uchar* buffer = nil; + + /* + * If we have some data files that were left uncompressed, perhaps + * because of GSHK's "don't compress anything smaller than 512 bytes" + * rule, NufxLib will try to compress them. We disable this + * behavior by disabling compression. That way, stuff that is + * already compressed will remain that way, and stuff that isn't + * compressed won't be. (We really only need to do this once, at + * the start of the program, but it's illustrative to do it here.) + */ + err = NuSetValue(pOutArchive, kNuValueDataCompression, kNuCompressNone); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: unable to set compression (err=%d)\n", err); + goto bail; + } + + /* + * Allocate a buffer large enough to hold all the compressed data, and + * wrap a data sink around it. + */ + buffer = malloc(pThread->thCompThreadEOF); + if (buffer == nil) { + err = kNuErrMalloc; + goto bail; + } + err = NuCreateDataSinkForBuffer(false, kNuConvertOff, buffer, + pThread->thCompThreadEOF, &pDataSink); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: unable to create data sink (err=%d)\n", + err); + goto bail; + } + + /* + * Get the compressed data. For a pre-sized thread, this grabs the + * entire contents of the buffer, including the padding. + */ + err = NuExtractThread(pInArchive, pThread->threadIdx, pDataSink); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: unable to extract thread %ld (err=%d)\n", + pThread->threadIdx, err); + goto bail; + } + + /* + * The (perhaps compressed) data is in the buffer, now create a data + * source that describes it. + * + * This is complicated by the existence of pre-sized threads. There + * are two possibilities: + * 1. We have a certain amount of non-pre-sized data (thCompThreadEOF) + * that will expand out to a certain length (actualThreadEOF). + * 2. We have a certain amount of pre-sized data (actualThreadEOF) + * that will fit within a buffer (thCompThreadEOF). + * As you can see, the arguments need to be reversed for pre-sized + * threads. + * + * We always use "actualThreadEOF" because "thThreadEOF" is broken + * for disk archives created by certain versions of ShrinkIt. + */ + if (NuIsPresizedThreadID(NuGetThreadID(pThread))) { + err = NuCreateDataSourceForBuffer(pThread->thThreadFormat, true, + pThread->thCompThreadEOF, buffer, 0, + pThread->actualThreadEOF, &pDataSource); + if (err != kNuErrNone) { + fprintf(stderr, + "ERROR: unable to create pre-sized data source (err=%d)\n",err); + goto bail; + } + } else { + err = NuCreateDataSourceForBuffer(pThread->thThreadFormat, true, + pThread->actualThreadEOF, buffer, 0, + pThread->thCompThreadEOF, &pDataSource); + if (err != kNuErrNone) { + fprintf(stderr, + "ERROR: unable to create data source (err=%d)\n", err); + goto bail; + } + } + buffer = nil; /* doClose was set, so it's owned by the data source */ + + /* yes, this is a kluge... sigh */ + err = NuDataSourceSetRawCrc(pDataSource, pThread->thThreadCRC); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: can't set source CRC (err=%d)\n", err); + goto bail; + } + + /* + * Schedule the data for addition to the record. + * + * Note that NuAddThread makes a copy of the data source, and clears + * "doClose" on our copy, so we are free to dispose of pDataSource. + */ + err = NuAddThread(pOutArchive, newRecordIdx, NuGetThreadID(pThread), + pDataSource, nil); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: unable to add thread (err=%d)\n", err); + goto bail; + } + pDataSource = nil; /* library owns it now */ + +bail: + if (pDataSource != nil) + NuFreeDataSource(pDataSource); + if (pDataSink != nil) + NuFreeDataSink(pDataSink); + if (buffer != nil) + free(buffer); + return err; +} + + +/* + * Copy a thread from one archive to another. + * + * Depending on "flags", this will either copy it raw or uncompress and + * recompress. + */ +NuError +CopyThread(NuArchive* pInArchive, NuArchive* pOutArchive, long flags, + const NuThread* pThread, long newRecordIdx) +{ + if (flags & kFlagCopyOnly) { + return CopyThreadUncompressed(pInArchive, pOutArchive, flags, pThread, + newRecordIdx); + } else { + return CopyThreadRecompressed(pInArchive, pOutArchive, flags, pThread, + newRecordIdx); + } +} + + +/* + * Copy a record from the input to the output. + * + * This runs through the list of threads and copies each one individually. + * It will copy them in the original order or in reverse order (the latter + * of which will not usually have any effect since NufxLib imposes a + * specific thread ordering on most common types) depending on "flags". + */ +NuError +CopyRecord(NuArchive* pInArchive, NuArchive* pOutArchive, long flags, + NuRecordIdx recordIdx) +{ + NuError err = kNuErrNone; + const NuRecord* pRecord; + const NuThread* pThread; + NuFileDetails fileDetails; + NuRecordIdx newRecordIdx; + long numThreads; + int idx; + + /* + * Grab the original record and see how many threads it has. + */ + err = NuGetRecord(pInArchive, recordIdx, &pRecord); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: unable to get recordIdx %ld\n", recordIdx); + goto bail; + } + + /* + * Pre-v3 records didn't put CRCs in the thread headers. If we just + * copy the thread over without reprocessing the data, we won't compute + * a CRC for the thread, and we will get CRC failures. + */ + if (!gSentRecordWarning && (flags & kFlagCopyOnly) && + pRecord->recVersionNumber < 3) + { + printf("WARNING: pre-v3 records that aren't recompressed may exhibit CRC failures\n"); + gSentRecordWarning = true; + } + + numThreads = NuRecordGetNumThreads(pRecord); + if (!numThreads) { + fprintf(stderr, "WARNING: recordIdx=%ld was empty\n", recordIdx); + goto bail; + } + + /* + * Create a new record that looks just like the original. + */ + memset(&fileDetails, 0, sizeof(fileDetails)); + fileDetails.storageName = pRecord->filename; + fileDetails.fileSysID = pRecord->recFileSysID; + fileDetails.fileSysInfo = pRecord->recFileSysInfo; + fileDetails.access = pRecord->recAccess; + fileDetails.fileType = pRecord->recFileType; + fileDetails.extraType = pRecord->recExtraType; + fileDetails.storageType = pRecord->recStorageType; + fileDetails.createWhen = pRecord->recCreateWhen; + fileDetails.modWhen = pRecord->recModWhen; + fileDetails.archiveWhen = pRecord->recArchiveWhen; + + err = NuAddRecord(pOutArchive, &fileDetails, &newRecordIdx); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: NuAddRecord failed (err=%d)\n", err); + goto bail; + } + + /* + * Copy the threads. + */ + if (flags & kFlagReverseThreads) { + for (idx = numThreads-1; idx >= 0; idx--) { + pThread = NuGetThread(pRecord, idx); + assert(pThread != nil); + + err = CopyThread(pInArchive, pOutArchive, flags, pThread, + newRecordIdx); + if (err != kNuErrNone) + goto bail; + } + } else { + for (idx = 0; idx < numThreads; idx++) { + pThread = NuGetThread(pRecord, idx); + assert(pThread != nil); + + err = CopyThread(pInArchive, pOutArchive, flags, pThread, + newRecordIdx); + if (err != kNuErrNone) + goto bail; + } + } + +bail: + return err; +} + + +/* + * Launder an archive from inFile to outFile. + * + * Returns 0 on success, nonzero on failure. + */ +int +LaunderArchive(const char* inFile, const char* outFile, long flags) +{ + NuError err = kNuErrNone; + NuArchive* pInArchive = nil; + NuArchive* pOutArchive = nil; + const NuMasterHeader* pMasterHeader; + NuRecordIdx recordIdx; + long idx, flushStatus; + + err = NuOpenRO(inFile, &pInArchive); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: couldn't open input archive '%s' (err=%d)\n", + inFile, err); + goto bail; + } + err = NuOpenRW(outFile, kTempFile, kNuOpenCreat|kNuOpenExcl, &pOutArchive); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: couldn't open output archive '%s' (err=%d)\n", + outFile, err); + goto bail; + } + + /* allow duplicates, in case the original archive has them */ + err = NuSetValue(pOutArchive, kNuValueAllowDuplicates, true); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: couldn't allow duplicates (err=%d)\n", err); + goto bail; + } + + if (flags & kFlagUseTmp) { + err = NuSetValue(pOutArchive, kNuValueModifyOrig, false); + if (err != kNuErrNone) { + fprintf(stderr, + "ERROR: couldn't disable modify orig (err=%d)\n", err); + goto bail; + } + } + + err = NuGetMasterHeader(pInArchive, &pMasterHeader); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: couldn't get master header (err=%d)\n", err); + goto bail; + } + + /* + * Iterate through the set of records. + */ + for (idx = 0; idx < (int)pMasterHeader->mhTotalRecords; idx++) { + err = NuGetRecordIdxByPosition(pInArchive, idx, &recordIdx); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: couldn't get record #%ld (err=%d)\n", + idx, err); + goto bail; + } + + err = CopyRecord(pInArchive, pOutArchive, flags, recordIdx); + if (err != kNuErrNone) + goto bail; + + /* + * If "frequent abort" is set, abort what we just did and redo it. + */ + if (flags & kFlagFrequentAbort) { + printf("(abort)\n"); + err = NuAbort(pOutArchive); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: abort failed (err=%d)\n", err); + goto bail; + } + + err = CopyRecord(pInArchive, pOutArchive, flags, recordIdx); + if (err != kNuErrNone) + goto bail; + + } + + /* + * If "frequent abort" or "frequent flush" is set, flush after + * each record is copied. + */ + if ((flags & kFlagFrequentAbort) || (flags & kFlagFrequentFlush)) { + printf("(flush)\n"); + err = NuFlush(pOutArchive, &flushStatus); + if (err != kNuErrNone) { + fprintf(stderr, + "ERROR: flush failed (err=%d, status=0x%04lx)\n", + err, flushStatus); + goto bail; + } + } + } + + /* first and only flush if frequent-flushing wasn't enabled */ + err = NuFlush(pOutArchive, &flushStatus); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: flush failed (err=%d, status=0x%04lx)\n", + err, flushStatus); + goto bail; + } + +bail: + if (pInArchive != nil) + NuClose(pInArchive); + if (pOutArchive != nil) { + if (err != kNuErrNone) + NuAbort(pOutArchive); + NuClose(pOutArchive); /* flush pending changes and close */ + } + return (err != kNuErrNone); +} + + +/* + * Print usage info. + */ +void +Usage(const char* argv0) +{ + fprintf(stderr, "Usage: %s [-crfat] infile.shk outfile.shk\n", argv0); +} + +/* + * Grab the name of an archive to read. + */ +int +main(int argc, char** argv) +{ + long major, minor, bug; + const char* pBuildDate; + long flags = 0; + char* cp = nil; + int cc; + + (void) NuGetVersion(&major, &minor, &bug, &pBuildDate, nil); + printf("Using NuFX lib %ld.%ld.%ld built on or after %s\n", + major, minor, bug, pBuildDate); + + if (argc < 3 || argc > 4) { + Usage(argv[0]); + exit(2); + } + + if (argc == 4) { + cp = argv[1]; + if (*cp++ != '-') { + Usage(argv[0]); + exit(2); + } + + while (*cp != '\0') { + switch (*cp) { + case 'c': flags |= kFlagCopyOnly; break; + case 'r': flags |= kFlagReverseThreads; break; + case 'f': flags |= kFlagFrequentFlush; break; + case 'a': flags |= kFlagFrequentAbort; break; + case 't': flags |= kFlagUseTmp; break; + default: + Usage(argv[0]); + exit(2); + } + cp++; + } + + argv++; + } + + cc = LaunderArchive(argv[1], argv[2], flags); + + if (cc == 0) + printf("Success!\n"); + else + printf("Failed.\n"); + exit(cc != 0); +} + diff --git a/nufxlib-0/samples/Makefile.in b/nufxlib-0/samples/Makefile.in new file mode 100644 index 0000000..a5f7ef9 --- /dev/null +++ b/nufxlib-0/samples/Makefile.in @@ -0,0 +1,83 @@ +# +# Makefile for nufxlib tests (should work with non-GNU make). +# +# This is normally invoked from the nufxlib makefile. +# +# If you invoke this directly, LIB_PRODUCT won't be defined, and it +# won't automatically detect changes to the library. However, any +# changes to the library should cause a re-build in here anyway if +# you're running "make" from the library directory. +# +SHELL = /bin/sh +CC = @CC@ +AR = ar rcv +#OPT = @CFLAGS@ -DNDEBUG +OPT = @CFLAGS@ +#OPT = @CFLAGS@ -DDEBUG_MSGS +#OPT = @CFLAGS@ -DDEBUG_VERBOSE +GCC_FLAGS = -Wall -Wwrite-strings -Wstrict-prototypes -Wpointer-arith -Wshadow +CFLAGS = @BUILD_FLAGS@ -I. -I.. @DEFS@ + +#ALL_SRCS = $(wildcard *.c *.cpp) +ALL_SRCS = Exerciser.c Launder.c ImgConv.c TestBasic.c TestExtract.c \ + TestSimple.c + +NUFXLIB = -L.. -lnufx @DMALLOC@ + +PRODUCTS = exerciser imgconv launder test-basic test-extract test-simple + +#ifdef PURIFY_BUILD +# PURIFY = purify +# CFLAGS += -DPURIFY +#endif +#ifdef QUANTIFY_BUILD +# QUANTIFY = quantify +# CFLAGS += -DQUANTIFY +#endif + +all: $(PRODUCTS) + @true + +#quantify: +# -rm -f $(PRODUCT) +# @$(MAKE) QUANTIFY_BUILD=1 +# +#purify: +# -rm -f $(PRODUCT) +# @$(MAKE) PURIFY_BUILD=1 + +exerciser: Exerciser.o $(LIB_PRODUCT) + $(PURIFY) $(QUANTIFY) $(CC) -o $@ Exerciser.o $(NUFXLIB) + +imgconv: ImgConv.o $(LIB_PRODUCT) + $(PURIFY) $(QUANTIFY) $(CC) -o $@ ImgConv.o $(NUFXLIB) + +launder: Launder.o $(LIB_PRODUCT) + $(PURIFY) $(QUANTIFY) $(CC) -o $@ Launder.o $(NUFXLIB) + +test-basic: TestBasic.o $(LIB_PRODUCT) + $(PURIFY) $(QUANTIFY) $(CC) -o $@ TestBasic.o $(NUFXLIB) + +test-simple: TestSimple.o $(LIB_PRODUCT) + $(PURIFY) $(QUANTIFY) $(CC) -o $@ TestSimple.o $(NUFXLIB) + +test-extract: TestExtract.o $(LIB_PRODUCT) + $(PURIFY) $(QUANTIFY) $(CC) -o $@ TestExtract.o $(NUFXLIB) + +tags:: + ctags --totals -R ../* + @#ctags *.cpp ../*.c *.h ../*.h + +clean: + -rm -f *.o core + -rm -f $(PRODUCTS) + +distclean: clean + -rm -f tags + -rm -f Makefile Makefile.bak + +depend: + makedepend -- $(CFLAGS) -I/usr/local/include -- $(ALL_SRCS) + +# DO NOT DELETE THIS LINE -- make depend depends on it. + diff --git a/nufxlib-0/samples/Makefile.msc b/nufxlib-0/samples/Makefile.msc new file mode 100644 index 0000000..9437785 --- /dev/null +++ b/nufxlib-0/samples/Makefile.msc @@ -0,0 +1,71 @@ +# +# Makefile for Microsoft C compilers. Tested against Visual C++ 6.0. +# Not pretty but it seems to work. +# +# Run with "nmake /f Makefile.msc". Expects NufxLib to have been built +# in "..". +# +# To build without debugging info, use "nmake nodebug=1". +# + +# Windows magic +TARGETOS = BOTH +!include + +NUFXSRCDIR = .. +LIB_PRODUCT = $(NUFXSRCDIR)\nufxlib.lib + +!ifdef NODEBUG +#OPT = /D NDEBUG /ML /Ogityb2 +OPT = /D DEBUG_MSGS /ML /Ogityb2 +LIB_FLAGS = /nodefaultlib:libcd.lib /nologo libc.lib setargv.obj +!else +OPT = /D DEBUG_MSGS /MLd /Od +LIB_FLAGS = /nodefaultlib:libc.lib /nologo libcd.lib setargv.obj +!endif + +BUILD_FLAGS = /W3 /GX /D "WIN32" /D "_CONSOLE" /I "$(NUFXSRCDIR)" + +# how to compile sources +.c.obj: + @$(cc) $(cdebug) $(OPT) $(BUILD_FLAGS) $(cflags) $(cvars) -o $@ $< + + +PRODUCTS = exerciser.exe imgconv.exe launder.exe test-basic.exe test-extract.exe test-simple.exe + +all: $(PRODUCTS) + +exerciser.exe: Exerciser.obj $(LIB_PRODUCT) + $(link) $(ldebug) Exerciser.obj -out:$@ $(NUFXSRCDIR)\nufxlib.lib $(LIB_FLAGS) + +imgconv.exe: ImgConv.obj $(LIB_PRODUCT) + $(link) $(ldebug) ImgConv.obj -out:$@ $(NUFXSRCDIR)\nufxlib.lib $(LIB_FLAGS) + +launder.exe: Launder.obj $(LIB_PRODUCT) + $(link) $(ldebug) Launder.obj -out:$@ $(NUFXSRCDIR)\nufxlib.lib $(LIB_FLAGS) + +test-basic.exe: TestBasic.obj $(LIB_PRODUCT) + $(link) $(ldebug) TestBasic.obj -out:$@ $(NUFXSRCDIR)\nufxlib.lib $(LIB_FLAGS) + +test-simple.exe: TestSimple.obj $(LIB_PRODUCT) + $(link) $(ldebug) TestSimple.obj -out:$@ $(NUFXSRCDIR)\nufxlib.lib $(LIB_FLAGS) + +test-extract.exe: TestExtract.obj $(LIB_PRODUCT) + $(link) $(ldebug) TestExtract.obj -out:$@ $(NUFXSRCDIR)\nufxlib.lib $(LIB_FLAGS) + +clean: + del *.obj + del exerciser.exe + del imgconv.exe + del launder.exe + del test-basic.exe + del test-simple.exe + del test-extract.exe + +Exerciser.obj: Exerciser.c Common.h $(NUFXSRCDIR)\NufxLib.h $(NUFXSRCDIR)\SysDefs.h +ImgConv.obj: ImgConv.c Common.h $(NUFXSRCDIR)\NufxLib.h $(NUFXSRCDIR)\SysDefs.h +Launder.obj: Launder.c Common.h $(NUFXSRCDIR)\NufxLib.h $(NUFXSRCDIR)\SysDefs.h +TestBasic.obj: TestBasic.c Common.h $(NUFXSRCDIR)\NufxLib.h $(NUFXSRCDIR)\SysDefs.h +TestSimple.obj: TestSimple.c Common.h $(NUFXSRCDIR)\NufxLib.h $(NUFXSRCDIR)\SysDefs.h +TestExtract.obj: TestExtract.c Common.h $(NUFXSRCDIR)\NufxLib.h $(NUFXSRCDIR)\SysDefs.h + diff --git a/nufxlib-0/samples/README-S.txt b/nufxlib-0/samples/README-S.txt new file mode 100644 index 0000000..eac499f --- /dev/null +++ b/nufxlib-0/samples/README-S.txt @@ -0,0 +1,91 @@ +NufxLib "samples" README + +This directory contains some test programs and useful sample code. + + +test-basic +========== + +Basic tests. Run this to verify that things are working. + + +exerciser +========= + +This program allows you to exercise all of NufxLib's basic functions. +Run it without arguments and hit "?" for a list of commands. + +If you think you have found a bug in NufxLib, you can use this to narrow +it down to a repeatable case. + + +imgconv +======= + +A 2IMG disk image converter. You can convert ".2MG" files to ShrinkIt +disk archives, and ShrinkIt disk archives to 2IMG format. imgconv uses +a creator type of "NFXL". + +You can use it like this: + + % imgconv file.shk file.2mg +or + % imgconv file.2mg file.shk + +It figures out what to do based on the filename. It will recognize ".sdk" +as a ShrinkIt archive. + +Limitations: works for DOS-ordered and ProDOS-ordered 2MG images, but +not for raw nibble images. Converting from .shk only works if the first +record in the archive is a disk image; you don't get to pick the one you +want from an archive with several in it. + + +launder +======= + +Run an archive through the laundry. This copies the entire contents of +an archive thread-by-thread, reconstructing it such that the data +matches the original even if the archive contents don't (e.g. records +are updated to version 3, files may be recompressed with LZW/2, option +lists are stripped out, etc). + +The basic usage is: + + % launder [-crfa] infile.shk outfile.shk + +The flags are: + + -c Just copy threads rather than recompressing them + -r Add threads in reverse order + -f Call NuFlush after every record + -a Call NuAbort after every record, then re-do the record and call NuFlush + -t Write to temp file, instead of writing directly into outfile.shk + +If you use the "-c" flag with an archive created by P8 ShrinkIt or NuLib, +the laundered archive may have CRC failures. This is because "launder" +creates version 3 records, which are expected to have a valid CRC in the +thread header. The only way to compute the CRC is to uncompress the data, +which "launder" doesn't do when "-c" is set. The data itself is fine, +it's just the thread CRC that's wrong (if the data were hosed, the LZW/1 +CRC would be bad too). "launder" will issue a warning when it detects +this situation. + +If you find that you're running out of memory on very large archives, you +can reduce the memory requirements by specifying the "-f" flag. + + +test-simple +=========== + +Simple test program. Give it the name of an archive, and it will display +the contents. + + +test-extract +============ + +Simple test program. Give it the name of an archive, and it will write +all filename threads into "out.buf", "out.fp", and "out.file" using three +different kinds of NuDataSinks. + diff --git a/nufxlib-0/samples/TestBasic.c b/nufxlib-0/samples/TestBasic.c new file mode 100644 index 0000000..72a278b --- /dev/null +++ b/nufxlib-0/samples/TestBasic.c @@ -0,0 +1,1158 @@ +/* + * NuFX archive manipulation library + * 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 Library General Public License, see the file COPYING.LIB. + * + * Test basic features of the library. Run this without arguments. + */ +#include +#include +#include "NufxLib.h" +#include "Common.h" + +#define kTestArchive "nlbt.shk" +#define kTestTempFile "nlbt.tmp" + +#define kNumEntries 3 /* how many records are we going to add? */ +#define kTestEntryBytes "bytes" +#define kTestEntryEnglish "English" +#define kTestEntryLong "three|is a fairly long filename, complete with" \ + "punctuation and other nifty/bad stuff" +#define kLocalFssep '|' + +/* + * Globals. + */ +char gSuppressError = false; +#define FAIL_OK gSuppressError = true; +#define FAIL_BAD gSuppressError = false; + + +/* + * =========================================================================== + * Helper functions + * =========================================================================== + */ + +/* + * Get a single character of input from the user. + */ +static char +TGetReplyChar(char defaultReply) +{ + char tmpBuf[32]; + + if (fgets(tmpBuf, sizeof(tmpBuf), stdin) == nil) + return defaultReply; + if (tmpBuf[0] == '\n' || tmpBuf[0] == '\r') + return defaultReply; + + return tmpBuf[0]; +} + +NuError +AddSimpleRecord(NuArchive* pArchive, const char* filename, + NuRecordIdx* pRecordIdx) +{ + NuFileDetails fileDetails; + + memset(&fileDetails, 0, sizeof(fileDetails)); + fileDetails.storageName = filename; + fileDetails.fileSysInfo = kLocalFssep; + fileDetails.access = kNuAccessUnlocked; + + return NuAddRecord(pArchive, &fileDetails, pRecordIdx); +} + + +/* + * Display error messages... or not. + */ +NuResult +ErrorMessageHandler(NuArchive* pArchive, void* vErrorMessage) +{ + const NuErrorMessage* pErrorMessage = (const NuErrorMessage*) vErrorMessage; + + if (gSuppressError) + return kNuOK; + + if (pErrorMessage->isDebug) { + fprintf(stderr, "NufxLib says: [%s:%d %s] %s\n", + pErrorMessage->file, pErrorMessage->line, pErrorMessage->function, + pErrorMessage->message); + } else { + fprintf(stderr, "NufxLib says: %s\n", pErrorMessage->message); + } + + return kNuOK; +} + + +/* + * =========================================================================== + * Tests + * =========================================================================== + */ + +/* + * Make sure the flags that control how we open the file work right, + * and verify that we handle existing zero-byte archive files correctly. + */ +int +Test_OpenFlags(void) +{ + NuError err; + FILE* fp = nil; + NuArchive* pArchive = nil; + + printf("... open zero-byte existing\n"); + fp = fopen(kTestArchive, kNuFileOpenWriteTrunc); + if (fp == nil) { + perror("fopen kTestArchive"); + goto failed; + } + fclose(fp); + fp = nil; + + FAIL_OK; + err = NuOpenRW(kTestArchive, kTestTempFile, kNuOpenCreat|kNuOpenExcl, + &pArchive); + FAIL_BAD; + if (err == kNuErrNone) { + fprintf(stderr, "ERROR: file opened when it shouldn't have\n"); + goto failed; + } + + err = NuOpenRW(kTestArchive, kTestTempFile, kNuOpenCreat, &pArchive); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: file didn't open when it should have\n"); + goto failed; + } + + err = NuClose(pArchive); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: close faileded\n"); + goto failed; + } + pArchive = nil; + + if (access(kTestArchive, F_OK) == 0) { + fprintf(stderr, "ERROR: archive should have been removed but wasn't\n"); + goto failed; + } + + return 0; + +failed: + if (pArchive != nil) { + NuAbort(pArchive); + NuClose(pArchive); + } + return -1; +} + + +/* + * Add some files to the archive. These will be used by later tests. + */ +int +Test_AddStuff(NuArchive* pArchive) +{ + NuError err; + uchar* buf = nil; + NuDataSource* pDataSource = nil; + NuRecordIdx recordIdx; + long status; + int i; + static const char* testMsg = + "This is a nice test message that has linefeeds in it so we can\n" + "see if the line conversion stuff is actually doing anything at\n" + "all. It's certainly nice to know that everything works the way\n" + "it's supposed to, which I suppose is why we have this nifty test\n" + "program available. It sure would be nice if everybody tested\n" + "there code, but where would Microsoft be without endless upgrades\n" + "and service packs? Bugs are what America was built on, and\n" + "anybody who says otherwise is a pinko commie lowlife. Verily.\n"; + + printf("... add 'bytes' record\n"); + buf = malloc(131072); + if (buf == nil) + goto failed; + for (i = 0; i < 131072; i++) + *(buf+i) = i & 0xff; + + FAIL_OK; + err = NuCreateDataSourceForBuffer(kNuThreadFormatUncompressed, true, + 0, nil, 0, 131072, &pDataSource); + FAIL_BAD; + if (err == kNuErrNone) { + fprintf(stderr, "ERROR: that should've faileded!\n"); + goto failed; + } + + /* + * Create a data source for the big batch of bytes. + */ + err = NuCreateDataSourceForBuffer(kNuThreadFormatUncompressed, true, + 0, buf, 0, 131072, &pDataSource); + if (err != kNuErrNone) { + fprintf(stderr, + "ERROR: 'bytes' data source create faileded (err=%d)\n", err); + goto failed; + } + buf = nil; /* now owned by library */ + + err = AddSimpleRecord(pArchive, kTestEntryBytes, &recordIdx); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: 'bytes' record faileded (err=%d)\n", err); + goto failed; + } + + err = NuAddThread(pArchive, recordIdx, kNuThreadIDDataFork, pDataSource, + nil); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: 'bytes' thread add faileded (err=%d)\n", err); + goto failed; + } + pDataSource = nil; /* now owned by library */ + + + /* + * Create a data source for our lovely text message. + */ + printf("... add 'English' record\n"); + err = NuCreateDataSourceForBuffer(kNuThreadFormatUncompressed, false, + 0, (const uchar*)testMsg, 0, strlen(testMsg), &pDataSource); + if (err != kNuErrNone) { + fprintf(stderr, + "ERROR: 'English' source create faileded (err=%d)\n", err); + goto failed; + } + + FAIL_OK; + err = NuAddThread(pArchive, recordIdx, kNuThreadIDDataFork, pDataSource, + nil); + FAIL_BAD; + if (err == kNuErrNone) { + fprintf(stderr, "ERROR: 'English' add should've conflicted!\n"); + goto failed; + } + + FAIL_OK; + err = AddSimpleRecord(pArchive, kTestEntryBytes, &recordIdx); + FAIL_BAD; + if (err == kNuErrNone) { + fprintf(stderr, "ERROR: duplicates not allowed, should've faileded\n"); + goto failed; + } + + err = AddSimpleRecord(pArchive, kTestEntryEnglish, &recordIdx); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: 'English' record faileded (err=%d)\n", err); + goto failed; + } + + err = NuAddThread(pArchive, recordIdx, kNuThreadIDDataFork, pDataSource, + nil); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: 'English' thread add faileded (err=%d)\n", err); + goto failed; + } + pDataSource = nil; /* now owned by library */ + + + /* + * Create an empty file with a rather non-empty name. + */ + printf("... add 'long' record\n"); + err = NuCreateDataSourceForBuffer(kNuThreadFormatUncompressed, false, + 0, nil, 0, 0, &pDataSource); + if (err != kNuErrNone) { + fprintf(stderr, + "ERROR: 'English' source create faileded (err=%d)\n", err); + goto failed; + } + + err = AddSimpleRecord(pArchive, kTestEntryLong, &recordIdx); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: 'long' record faileded (err=%d)\n", err); + goto failed; + } + + err = NuAddThread(pArchive, recordIdx, kNuThreadIDRsrcFork, pDataSource, + nil); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: 'long' thread add faileded (err=%d)\n", err); + goto failed; + } + pDataSource = nil; /* now owned by library */ + + + /* + * Flush changes. + */ + err = NuFlush(pArchive, &status); + if (err != kNuErrNone) { + fprintf(stderr,"ERROR: couldn't flush after add (err=%d, status=%ld)\n", + err, status); + goto failed; + } + + /* + * Flush again; should succeed since it doesn't have to do anything. + */ + err = NuFlush(pArchive, &status); + if (err != kNuErrNone) { + fprintf(stderr,"ERROR: second add flush failed (err=%d, status=%ld)\n", + err, status); + goto failed; + } + + return 0; +failed: + if (pDataSource != nil) + NuFreeDataSource(pDataSource); + if (buf != nil) + free(buf); + return -1; +} + + +/* + * Make sure that what we're seeing makes sense. + */ +NuResult +TestContentsCallback(NuArchive* pArchive, void* vpRecord) +{ + const NuRecord* pRecord = (NuRecord*) vpRecord; + + if (strcmp(pRecord->filename, kTestEntryBytes) == 0 || + strcmp(pRecord->filename, kTestEntryEnglish) == 0 || + strcmp(pRecord->filename, kTestEntryLong) == 0) + { + return kNuOK; + } + + fprintf(stderr, "ERROR: found mystery entry '%s'\n", pRecord->filename); + return kNuAbort; +} + + +/* + * Verify that the contents look about right. + */ +int +Test_Contents(NuArchive* pArchive) +{ + NuError err; + long posn; + NuRecordIdx recordIdx; + const NuRecord* pRecord; + int cc; + + /* + * First, do it with a callback. + */ + err = NuContents(pArchive, TestContentsCallback); + if (err != kNuErrNone) + goto failed; + + /* + * Now step through the records with get-by-position and verify that + * they're in the expected order. + */ + for (posn = 0; posn < kNumEntries; posn++) { + err = NuGetRecordIdxByPosition(pArchive, posn, &recordIdx); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: couldn't get record #%ld (err=%d)\n", + posn, err); + goto failed; + } + + err = NuGetRecord(pArchive, recordIdx, &pRecord); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: couldn't get record index %ld (err=%d)\n", + recordIdx, err); + goto failed; + } + assert(pRecord != nil); + + switch (posn) { + case 0: + cc = strcmp(pRecord->filename, kTestEntryBytes); + break; + case 1: + cc = strcmp(pRecord->filename, kTestEntryEnglish); + break; + case 2: + cc = strcmp(pRecord->filename, kTestEntryLong); + if (!cc) + cc = !(pRecord->recStorageType == kNuStorageExtended); + break; + default: + fprintf(stderr, "ERROR: somebody forgot to put a case here (%ld)\n", + posn); + cc = -1; + } + + if (cc) { + fprintf(stderr, "ERROR: got '%s' for %ld (%ld), not expected\n", + pRecord->filename, posn, recordIdx); + goto failed; + } + } + + /* + * Read one more past the end, should fail. + */ + FAIL_OK; + err = NuGetRecordIdxByPosition(pArchive, posn, &recordIdx); + FAIL_BAD; + if (err == kNuErrNone) { + fprintf(stderr, "ERROR: too many records (%ld was ok)\n", posn); + goto failed; + } + + return 0; +failed: + return -1; +} + + +/* + * Selection callback filter for "test". This gets called once per record. + */ +NuResult +VerifySelectionCallback(NuArchive* pArchive, void* vpProposal) +{ + NuError err; + const NuSelectionProposal* pProposal = vpProposal; + long count; + + if (pProposal->pRecord == nil || pProposal->pThread == nil || + pProposal->pRecord->filename == nil) + { + fprintf(stderr, "ERROR: unexpected nil in proposal\n"); + goto failed; + } + + err = NuGetExtraData(pArchive, (void**) &count); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: unable to get extra data (err=%d)\n", err); + goto failed; + } + + count++; + + err = NuSetExtraData(pArchive, (void*) count); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: unable to inc extra data (err=%d)\n", err); + goto failed; + } + + return kNuOK; +failed: + return kNuAbort; +} + +/* + * Verify the archive contents. + */ +int +Test_Verify(NuArchive* pArchive) +{ + NuError err; + long count; + + printf("... verifying CRCs\n"); + + err = NuSetSelectionFilter(pArchive, VerifySelectionCallback); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: unable to set selection filter (err=%d)\n",err); + goto failed; + } + + err = NuSetExtraData(pArchive, (void*) 0); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: unable to set extra data (err=%d)\n", err); + goto failed; + } + + err = NuTest(pArchive); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: verify failed (err=%d)\n", err); + goto failed; + } + + err = NuGetExtraData(pArchive, (void**) &count); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: last extra data get failed (err=%d)\n", err); + goto failed; + } + + if (count != kNumEntries) { + fprintf(stderr, "ERROR: verified %ld when expecting %d\n", count, + kNumEntries); + goto failed; + } + + return 0; +failed: + return -1; +} + +/* + * Extract stuff. + */ +int +Test_Extract(NuArchive* pArchive) +{ + NuError err; + NuRecordIdx recordIdx; + const NuRecord* pRecord; + const NuThread* pThread; + NuDataSink* pDataSink = nil; + uchar* buf = nil; + + printf("... extracting files\n"); + + /* + * Tell it the current system uses CRLF, so it'll bloat up when we do + * a text conversion. + */ + err = NuSetValue(pArchive, kNuValueEOL, kNuEOLCRLF); + + /* + * Extract "bytes". + */ + err = NuGetRecordIdxByName(pArchive, kTestEntryBytes, &recordIdx); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: couldn't find '%s' (err=%d)\n", kTestEntryBytes, + err); + goto failed; + } + err = NuGetRecord(pArchive, recordIdx, &pRecord); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: couldn't get record index %ld (err=%d)\n", + recordIdx, err); + goto failed; + } + assert(pRecord != nil); + + /* we're not using ShrinkIt compat mode, so there should not be a comment */ + pThread = NuGetThread(pRecord, 1); + assert(pThread != nil); + if (NuGetThreadID(pThread) != kNuThreadIDDataFork) { + fprintf(stderr, "ERROR: 'bytes' had unexpected threadID 0x%08lx\n", + NuGetThreadID(pThread)); + goto failed; + } + + buf = malloc(pThread->actualThreadEOF); + if (buf == nil) { + fprintf(stderr, "ERROR: malloc(%ld) failed\n",pThread->actualThreadEOF); + goto failed; + } + + /* + * Try to extract it with text conversion off. + */ + err = NuCreateDataSinkForBuffer(true, kNuConvertOff, buf, + pThread->actualThreadEOF, &pDataSink); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: couldn't create data sink (err=%d)\n", err); + goto failed; + } + + err = NuExtractThread(pArchive, pThread->threadIdx, pDataSink); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: couldn't extract 'bytes' (off) (err=%d)\n", + err); + goto failed; + } + NuFreeDataSink(pDataSink); + pDataSink = nil; + + /* + * Try to extract with "on" conversion, which should fail because the + * buffer is too small. + */ + err = NuCreateDataSinkForBuffer(true, kNuConvertOn, buf, + pThread->actualThreadEOF, &pDataSink); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: couldn't create data sink (err=%d)\n", err); + goto failed; + } + + FAIL_OK; + err = NuExtractThread(pArchive, pThread->threadIdx, pDataSink); + FAIL_BAD; + if (err == kNuErrNone) { + fprintf(stderr, "ERROR: managed to extract bloated 'bytes'?\n"); + goto failed; + } + NuFreeDataSink(pDataSink); + pDataSink = nil; + + /* + * Try to extract with "auto" conversion, which should conclude that + * the input is text and not try to convert. + */ + err = NuCreateDataSinkForBuffer(true, kNuConvertAuto, buf, + pThread->actualThreadEOF, &pDataSink); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: couldn't create data sink (err=%d)\n", err); + goto failed; + } + + err = NuExtractThread(pArchive, pThread->threadIdx, pDataSink); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: couldn't extract 'bytes' (auto) (err=%d)\n", + err); + goto failed; + } + NuFreeDataSink(pDataSink); + pDataSink = nil; + + + + free(buf); + buf = nil; + + + + /* + * Extract "English". + */ + err = NuGetRecordIdxByName(pArchive, kTestEntryEnglish, &recordIdx); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: couldn't find '%s' (err=%d)\n", + kTestEntryEnglish, err); + goto failed; + } + err = NuGetRecord(pArchive, recordIdx, &pRecord); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: couldn't get record index %ld (err=%d)\n", + recordIdx, err); + goto failed; + } + assert(pRecord != nil); + + /* we're not using ShrinkIt compat mode, so there should not be a comment */ + pThread = NuGetThread(pRecord, 1); + assert(pThread != nil); + if (NuGetThreadID(pThread) != kNuThreadIDDataFork) { + fprintf(stderr, "ERROR: 'English' had unexpected threadID 0x%08lx\n", + NuGetThreadID(pThread)); + goto failed; + } + + buf = malloc(pThread->actualThreadEOF); + if (buf == nil) { + fprintf(stderr, "ERROR: malloc(%ld) failed\n",pThread->actualThreadEOF); + goto failed; + } + + /* + * Try to extract it with text conversion off. + */ + err = NuCreateDataSinkForBuffer(true, kNuConvertOff, buf, + pThread->actualThreadEOF, &pDataSink); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: couldn't create data sink (err=%d)\n", err); + goto failed; + } + + err = NuExtractThread(pArchive, pThread->threadIdx, pDataSink); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: couldn't extract 'bytes' (off) (err=%d)\n", + err); + goto failed; + } + NuFreeDataSink(pDataSink); + pDataSink = nil; + + /* + * Try to extract with "auto" conversion, which should fail because the + * buffer is too small, and the input looks like text. + */ + err = NuCreateDataSinkForBuffer(true, kNuConvertAuto, buf, + pThread->actualThreadEOF, &pDataSink); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: couldn't create data sink (err=%d)\n", err); + goto failed; + } + + FAIL_OK; + err = NuExtractThread(pArchive, pThread->threadIdx, pDataSink); + FAIL_BAD; + if (err == kNuErrNone) { + fprintf(stderr, "ERROR: managed to extract bloated 'English'?\n"); + goto failed; + } + NuFreeDataSink(pDataSink); + pDataSink = nil; + + + + /*Free(buf);*/ + /*buf = nil;*/ + + + + /* + * Extract "long" (which is zero bytes). + */ + err = NuGetRecordIdxByName(pArchive, kTestEntryLong, &recordIdx); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: couldn't find '%s' (err=%d)\n", + kTestEntryLong, err); + goto failed; + } + err = NuGetRecord(pArchive, recordIdx, &pRecord); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: couldn't get record index %ld (err=%d)\n", + recordIdx, err); + goto failed; + } + assert(pRecord != nil); + + /* we're not using ShrinkIt compat mode, so there should not be a comment */ + pThread = NuGetThread(pRecord, 1); + assert(pThread != nil); + if (NuGetThreadID(pThread) != kNuThreadIDRsrcFork) { + fprintf(stderr, "ERROR: 'Long' had unexpected threadID 0x%08lx\n", + NuGetThreadID(pThread)); + goto failed; + } + + /* + * Try it with text conversion on; shouldn't matter. + */ + err = NuCreateDataSinkForBuffer(true, kNuConvertOn, buf, + 1, &pDataSink); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: couldn't create data sink (err=%d)\n", err); + goto failed; + } + + err = NuExtractThread(pArchive, pThread->threadIdx, pDataSink); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: couldn't extract 'Long' (off) (err=%d)\n", + err); + goto failed; + } + NuFreeDataSink(pDataSink); + pDataSink = nil; + + + + free(buf); + buf = nil; + + + + return 0; +failed: + if (buf != nil) + free(buf); + if (pDataSink != nil) + (void) NuFreeDataSink(pDataSink); + return -1; +} + +/* + * Delete the first and last records. Does *not* flush the archive. + */ +int +Test_Delete(NuArchive* pArchive) +{ + NuError err; + NuRecordIdx recordIdx; + const NuRecord* pRecord; + const NuThread* pThread = nil; + long count; + int idx; + + printf("... deleting first and last\n"); + + /* + * Delete all threads from the first record ("bytes"). + */ + err = NuGetRecordIdxByPosition(pArchive, 0, &recordIdx); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: couldn't find #%d (err=%d)\n", 0, err); + goto failed; + } + err = NuGetRecord(pArchive, recordIdx, &pRecord); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: couldn't get record index %ld (err=%d)\n", + recordIdx, err); + goto failed; + } + assert(pRecord != nil); + assert(pRecord->recTotalThreads > 0); + + for (idx = 0; idx < (int)pRecord->recTotalThreads; idx++) { + pThread = NuGetThread(pRecord, idx); + assert(pThread != nil); + + err = NuDeleteThread(pArchive, pThread->threadIdx); + if (err != kNuErrNone) { + fprintf(stderr, + "ERROR: couldn't delete thread #%d (%ld) (err=%d)\n", + idx, recordIdx, err); + goto failed; + } + } + + /* try to re-delete the same thread */ + assert(pThread != nil); + FAIL_OK; + err = NuDeleteThread(pArchive, pThread->threadIdx); + FAIL_BAD; + if (err == kNuErrNone) { + fprintf(stderr, "ERROR: allowed to re-delete thread (%ld) (err=%d)\n", + recordIdx, err); + goto failed; + } + + /* try to delete the modified record */ + FAIL_OK; + err = NuDeleteRecord(pArchive, recordIdx); + FAIL_BAD; + if (err == kNuErrNone) { + fprintf(stderr, + "ERROR: able to delete modified record (%ld) (err=%d)\n", + recordIdx, err); + goto failed; + } + + /* + * Make sure the attr hasn't been updated yet. + */ + err = NuGetAttr(pArchive, kNuAttrNumRecords, (unsigned long*) &count); + if (count != kNumEntries) { + fprintf(stderr, "ERROR: kNuAttrNumRecords %ld vs %d\n", + count, kNumEntries); + goto failed; + } + + /* + * Delete the last record ("long"). + */ + err = NuGetRecordIdxByPosition(pArchive, kNumEntries-1, &recordIdx); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: couldn't find #%d (err=%d)\n", 0, err); + goto failed; + } + err = NuGetRecord(pArchive, recordIdx, &pRecord); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: couldn't get record index %ld (err=%d)\n", + recordIdx, err); + goto failed; + } + assert(pRecord != nil); + + /* grab the first thread before we whack the record */ + pThread = NuGetThread(pRecord, 0); + assert(pThread != nil); + + err = NuDeleteRecord(pArchive, recordIdx); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: unable to delete record #%d (%ld) (err=%d)\n", + kNumEntries-1, recordIdx, err); + goto failed; + } + + /* try to delete a thread from the deleted record */ + FAIL_OK; + err = NuDeleteThread(pArchive, pThread->threadIdx); + FAIL_BAD; + if (err == kNuErrNone) { + fprintf(stderr, + "ERROR: allowed to delete from deleted (%ld) (err=%d)\n", + pThread->threadIdx, err); + goto failed; + } + + return 0; +failed: + return -1; +} + + +/* + * Verify that the count in the master header has been updated. + */ +int +Test_MasterCount(NuArchive* pArchive, long expected) +{ + NuError err; + const NuMasterHeader* pMasterHeader; + + printf("... checking master count\n"); + + err = NuGetMasterHeader(pArchive, &pMasterHeader); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: couldn't get master header (err=%d)\n", err); + goto failed; + } + + if (pMasterHeader->mhTotalRecords != (ulong)expected) { + fprintf(stderr, "ERROR: unexpected MH count (%ld vs %ld)\n", + pMasterHeader->mhTotalRecords, expected); + goto failed; + } + + return 0; +failed: + return -1; +} + + +/* + * Run some tests. + * + * Returns 0 on success, -1 on error. + */ +int +DoTests(void) +{ + NuError err; + NuArchive* pArchive = nil; + long status; + int cc, result = 0; + char answer; + + /* + * Make sure we're starting with a clean slate. + */ + if (access(kTestArchive, F_OK) == 0) { + printf("Test archive '%s' exists, remove (y/n)? ", kTestArchive); + fflush(stdout); + answer = TGetReplyChar('n'); + if (tolower(answer) != 'y') + goto failed; + cc = unlink(kTestArchive); + if (cc < 0) { + perror("unlink kTestArchive"); + goto failed; + } + } + if (access(kTestTempFile, F_OK) == 0) { + printf("Test temp file '%s' exists, remove (y/n)? ", kTestTempFile); + fflush(stdout); + answer = TGetReplyChar('n'); + if (tolower(answer) != 'y') + goto failed; + cc = unlink(kTestTempFile); + if (cc < 0) { + perror("unlink kTestTempFile"); + goto failed; + } + } + + /* + * Test some of the open flags. + */ + if (Test_OpenFlags() != 0) + goto failed; + + /* + * Create a new archive to play with. + */ + err = NuOpenRW(kTestArchive, kTestTempFile, kNuOpenCreat|kNuOpenExcl, + &pArchive); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: NuOpenRW faileded (err=%d)\n", err); + goto failed; + } + err = NuSetErrorMessageHandler(pArchive, ErrorMessageHandler); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: couldn't set message handler (err=%d)\n", err); + goto failed; + } + + /* + * Add some test entries. + */ + if (Test_AddStuff(pArchive) != 0) + goto failed; + + /* + * Check the archive contents. + */ + printf("... checking contents\n"); + if (Test_Contents(pArchive) != 0) + goto failed; + + /* + * Reopen it read-only. + */ + printf("... reopening archive read-only\n"); + err = NuClose(pArchive); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: mid NuClose failed (err=%d)\n", err); + goto failed; + } + pArchive = nil; + + err = NuOpenRO(kTestArchive, &pArchive); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: NuOpenRO failed (err=%d)\n", err); + goto failed; + } + err = NuSetErrorMessageHandler(pArchive, ErrorMessageHandler); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: couldn't set message handler (err=%d)\n", err); + goto failed; + } + + /* + * Make sure the contents are still what we expect. + */ + printf("... checking contents\n"); + if (Test_Contents(pArchive) != 0) + goto failed; + + /* + * Verify the archive contents. + */ + if (Test_Verify(pArchive) != 0) + goto failed; + + /* + * Extract the files. + */ + if (Test_Extract(pArchive) != 0) + goto failed; + + /* + * Reopen it read-write. + */ + printf("... reopening archive read-write\n"); + err = NuClose(pArchive); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: late NuClose failed (err=%d)\n", err); + goto failed; + } + pArchive = nil; + + err = NuOpenRW(kTestArchive, kTestTempFile, 0, &pArchive); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: re-NuOpenRW failed (err=%d)\n", err); + goto failed; + } + err = NuSetErrorMessageHandler(pArchive, ErrorMessageHandler); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: couldn't set message handler (err=%d)\n", err); + goto failed; + } + + /* + * Contents shouldn't have changed. + */ + printf("... checking contents\n"); + if (Test_Contents(pArchive) != 0) + goto failed; + + /* + * Test deletion. + */ + if (Test_Delete(pArchive) != 0) + goto failed; + + /* + * Abort the changes and verify that nothing has changed. + */ + printf("... aborting changes\n"); + err = NuAbort(pArchive); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: abort failed (err=%d)\n", err); + goto failed; + } + + printf("... checking contents\n"); + if (Test_Contents(pArchive) != 0) + goto failed; + + /* + * Delete them again. + */ + if (Test_Delete(pArchive) != 0) + goto failed; + + /* + * Flush the deletions. This should remove the first and last records. + */ + err = NuFlush(pArchive, &status); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: flush failed (err=%d, status=%ld)\n", + err, status); + goto failed; + } + + /* + * Check count in master header. + */ + if (Test_MasterCount(pArchive, kNumEntries-2) != 0) + goto failed; + + /* + * That's all, folks... + */ + NuClose(pArchive); + pArchive = nil; + + printf("... removing '%s'\n", kTestArchive); + cc = unlink(kTestArchive); + if (cc < 0) { + perror("unlink kTestArchive"); + goto failed; + } + + +leave: + if (pArchive != nil) { + NuAbort(pArchive); + NuClose(pArchive); + } + return result; + +failed: + result = -1; + goto leave; +} + + +/* + * Crank away. + */ +int +main(void) +{ + long major, minor, bug; + const char* pBuildDate; + const char* pBuildFlags; + int cc; + + (void) NuGetVersion(&major, &minor, &bug, &pBuildDate, &pBuildFlags); + printf("Using NuFX library v%ld.%ld.%ld, built on or after\n" + "%s with [%s]\n\n", + major, minor, bug, pBuildDate, pBuildFlags); + + if (NuSetGlobalErrorMessageHandler(ErrorMessageHandler) != kNuErrNone) { + fprintf(stderr, "ERROR: can't set the global message handler"); + exit(1); + } + + printf("... starting tests\n"); + + cc = DoTests(); + + printf("... tests ended, %s\n", cc == 0 ? "SUCCESS" : "FAILURE"); + exit(cc != 0); +} + diff --git a/nufxlib-0/samples/TestExtract.c b/nufxlib-0/samples/TestExtract.c new file mode 100644 index 0000000..4688b0e --- /dev/null +++ b/nufxlib-0/samples/TestExtract.c @@ -0,0 +1,494 @@ +/* + * NuFX archive manipulation library + * 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 Library General Public License, see the file COPYING.LIB. + * + * Test extraction of individual threads in various ways. The net result + * of this is three files (out.file, out.fp, out.buf) that contain the + * result of writing all filenames in an archive to the same data sink. + * + * This gathers up information on the contents of the archive via a + * callback, and then emits all of the data at once. + * + * (This was originally written in C++, and converted to C after I repented.) + */ +#include +#include +#include +#include +#include "NufxLib.h" +#include "Common.h" + + +#define false 0 +#define true (!false) + +#define kHappySize 2408 + + +/* + * =========================================================================== + * ArchiveRecord + * =========================================================================== + */ + +/* + * Track an archive record. + */ +typedef struct ArchiveRecord { + char* filename; + NuRecordIdx recordIdx; + + long numThreads; + NuThread* pThreads; + + struct ArchiveRecord* pNext; +} ArchiveRecord; + + +/* + * Alloc a new ArchiveRecord. + */ +ArchiveRecord* +ArchiveRecord_New(const NuRecord* pRecord) +{ + ArchiveRecord* pArcRec = nil; + + pArcRec = malloc(sizeof(*pArcRec)); + if (pArcRec == nil) + return nil; + + if (pRecord->filename == nil) + pArcRec->filename = strdup(""); + else + pArcRec->filename = strdup((char*)pRecord->filename); + + pArcRec->recordIdx = pRecord->recordIdx; + pArcRec->numThreads = NuRecordGetNumThreads(pRecord); + (void) NuRecordCopyThreads(pRecord, &pArcRec->pThreads); + + pArcRec->pNext = nil; + + return pArcRec; +} + +/* + * Free up an ArchiveRecord. + */ +void +ArchiveRecord_Free(ArchiveRecord* pArcRec) +{ + if (pArcRec == nil) + return; + + if (pArcRec->filename != nil) + free(pArcRec->filename); + if (pArcRec->pThreads != nil) + free(pArcRec->pThreads); + free(pArcRec); +} + +/* + * Find a thread with a matching NuThreadID. + */ +const NuThread* +ArchiveRecord_FindThreadByID(const ArchiveRecord* pArcRec, NuThreadID threadID) +{ + const NuThread* pThread; + int i; + + for (i = 0; i < pArcRec->numThreads; i++) { + pThread = NuThreadGetByIdx(pArcRec->pThreads, i); + if (NuGetThreadID(pThread) == threadID) + return pThread; + } + + return nil; +} + + +const char* +ArchiveRecord_GetFilename(const ArchiveRecord* pArcRec) +{ + return pArcRec->filename; +} + +NuRecordIdx +ArchiveRecord_GetRecordIdx(const ArchiveRecord* pArcRec) +{ + return pArcRec->recordIdx; +} + +long +ArchiveRecord_GetNumThreads(const ArchiveRecord* pArcRec) +{ + return pArcRec->numThreads; +} + +const NuThread* +ArchiveRecord_GetThread(const ArchiveRecord* pArcRec, int idx) +{ + if (idx < 0 || idx >= pArcRec->numThreads) + return nil; + return NuThreadGetByIdx(pArcRec->pThreads, idx); +} + +void +ArchiveRecord_SetNext(ArchiveRecord* pArcRec, ArchiveRecord* pNextRec) +{ + pArcRec->pNext = pNextRec; +} + +ArchiveRecord* +ArchiveRecord_GetNext(const ArchiveRecord* pArcRec) +{ + return pArcRec->pNext; +} + + +/* + * =========================================================================== + * ArchiveData + * =========================================================================== + */ + +/* + * A collection of records. + */ +typedef struct ArchiveData { + long numRecords; + ArchiveRecord* pRecordHead; + ArchiveRecord* pRecordTail; +} ArchiveData; + + +ArchiveData* +ArchiveData_New(void) +{ + ArchiveData* pArcData; + + pArcData = malloc(sizeof(*pArcData)); + if (pArcData == nil) + return nil; + + pArcData->numRecords = 0; + pArcData->pRecordHead = pArcData->pRecordTail = nil; + + return pArcData; +} + +void +ArchiveData_Free(ArchiveData* pArcData) +{ + ArchiveRecord* pNext; + + if (pArcData == nil) + return; + + printf("*** Deleting %ld records!\n", pArcData->numRecords); + while (pArcData->pRecordHead != nil) { + pNext = ArchiveRecord_GetNext(pArcData->pRecordHead); + ArchiveRecord_Free(pArcData->pRecordHead); + pArcData->pRecordHead = pNext; + } + + free(pArcData); +} + + +ArchiveRecord* +ArchiveData_GetRecordHead(const ArchiveData* pArcData) +{ + return pArcData->pRecordHead; +} + + +/* add an ArchiveRecord to the list pointed at by ArchiveData */ +void +ArchiveData_AddRecord(ArchiveData* pArcData, ArchiveRecord* pRecord) +{ + assert(pRecord != nil); + assert((pArcData->pRecordHead == nil && pArcData->pRecordTail == nil) || + (pArcData->pRecordHead != nil && pArcData->pRecordTail != nil)); + + if (pArcData->pRecordHead == nil) { + /* first */ + pArcData->pRecordHead = pArcData->pRecordTail = pRecord; + } else { + /* not first, add to end */ + ArchiveRecord_SetNext(pArcData->pRecordTail, pRecord); + pArcData->pRecordTail = pRecord; + } + + pArcData->numRecords++; +} + +/* dump the contents of the ArchiveData to stdout */ +void +ArchiveData_DumpContents(const ArchiveData* pArcData) +{ + ArchiveRecord* pArcRec; + + pArcRec = pArcData->pRecordHead; + while (pArcRec != nil) { + const NuThread* pThread; + int i, count; + + printf("%5ld '%s'\n", + ArchiveRecord_GetRecordIdx(pArcRec), + ArchiveRecord_GetFilename(pArcRec)); + + count = ArchiveRecord_GetNumThreads(pArcRec); + for (i = 0; i < count; i++) { + pThread = ArchiveRecord_GetThread(pArcRec, i); + printf(" %5ld 0x%04x 0x%04x\n", pThread->threadIdx, + pThread->thThreadClass, pThread->thThreadKind); + } + + pArcRec = ArchiveRecord_GetNext(pArcRec); + } +} + + +/* + * =========================================================================== + * Main stuff + * =========================================================================== + */ + +/* + * Callback function to collect archive information. + */ +NuResult +GatherContents(NuArchive* pArchive, void* vpRecord) +{ + NuRecord* pRecord = (NuRecord*) vpRecord; + ArchiveData* pArchiveData = nil; + ArchiveRecord* pArchiveRecord = ArchiveRecord_New(pRecord); + + NuGetExtraData(pArchive, (void**)&pArchiveData); + assert(pArchiveData != nil); + + printf("*** Filename = '%s'\n", + pRecord->filename == nil ? "":(const char*)pRecord->filename); + + ArchiveData_AddRecord(pArchiveData, pArchiveRecord); + + return kNuOK; +} + + +/* + * Copy the filename thread from every record to "pDataSink". + */ +NuError +ReadAllFilenameThreads(NuArchive* pArchive, ArchiveData* pArchiveData, + NuDataSink* pDataSink) +{ + NuError err = kNuErrNone; + ArchiveRecord* pArchiveRecord; + const NuThread* pThread; + + pArchiveRecord = ArchiveData_GetRecordHead(pArchiveData); + while (pArchiveRecord != nil) { + pThread = ArchiveRecord_FindThreadByID(pArchiveRecord, + kNuThreadIDFilename); + if (pThread != nil) { + err = NuExtractThread(pArchive, pThread->threadIdx, pDataSink); + if (err != kNuErrNone) { + fprintf(stderr, "*** Extract failed (%d)\n", err); + goto bail; + } + } + pArchiveRecord = ArchiveRecord_GetNext(pArchiveRecord); + } + +bail: + return err; +} + + +/* extract every filename thread into a single file, overwriting each time */ +NuError +ExtractToFile(NuArchive* pArchive, ArchiveData* pArchiveData) +{ + NuError err; + NuDataSink* pDataSink = nil; + + err = NuCreateDataSinkForFile(true, kNuConvertOff, "out.file", PATH_SEP, + &pDataSink); + if (err != kNuErrNone) + goto bail; + + err = NuSetValue(pArchive, kNuValueHandleExisting, kNuAlwaysOverwrite); + if (err != kNuErrNone) + goto bail; + + err = ReadAllFilenameThreads(pArchive, pArchiveData, pDataSink); + if (err != kNuErrNone) + goto bail; + +bail: + (void) NuFreeDataSink(pDataSink); + if (err == kNuErrNone) + printf("*** File write complete\n"); + return err; +} + +/* extract every filename thread into a FILE*, appending */ +NuError +ExtractToFP(NuArchive* pArchive, ArchiveData* pArchiveData) +{ + NuError err; + FILE* fp = nil; + NuDataSink* pDataSink = nil; + + if ((fp = fopen("out.fp", kNuFileOpenWriteTrunc)) == nil) + return kNuErrFileOpen; + + err = NuCreateDataSinkForFP(true, kNuConvertOff, fp, &pDataSink); + if (err != kNuErrNone) + goto bail; + + err = ReadAllFilenameThreads(pArchive, pArchiveData, pDataSink); + if (err != kNuErrNone) + goto bail; + +bail: + (void) NuFreeDataSink(pDataSink); + if (fp != nil) + fclose(fp); + if (err == kNuErrNone) + printf("*** FP write complete\n"); + return err; +} + +/* extract every filename thread into a buffer, advancing as we go */ +NuError +ExtractToBuffer(NuArchive* pArchive, ArchiveData* pArchiveData) +{ + NuError err; + unsigned char buffer[kHappySize]; + NuDataSink* pDataSink = nil; + unsigned long count; + + err = NuCreateDataSinkForBuffer(true, kNuConvertOff, buffer, kHappySize, + &pDataSink); + if (err != kNuErrNone) + goto bail; + + err = ReadAllFilenameThreads(pArchive, pArchiveData, pDataSink); + if (err != kNuErrNone) { + if (err == kNuErrBufferOverrun) + fprintf(stderr, "*** Hey, buffer wasn't big enough!\n"); + goto bail; + } + + /* write the buffer to a file */ + (void) NuDataSinkGetOutCount(pDataSink, &count); + if (count > 0) { + FILE* fp; + if ((fp = fopen("out.buf", kNuFileOpenWriteTrunc)) != nil) { + + printf("*** Writing %ld bytes\n", count); + if (fwrite(buffer, count, 1, fp) != 1) + err = kNuErrFileWrite; + fclose(fp); + } + } else { + printf("*** No data found!\n"); + } + +bail: + (void) NuFreeDataSink(pDataSink); + return err; +} + + +/* + * Do file stuff. + */ +int +DoFileStuff(const char* filename) +{ + NuError err; + NuArchive* pArchive = nil; + NuMasterHeader* pMasterHeader = nil; + ArchiveData* pArchiveData = ArchiveData_New(); + + err = NuOpenRO(filename, &pArchive); + if (err != kNuErrNone) + goto bail; + + NuSetExtraData(pArchive, pArchiveData); + + printf("*** Gathering contents!\n"); + err = NuContents(pArchive, GatherContents); + if (err != kNuErrNone) + goto bail; + + printf("*** Dumping contents!\n"); + ArchiveData_DumpContents(pArchiveData); + + err = ExtractToFile(pArchive, pArchiveData); + if (err != kNuErrNone) + goto bail; + err = ExtractToFP(pArchive, pArchiveData); + if (err != kNuErrNone) + goto bail; + err = ExtractToBuffer(pArchive, pArchiveData); + if (err != kNuErrNone) + goto bail; + +bail: + if (err != kNuErrNone) + fprintf(stderr, "*** ERROR: got error %d\n", err); + + if (pArchive != nil) { + NuError err2 = NuClose(pArchive); + if (err == kNuErrNone && err2 != kNuErrNone) + err = err2; + } + + if (pMasterHeader != nil) + free(pMasterHeader); + + if (pArchiveData != nil) + free(pArchiveData); + + return err; +} + + +/* + * Grab the name of an archive to read. If no name was provided, use stdin. + */ +int +main(int argc, char** argv) +{ + long major, minor, bug; + const char* pBuildDate; + FILE* infp; + int cc; + + (void) NuGetVersion(&major, &minor, &bug, &pBuildDate, nil); + printf("Using NuFX lib %ld.%ld.%ld built on or after %s\n", + major, minor, bug, pBuildDate); + + if (argc == 2) { + infp = fopen(argv[1], kNuFileOpenReadOnly); + if (infp == nil) { + perror("fopen failed"); + exit(1); + } + } else { + fprintf(stderr, "ERROR: you have to specify a filename\n"); + exit(2); + } + + cc = DoFileStuff(argv[1]); + + exit(cc != 0); +} + diff --git a/nufxlib-0/samples/TestSimple.c b/nufxlib-0/samples/TestSimple.c new file mode 100644 index 0000000..c0355e5 --- /dev/null +++ b/nufxlib-0/samples/TestSimple.c @@ -0,0 +1,105 @@ +/* + * NuFX archive manipulation library + * 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 Library General Public License, see the file COPYING.LIB. + * + * Simple test program. Opens an archive, dumps the contents. + * + * If the first argument is "-", this will read from stdin. Otherwise, + * the first argument is taken to be an archive filename, and opened. + */ +#include +#include "NufxLib.h" +#include "Common.h" + + +/* + * Callback function to display the contents of a single record. + * + * "pRecord->filename" is the record's filename, whether from the record + * header, a filename thread, or a default value ("UNKNOWN", stuffed in + * when a record has no filename at all). + */ +NuResult +ShowContents(NuArchive* pArchive, void* vpRecord) +{ + const NuRecord* pRecord = (NuRecord*) vpRecord; + + printf("*** Filename = '%s'\n", pRecord->filename); + + return kNuOK; +} + + +/* + * Dump the contents from the streaming input. + * + * If we're not interested in handling an archive on stdin, we could just + * pass the filename in here and use NuOpenRO instead. + */ +int +DoStreamStuff(FILE* fp) +{ + NuError err; + NuArchive* pArchive = nil; + + err = NuStreamOpenRO(fp, &pArchive); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: unable to open stream archive (err=%d)\n", err); + goto bail; + } + + printf("*** Streaming contents!\n"); + + err = NuContents(pArchive, ShowContents); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: NuContents failed (err=%d)\n", err); + goto bail; + } + +bail: + if (pArchive != nil) { + NuError err2 = NuClose(pArchive); + if (err == kNuErrNone) + err = err2; + } + + return err; +} + + +/* + * Grab the name of an archive to read. If "-" was given, use stdin. + */ +int +main(int argc, char** argv) +{ + long major, minor, bug; + const char* pBuildDate; + FILE* infp = nil; + int cc; + + (void) NuGetVersion(&major, &minor, &bug, &pBuildDate, nil); + printf("Using NuFX lib %ld.%ld.%ld built on or after %s\n", + major, minor, bug, pBuildDate); + + if (argc != 2) { + fprintf(stderr, "Usage: %s (archive-name|-)\n", argv[0]); + exit(2); + } + + if (strcmp(argv[1], "-") == 0) + infp = stdin; + else { + infp = fopen(argv[1], kNuFileOpenReadOnly); + if (infp == nil) { + fprintf(stderr, "ERROR: unable to open '%s'\n", argv[1]); + exit(1); + } + } + + cc = DoStreamStuff(infp); + exit(cc != 0); +} + diff --git a/nulib2/Add.c b/nulib2/Add.c new file mode 100644 index 0000000..fc3246d --- /dev/null +++ b/nulib2/Add.c @@ -0,0 +1,109 @@ +/* + * 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. + * + * Add files to or update files in the archive. + */ +#include "Nulib2.h" + +static NuError AddToArchive(NulibState* pState, NuArchive* pArchive); + + +/* + * Add the specified files to a new or existing archive. + */ +NuError +DoAdd(NulibState* pState) +{ + NuError err; + NuArchive* pArchive = nil; + long flushStatus; + + assert(pState != nil); + + err = OpenArchiveReadWrite(pState); + if (err != kNuErrNone) + goto bail; + + pArchive = NState_GetNuArchive(pState); + assert(pArchive != nil); + + NState_SetMatchCount(pState, 0); + + /* tell them about the list of files */ + err = AddToArchive(pState, pArchive); + if (err != kNuErrNone) + goto bail; + + /*(void)NuDebugDumpArchive(pArchive);*/ + + if (!NState_GetMatchCount(pState)) + printf("%s: no records matched\n", gProgName); + +bail: + if (pArchive != nil) { + NuError err2; + + #if 0 + if (err != kNuErrNone) { + printf("Attempting to flush changes in spite of errors...\n"); + err = kNuErrNone; + } + #endif + + if (err == kNuErrNone) { + err = NuFlush(pArchive, &flushStatus); + if (err != kNuErrNone && err == kNuErrNone) { + ReportError(err, + "Unable to flush archive changes (status=0x%04lx)", + flushStatus); + NuAbort(pArchive); + } + } else { + NuAbort(pArchive); + } + + err2 = NuClose(pArchive); + assert(err2 == kNuErrNone); + } + return err; +} + + +/* + * Add the requested files to the specified archive. + * + * This just results in NuAddFile calls; the deferred write operation + * isn't initiated. + */ +static NuError +AddToArchive(NulibState* pState, NuArchive* pArchive) +{ + NuError err = kNuErrNone; + char* const* pSpec; + ulong fileCount; + int i; + + assert(pState != nil); + assert(pArchive != nil); + + if (!NState_GetFilespecCount(pState)) { + err = kNuErrSyntax; + ReportError(err, "no files were specified"); + } + + fileCount = 0; + + pSpec = NState_GetFilespecPointer(pState); + for (i = NState_GetFilespecCount(pState); i > 0; i--, pSpec++) { + err = AddFile(pState, pArchive, *pSpec); + if (err != kNuErrNone) + goto bail; + } + +bail: + return err; +} + diff --git a/nulib2/ArcUtils.c b/nulib2/ArcUtils.c new file mode 100644 index 0000000..16a6261 --- /dev/null +++ b/nulib2/ArcUtils.c @@ -0,0 +1,935 @@ +/* + * 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. + * + * Common archive-related utility functions. + */ +#include "Nulib2.h" + + +/* + * =========================================================================== + * Output pathnames + * =========================================================================== + */ + +/* + * General-purpose output pathname filter, invoked by nufxlib via a + * callback. Normalizes the pathname to match the current OS requirements. + * + * If we're extracting to stdout, this fills in the "newFp" field instead. + * + * The buffer we return to the archive library will be overwritten the + * next time this function gets called. This is expected. + */ +static NuResult +OutputPathnameFilter(NuArchive* pArchive, void* vproposal) +{ + NuPathnameProposal* pathProposal = vproposal; + NulibState* pState; + const char* newPathname; + NuRecordIdx renameFromIdx; + char* renameToStr; + char* resultBuf; + + assert(pArchive != nil); + (void) NuGetExtraData(pArchive, (void**) &pState); + assert(pState != nil); + + /* handle extract-to-pipe */ + if (NState_GetCommand(pState) == kCommandExtractToPipe) { + pathProposal->newDataSink = NState_GetPipeSink(pState); + NState_SetSuppressOutput(pState, true); + return kNuOK; + } + + /* + * If they're trying to rename this file, do so now. We don't run + * the output through the normalizer; if the user typed it, assume + * that it's okay (and let the OS tell them if it isn't). + */ + renameToStr = NState_GetRenameToStr(pState); + if (renameToStr != nil) { + renameFromIdx = NState_GetRenameFromIdx(pState); + + if (renameFromIdx == pathProposal->pRecord->recordIdx) { + /* right source file, proceed with the rename */ + NState_SetTempPathnameLen(pState, strlen(renameToStr) +1); + resultBuf = NState_GetTempPathnameBuf(pState); + assert(resultBuf != nil); + strcpy(resultBuf, renameToStr); + + pathProposal->newPathname = resultBuf; + } + + /* free up renameToStr */ + NState_SetRenameToStr(pState, nil); + + goto bail; + } + + /* + * Convert the pathname into something suitable for the current OS. + */ + newPathname = NormalizePath(pState, pathProposal); + if (newPathname == nil) { + ReportError(kNuErrNone, "unable to convert pathname"); + return kNuAbort; + } + + pathProposal->newPathname = newPathname; + +bail: + return kNuOK; +} + + +/* + * =========================================================================== + * User input + * =========================================================================== + */ + +/* + * Get a single character from the input. + * + * For portability, I'm just getting a line of input and keeping the + * first character. A fancier version would play with line disciplines + * so you wouldn't have to hit "return". + */ +static char +GetReplyChar(char defaultReply) +{ + char tmpBuf[32]; + + if (fgets(tmpBuf, sizeof(tmpBuf), stdin) == nil) + return defaultReply; + if (tmpBuf[0] == '\n' || tmpBuf[0] == '\r') + return defaultReply; + + return tmpBuf[0]; +} + + +#define kMaxInputLen 128 + +/* + * Get a string back from the user. + * + * String returned should be freed by the caller. + */ +static char* +GetReplyString(const char* prompt) +{ + char buf[kMaxInputLen]; + char* result; + int len; + + printf("%s", prompt); + fflush(stdout); + result = fgets(buf, sizeof(buf), stdin); + if (result == nil || feof(stdin) || ferror(stdin) || buf[0] == '\0' || + buf[0] == '\n') + { + return nil; + } + + /* nuke the terminating '\n', which is lots of fun in filenames */ + len = strlen(buf); + if (buf[len-1] == '\n') + buf[len-1] = '\0'; + + return strdup(buf); +} + + +/* + * Get a one-line comment from the user, of at most "maxLen" bytes. + * + * If the user enters a blank line, return "nil". + * + * A pointer to a newly-allocated buffer is returned. + */ +char* +GetSimpleComment(NulibState* pState, const char* pathname, int maxLen) +{ + char* buf = nil; + char* result; + int len; + + buf = Malloc(maxLen); + if (buf == nil) + return nil; + + printf("Enter one-line comment for '%s'\n: ", pathname); + fflush(stdout); + + result = fgets(buf, maxLen, stdin); + if (result == nil || feof(stdin) || ferror(stdin) || buf[0] == '\0' || + buf[0] == '\n') + { + Free(buf); + return nil; + } + + /* nuke the terminating '\n', which we don't need */ + len = strlen(buf); + if (buf[len-1] == '\n') + buf[len-1] = '\0'; + + return buf; +} + + +/* + * =========================================================================== + * Callbacks (progress updates, error handling, record selection) + * =========================================================================== + */ + +#define kMaxDisplayLen 60 + +/* + * Returns "true" if the filespec in "spec" matches what's in "pRecord". + * + * (Someday "spec" might be a regexp.) + */ +static Boolean +SpecMatchesRecord(NulibState* pState, const char* spec, const NuRecord* pRecord) +{ + if (NState_GetModRecurse(pState)) + return (strncmp(spec, pRecord->filename, strlen(spec)) == 0); + else + return (strcmp(spec, pRecord->filename) == 0); +} + +/* + * Determine whether the current record 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 +IsSpecified(NulibState* pState, const NuRecord* pRecord) +{ + 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 (SpecMatchesRecord(pState, *pSpec, pRecord)) + return true; + } + + return false; +} + + +/* + * General-purpose selection filter, invoked as a callback. Compares the + * selection proposal with the filenames in "filespec". + */ +NuResult +SelectionFilter(NuArchive* pArchive, void* vproposal) +{ + const NuSelectionProposal* selProposal = vproposal; + NulibState* pState; + + assert(pArchive != nil); + (void) NuGetExtraData(pArchive, (void**) &pState); + assert(pState != nil); + + if (IsSpecified(pState, selProposal->pRecord)) { + NState_IncMatchCount(pState); + + /* we don't get progress notifications for delete, so do it here */ + if (NState_GetCommand(pState) == kCommandDelete) + printf("Deleting %s\n", selProposal->pRecord->filename); + + return kNuOK; + } else + return kNuSkip; +} + + +/* + * Print a three-digit progress percentage; range is 0% to 100%. + */ +void +PrintPercentage(ulong total, ulong progress) +{ + ulong perc; + + if (!total) { + /*printf(" %%");*/ + printf(" "); + return; + } + + if (total < 21474836) { + perc = (progress * 100 + 50) / total; + if (perc > 100) + perc = 100; + } else { + perc = progress / (total / 100); + if (perc > 100) + perc = 100; + } + + printf("%3ld%%", perc); +} + +/* + * Show our progress, unless we're expanding to a pipe. Invoked as a + * callback by nufxlib. + */ +NuResult +ProgressUpdater(NuArchive* pArchive, void* vProgress) +{ + const NuProgressData* pProgress = vProgress; + NulibState* pState; + const char* percStr; + const char* actionStr; + char nameBuf[kMaxDisplayLen+1]; + Boolean showName, eolConv; + + assert(pArchive != nil); + (void) NuGetExtraData(pArchive, (void**) &pState); + assert(pState != nil); + + if (NState_GetSuppressOutput(pState)) + return kNuOK; + + percStr = nil; + showName = false; + eolConv = false; + + switch (pProgress->operation) { + case kNuOpAdd: + switch (pProgress->state) { + case kNuProgressPreparing: + case kNuProgressOpening: + actionStr = "adding "; + showName = true; + break; + case kNuProgressCompressing: + actionStr = "compressing"; + break; + case kNuProgressStoring: + actionStr = "storing "; + break; + default: + actionStr = "?????? "; + break; + } + break; + case kNuOpExtract: + switch (pProgress->state) { + case kNuProgressPreparing: + case kNuProgressOpening: + actionStr = "extracting"; + showName = true; + break; + case kNuProgressExpanding: + actionStr = "expanding "; + break; + case kNuProgressCopying: + actionStr = "extracting"; + break; + default: + actionStr = "?????? "; + break; + } + if (pProgress->expand.convertEOL == kNuConvertOn) + eolConv = true; + break; + case kNuOpTest: + switch (pProgress->state) { + case kNuProgressPreparing: + case kNuProgressOpening: + showName = true; + /* no break */ + case kNuProgressExpanding: + case kNuProgressCopying: + actionStr = "verifying"; + break; + default: + actionStr = "?????? "; + break; + } + break; + default: + assert(0); + actionStr = "????"; + } + + switch (pProgress->state) { + case kNuProgressDone: + actionStr = nil; + percStr = "DONE\n"; + break; + case kNuProgressSkipped: + actionStr = nil; + percStr = "SKIP\n"; + break; + case kNuProgressFailed: + actionStr = nil; + percStr = "FAIL\n"; + break; + default: + break; + } + + if (showName) { + /* + * Use "pathname" (whole thing) rather than "filename" (file part). + * Could also use "origPathname", but I like to show what they're + * getting instead of what they're giving. + */ + int len = strlen(pProgress->pathname); + if (len < sizeof(nameBuf)) { + strcpy(nameBuf, pProgress->pathname); + } else { + nameBuf[0] = nameBuf[1] = '.'; + strncpy(nameBuf+2, pProgress->pathname + len - (sizeof(nameBuf)-3), + sizeof(nameBuf)-3); + nameBuf[sizeof(nameBuf)-1] = '\0'; + } + } + + if (actionStr == nil && percStr != nil) { + printf("\r%s", percStr); + } else if (actionStr != nil && percStr == nil) { + if (percStr == nil) { + putc('\r', stdout); + PrintPercentage(pProgress->uncompressedLength, + pProgress->uncompressedProgress); + if (showName) + printf(" %s%s %s", actionStr, eolConv ? "+" : " ", nameBuf); + else + printf(" %s%s", actionStr, eolConv ? "+" : " "); + } + } else { + assert(0); + printf("????\n"); + } + + fflush(stdout); + /*printf(" %ld \n", pProgress->uncompressedProgress);*/ + /*usleep(250000);*/ + + return kNuOK; +} + + +/* + * Decide whether or not to replace an existing file (during extract) + * or record (during add). + */ +static NuResult +HandleReplaceExisting(NulibState* pState, NuArchive* pArchive, + const NuErrorStatus* pErrorStatus) +{ + NuResult result = kNuOK; + char* renameName; + char reply; + + assert(pState != nil); + assert(pErrorStatus != nil); + assert(pErrorStatus->pathname != nil); + + assert(pErrorStatus->canOverwrite); + assert(pErrorStatus->canSkip); + assert(pErrorStatus->canAbort); + + if (NState_GetInputUnavailable(pState)) { + putc('\n', stdout); + ReportError(pErrorStatus->err, "Giving up"); + result = kNuAbort; + goto bail; + } + + while (1) { + printf("\n Replace %s? [y]es, [n]o, [A]ll, [N]one", + pErrorStatus->pathname); + if (pErrorStatus->canRename) /* renaming records not allowed */ + printf(", [r]ename: "); + else + printf(": "); + fflush(stdout); + + reply = GetReplyChar('n'); + + switch (reply) { + case 'y': + result = kNuOverwrite; + goto bail; + case 'n': + result = kNuSkip; + goto bail; + case 'A': + (void) NuSetValue(pArchive, kNuValueHandleExisting, + kNuAlwaysOverwrite); + result = kNuOverwrite; + goto bail; + case 'N': + (void) NuSetValue(pArchive, kNuValueHandleExisting, + kNuNeverOverwrite); + result = kNuSkip; + goto bail; + case 'r': + if (!pErrorStatus->canRename) { + printf("Response not acceptable\n"); + break; /* continue in "while" loop */ + } + renameName = GetReplyString("New name: "); + if (renameName == nil) + break; /* continue in "while" loop */ + if (pErrorStatus->pRecord == nil) { + ReportError(kNuErrNone, "Unexpected nil record"); + break; /* continue in "while" loop */ + } + NState_SetRenameFromIdx(pState, + pErrorStatus->pRecord->recordIdx); + NState_SetRenameToStr(pState, renameName); + result = kNuRename; + goto bail; + case 'q': /* stealth option to quit */ + case 'Q': + result = kNuAbort; + goto bail; + default: + printf("Response not understood -- please use y/n/A/N/r\n"); + break; /* continue in "while" loop */ + } + } + +bail: + return result; +} + +/* + * Found a bad CRC... should we press onward? + * + * Note pErrorStatus->pathname may be nil if the error was found in the + * master header or in the record header. + */ +static NuResult +HandleBadCRC(NulibState* pState, NuArchive* pArchive, + const NuErrorStatus* pErrorStatus) +{ + NuResult result = kNuOK; + char reply; + + assert(pState != nil); + assert(pErrorStatus != nil); + + if (NState_GetInputUnavailable(pState)) { + putc('\n', stderr); + ReportError(pErrorStatus->err, "Giving up"); + result = kNuAbort; + goto bail; + } + + while (1) { + if (pErrorStatus->pathname != nil) + fprintf(stderr, "\n Found a bad CRC in %s\n", + pErrorStatus->pathname); + else + fprintf(stderr, "\n Found a bad CRC in the archive\n"); + + fprintf(stderr, + " Archive may be damaged, continue anyway? [y]es, [n]o: "); + fflush(stderr); + + reply = GetReplyChar('n'); + + switch (reply) { + case 'y': + result = kNuIgnore; + goto bail; + case 'n': + case 'N': + result = kNuAbort; + goto bail; + default: + fprintf(stderr, "Response not understood -- please use y/n\n"); + break; /* continue in "while" loop */ + } + } + +bail: + return result; +} + +#if 0 +/* + * Tried to add a nonexistent file; continue? + */ +static NuResult +HandleAddNotFound(NulibState* pState, NuArchive* pArchive, + const NuErrorStatus* pErrorStatus) +{ + NuResult result = kNuOK; + char reply; + + assert(pState != nil); + assert(pErrorStatus != nil); + assert(pErrorStatus->pathname != nil); + + if (NState_GetInputUnavailable(pState)) { + putc('\n', stdout); + ReportError(pErrorStatus->err, "Giving up"); + result = kNuAbort; + goto bail; + } + + while (1) { + fprintf("\n Couldn't find %s, continue? [y]es, [n]o: ", + pErrorStatus->pathname); + fflush(stderr); + + reply = GetReplyChar('n'); + + switch (reply) { + case 'y': + result = kNuSkip; + goto bail; + case 'n': + case 'N': + result = kNuAbort; + goto bail; + default: + fprintf(stderr, "Response not understood -- please use y/n\n"); + break; /* continue in "while" loop */ + } + } + +bail: + return result; +} +#endif + +/* + * Something failed, and the user may want to choose how to handle it. + * Invoked as a callback. + */ +NuResult +ErrorHandler(NuArchive* pArchive, void* vErrorStatus) +{ + const NuErrorStatus* pErrorStatus = vErrorStatus; + NulibState* pState; + NuResult result; + + assert(pArchive != nil); + (void) NuGetExtraData(pArchive, (void**) &pState); + assert(pState != nil); + + /* default action is to abort the current operation */ + result = kNuAbort; + + /* + * When extracting, the error handler callback gets invoked for several + * different problems because we might want to rename the file. Also, + * because extractions are done with "bulk" calls, returning an + * individual error message would be meaningless. + * + * When adding files, the NuAddFile and NuAddRecord calls can return + * immediate, specific results for a single add. The only reasons for + * calling here are to decide if an existing record should be replaced + * or not (without even an option to rename), or to decide what to do + * when the NuFlush call runs into a problem while adding a file. + */ + if (pErrorStatus->operation == kNuOpExtract) { + if (pErrorStatus->err == kNuErrFileExists) { + result = HandleReplaceExisting(pState, pArchive, pErrorStatus); + } else if (pErrorStatus->err == kNuErrNotNewer) { + /* if we were expecting this, it's okay */ + if (NState_GetModFreshen(pState) || NState_GetModUpdate(pState)) { + printf("\rSKIP\n"); + result = kNuSkip; + } else { + DBUG(("WEIRD one\n")); + } + } else if (pErrorStatus->err == kNuErrDuplicateNotFound) { + /* if we were expecting this, it's okay */ + if (NState_GetModFreshen(pState)) { + printf("\rSKIP\n"); + result = kNuSkip; + } else { + DBUG(("WEIRD two\n")); + } + } + } else if (pErrorStatus->operation == kNuOpAdd) { + if (pErrorStatus->err == kNuErrRecordExists) { + /* if they want to update or freshen, don't hassle them */ + if (NState_GetModFreshen(pState) || NState_GetModUpdate(pState)) + result = kNuOverwrite; + else + result = HandleReplaceExisting(pState, pArchive, pErrorStatus); + } else if (pErrorStatus->err == kNuErrFileNotFound) { + /* + * This should never happen, because NuLib2 verifies the + * presence of the files. (If you want to test this out, + * you have to "sabotage" AddFile, or remove a file from disk + * while NuFlush is running.) + */ + assert(0); + /*result = HandleAddNotFound(pState, pArchive, pErrorStatus);*/ + } + } else if (pErrorStatus->operation == kNuOpTest) { + if (pErrorStatus->err == kNuErrBadMHCRC || + pErrorStatus->err == kNuErrBadRHCRC || + pErrorStatus->err == kNuErrBadThreadCRC || + pErrorStatus->err == kNuErrBadDataCRC) + { + result = HandleBadCRC(pState, pArchive, pErrorStatus); + } + } + + return result; +} + + +#if 0 +/* + * Display an error message. + * + * (This was just a test to see if it worked... NufxLib's default behavior + * is fine for NuLib2.) + */ +NuResult +ErrorMessageHandler(NuArchive* pArchive, void* vErrorMessage) +{ + const NuErrorMessage* pErrorMessage = (const NuErrorMessage*) vErrorMessage; + + fprintf(stderr, "%s%d %3d %s:%d %s %s\n", + pArchive == nil ? "(GLOBAL)" : "", + pErrorMessage->isDebug, pErrorMessage->err, pErrorMessage->file, + pErrorMessage->line, pErrorMessage->function, pErrorMessage->message); + return kNuOK; +} +#endif + + +/* + * =========================================================================== + * Open an archive + * =========================================================================== + */ + +/* an archive name of "-" indicates we want to use stdin */ +static const char* kStdinArchive = "-"; + + +/* + * Determine whether the access bits on the record make it a read-only + * file or not. + * + * Uses a simplified view of the access flags. + */ +Boolean +IsRecordReadOnly(const NuRecord* pRecord) +{ + if (pRecord->recAccess == 0x21L || pRecord->recAccess == 0x01L) + return true; + else + return false; +} + + +/* + * Returns "true" if "archiveName" is the name we use to represent stdin. + */ +Boolean +IsFilenameStdin(const char* archiveName) +{ + assert(archiveName != nil); + return (strcmp(archiveName, kStdinArchive) == 0); +} + + +#define BailError(err) { if (err != kNuErrNone) goto bail; } + +/* + * Open the archive in read-only mode. We use "file mode" for a file, or + * "streaming mode" for stdin. + */ +NuError +OpenArchiveReadOnly(NulibState* pState) +{ + NuError err; + NuArchive* pArchive; + + assert(pState != nil); + + if (IsFilenameStdin(NState_GetArchiveFilename(pState))) { + err = NuStreamOpenRO(stdin, &pArchive); + if (err != kNuErrNone) { + ReportError(err, "unable to open create stdin archive"); + goto bail; + } + /* + * 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 = NuOpenRO(NState_GetArchiveFilename(pState), &pArchive); + if (err != kNuErrNone) { + ReportError(err, "unable to open '%s'", + NState_GetArchiveFilename(pState)); + goto bail; + } + } + + /* introduce them */ + NState_SetNuArchive(pState, pArchive); + err = NuSetExtraData(pArchive, pState); + + err = NuSetSelectionFilter(pArchive, SelectionFilter); + err = NuSetOutputPathnameFilter(pArchive, OutputPathnameFilter); + err = NuSetProgressUpdater(pArchive, ProgressUpdater); + err = NuSetErrorHandler(pArchive, ErrorHandler); + /*err = NuSetErrorMessageHandler(pArchive, ErrorMessageHandler);*/ + + /* set the EOL conversion */ + if (NState_GetModConvertAll(pState)) + err = NuSetValue(pArchive, kNuValueConvertExtractedEOL, kNuConvertOn); + else if (NState_GetModConvertText(pState)) + err = NuSetValue(pArchive, kNuValueConvertExtractedEOL, kNuConvertAuto); + else + err = NuSetValue(pArchive, kNuValueConvertExtractedEOL, kNuConvertOff); + BailError(err); + + /* handle "-s" flag */ + if (NState_GetModOverwriteExisting(pState)) { + err = NuSetValue(pArchive, kNuValueHandleExisting, kNuAlwaysOverwrite); + BailError(err); + } + + /* handle "-f" and "-u" flags (this overrides "-s" during extraction) */ + if (NState_GetModFreshen(pState) || NState_GetModUpdate(pState)) { + err = NuSetValue(pArchive, kNuValueOnlyUpdateOlder, true); + BailError(err); + } + if (NState_GetModFreshen(pState)) { + err = NuSetValue(pArchive, kNuValueHandleExisting, kNuMustOverwrite); + BailError(err); + } + + DBUG(("--- enabling ShrinkIt compatibility mode\n")); + err = NuSetValue(pArchive, kNuValueMimicSHK, true); + BailError(err); + + if (strcmp(SYSTEM_DEFAULT_EOL, "\r") == 0) + err = NuSetValue(pArchive, kNuValueEOL, kNuEOLCR); + else if (strcmp(SYSTEM_DEFAULT_EOL, "\n") == 0) + err = NuSetValue(pArchive, kNuValueEOL, kNuEOLLF); + else if (strcmp(SYSTEM_DEFAULT_EOL, "\r\n") == 0) + err = NuSetValue(pArchive, kNuValueEOL, kNuEOLCRLF); + else { + assert(0); + err = kNuErrInternal; + ReportError(err, "Unknown SYSTEM_DEFAULT_EOL '%s'", SYSTEM_DEFAULT_EOL); + goto bail; + } + BailError(err); + +bail: + return err; +} + + +/* + * Open the archive in read-write mode, for purposes of adding, deleting, + * or updating files. We don't plan on extracting anything with this. + * + * "Streaming mode" isn't allowed. + */ +NuError +OpenArchiveReadWrite(NulibState* pState) +{ + NuError err = kNuErrNone; + NuArchive* pArchive; + char* tempName = nil; + + assert(pState != nil); + assert(IsFilenameStdin(NState_GetArchiveFilename(pState)) == false); + + tempName = MakeTempArchiveName(pState); + if (tempName == nil) + goto bail; + DBUG(("TEMP NAME = '%s'\n", tempName)); + + err = NuOpenRW(NState_GetArchiveFilename(pState), tempName, + kNuOpenCreat, &pArchive); + if (err != kNuErrNone) { + ReportError(err, "unable to open '%s'", + NState_GetArchiveFilename(pState)); + goto bail; + } + + /* introduce them */ + NState_SetNuArchive(pState, pArchive); + err = NuSetExtraData(pArchive, pState); + BailError(err); + + err = NuSetSelectionFilter(pArchive, SelectionFilter); + BailError(err) + err = NuSetProgressUpdater(pArchive, ProgressUpdater); + BailError(err) + err = NuSetErrorHandler(pArchive, ErrorHandler); + BailError(err) + /*err = NuSetErrorMessageHandler(pArchive, ErrorMessageHandler);*/ + + /* handle "-0" flag */ + if (NState_GetModNoCompression(pState)) { + err = NuSetValue(pArchive, kNuValueDataCompression, kNuCompressNone); + BailError(err); + } + + /* handle "-f" and "-u" flags */ + /* (BUG: if "-f" is set, creating a new archive is impossible) */ + if (NState_GetModFreshen(pState) || NState_GetModUpdate(pState)) { + err = NuSetValue(pArchive, kNuValueOnlyUpdateOlder, true); + BailError(err); + } + if (NState_GetModFreshen(pState)) { + err = NuSetValue(pArchive, kNuValueHandleExisting, kNuMustOverwrite); + BailError(err); + } + + DBUG(("--- enabling ShrinkIt compatibility mode\n")); + err = NuSetValue(pArchive, kNuValueMimicSHK, true); + BailError(err); + + /* this probably isn't needed here, but set it anyway */ + if (strcmp(SYSTEM_DEFAULT_EOL, "\r") == 0) + err = NuSetValue(pArchive, kNuValueEOL, kNuEOLCR); + else if (strcmp(SYSTEM_DEFAULT_EOL, "\n") == 0) + err = NuSetValue(pArchive, kNuValueEOL, kNuEOLLF); + else if (strcmp(SYSTEM_DEFAULT_EOL, "\r\n") == 0) + err = NuSetValue(pArchive, kNuValueEOL, kNuEOLCRLF); + else { + assert(0); + err = kNuErrInternal; + ReportError(err, "Unknown SYSTEM_DEFAULT_EOL '%s'", SYSTEM_DEFAULT_EOL); + goto bail; + } + BailError(err); + + /*(void) NuSetValue(pArchive, kNuValueAllowDuplicates, true);*/ + +bail: + Free(tempName); + return err; +} + diff --git a/nulib2/COPYING b/nulib2/COPYING new file mode 100644 index 0000000..60549be --- /dev/null +++ b/nulib2/COPYING @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) 19yy + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) 19yy name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. diff --git a/nulib2/ChangeLog.txt b/nulib2/ChangeLog.txt new file mode 100644 index 0000000..2a550e2 --- /dev/null +++ b/nulib2/ChangeLog.txt @@ -0,0 +1,123 @@ +2000/05/18 ***** v1.0.0 shipped ***** + +2000/05/18 fadden + - added nulib2 to set of things stripped by "distbin" + - updated version information to indicate final release + +2000/03/25 ***** v0.6.1 shipped ***** + +2000/03/25 fadden + - Sheppy says Mac OS X PPC v1.02 and v1.2 work with minor SysDefs tweak + +2000/03/05 ***** v0.6.0 (beta) shipped ***** + +2000/03/05 fadden + - don't call mktemp(), just pass template into NuOpenRW + - removed DEBUG_MSGS from default CFLAGS + - updated version information to indicate beta release + +2000/02/24 ***** v0.5.1 shipped ***** + +2000/02/20 changes from Scott Blackman + - portability fixes for DJGPP under Win95 + +2000/02/17 changes from Devin Reade + - portability fixes for BSD, AIX, and others + - added "distbin" target + +2000/02/09 ***** v0.5.0 (alpha) shipped ***** + +2000/02/09 fadden + - changed the comparison used when extracting/deleting a list of files + from strcasecmp to strcmp, since NufxLib does case-sensitive compares. + - fixed the percentage for files and archives larger than 21MB + +2000/02/08 fadden + - tweaked the BeOS/PPC config around a little + - deleted some commas to make "gcc -pedantic" happy + - changed version to x.y.z format here too + - generalized the "aux" handling to include all MS-DOS device names + +2000/02/06 fadden + - include @CFLAGS@ in case somebody wants to override them + +2000/02/06 ***** v0.4b shipped ***** + +2000/02/06 fadden + - added "install-shared" make target + - portability fixes for HP/UX + +2000/02/06 ***** v0.4a shipped ***** + +2000/02/06 fadden + - massaged configure.in for BeOS, and added some type casts for mwerks + +2000/02/06 ***** v0.4 shipped ***** + +2000/02/05 fadden + - added "mkinstalldirs" to install target + - added Win32 makefile + - made a few implicit typecasts explicit for Visual C++'s benefit + - change "aux" to "_aux", because FAT filesystems choke on it + +2000/02/04 fadden + - added Win32 recursive directory descent + +2000/02/02 fadden + - minor changes to get it working under Win32 (Visual C++ 6.0) + - added --enable-dmalloc to configuration + +2000/02/01 fadden + - screen out leading "./", and junk the path if ".." shows up in path + - don't try to add comments to records we're skipping + - set kNuValueEOL appropriately for the current system + +2000/01/29 ***** v0.3 shipped ***** + +2000/01/29 fadden + - added "make install" target, with the standard autoconf defines + - added some examples to the man page + +2000/01/28 fadden + - merged "Kind" and "Type" columns in "v" output + - display a '+' when doing EOL conversions on an extracted file + +2000/01/26 fadden + - added UI for allowing the user to ignore bad CRCs + - implemented "-j" (junk paths) for add and extract + - implemented "-c" (comments) for add and extract + - added totals to bottom of "v" output + +2000/01/25 fadden + - when extracting without type preservation, append "_rsrc_" to + resource forks + +2000/01/24 fadden + - added support for "-k" (add as disk image) flag + +2000/01/24 ***** v0.2 shipped ***** + +2000/01/22 fadden + - added support for "-u" (update) and "-f" (freshen) flags + - set file dates in AddFile call + +2000/01/20 fadden + - restructed the progress updater + +2000/01/19 fadden + - normalized SysDefs.h, changing UNIX to UNIX_LIKE and defining for BeOS + - added "shared" target to makefile + - added BeOS stuff to autoconf setup + +2000/01/17 fadden + - started recording locked/unlocked status + - some BeOS/Metrowerks "it's not gcc" changes from Eric Shepherd + - implemented "-s" (stomp existing) and "-0" (no compression) modifiers + +2000/01/17 ***** v0.1 shipped ***** + +(much time passes) + +mid-1998 fadden + - work begins + diff --git a/nulib2/Delete.c b/nulib2/Delete.c new file mode 100644 index 0000000..1dfa9c1 --- /dev/null +++ b/nulib2/Delete.c @@ -0,0 +1,46 @@ +/* + * 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. + * + * Delete files from the archive. + */ +#include "Nulib2.h" + + +/* + * Delete the specified files. + * + * This uses the "bulk" delete call, allowing the SelectionFilter callback + * to do the matching against specified filenames. + */ +NuError +DoDelete(NulibState* pState) +{ + NuError err; + NuArchive* pArchive = nil; + + assert(pState != nil); + + err = OpenArchiveReadWrite(pState); + if (err != kNuErrNone) + goto bail; + pArchive = NState_GetNuArchive(pState); + assert(pArchive != nil); + + NState_SetMatchCount(pState, 0); + + err = NuDelete(pArchive); + if (err != kNuErrNone) + goto bail; + + if (!NState_GetMatchCount(pState)) + printf("%s: no records matched\n", gProgName); + +bail: + if (pArchive != nil) + (void) NuClose(pArchive); + return err; +} + diff --git a/nulib2/Extract.c b/nulib2/Extract.c new file mode 100644 index 0000000..7fdfbc8 --- /dev/null +++ b/nulib2/Extract.c @@ -0,0 +1,172 @@ +/* + * 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. + * + * Extract files and test archives. + */ +#include "Nulib2.h" + + +/* + * Extract all of the records from the archive, pulling out and displaying + * comment threads. + * + * The "bulk extract" call doesn't deal with comments. Since we want to + * show them while we're extracting the files, we have to manually find + * and extract them. + */ +static NuError +ExtractAllRecords(NulibState* pState, NuArchive* pArchive) +{ + NuError err; + const NuRecord* pRecord; + const NuThread* pThread; + NuRecordIdx recordIdx; + NuAttr numRecords; + int idx, threadIdx; + + DBUG(("--- doing manual extract\n")); + assert(NState_GetCommand(pState) == kCommandExtract); /* no "-p" here */ + + err = NuGetAttr(pArchive, kNuAttrNumRecords, &numRecords); + for (idx = 0; idx < (int) numRecords; idx++) { + err = NuGetRecordIdxByPosition(pArchive, idx, &recordIdx); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: couldn't get record #%d (err=%d)\n", + idx, err); + goto bail; + } + + err = NuGetRecord(pArchive, recordIdx, &pRecord); + if (err != kNuErrNone) { + fprintf(stderr, "ERROR: unable to get recordIdx %ld\n", recordIdx); + goto bail; + } + + /* do we want to extract this record? */ + if (!IsSpecified(pState, pRecord)) + continue; + NState_IncMatchCount(pState); + + /* + * Look for a comment thread. + */ + for (threadIdx = 0; (ulong)threadIdx < pRecord->recTotalThreads; + threadIdx++) + { + pThread = NuGetThread(pRecord, threadIdx); + assert(pThread != nil); + + if (NuGetThreadID(pThread) == kNuThreadIDComment && + pThread->actualThreadEOF > 0) + { + printf("----- '%s':\n", pRecord->filename); + err = NuExtractThread(pArchive, pThread->threadIdx, + NState_GetCommentSink(pState)); + if (err != kNuErrNone) { + printf("[comment extraction failed, continuing\n"); + } else { + printf("\n-----\n"); + } + } + } + + /* extract the record, using the usual mechanisms */ + err = NuExtractRecord(pArchive, recordIdx); + if (err != kNuErrNone) + goto bail; + } + +bail: + return err; +} + + +/* + * Extract the specified files. + */ +NuError +DoExtract(NulibState* pState) +{ + NuError err; + NuArchive* pArchive = nil; + + assert(pState != nil); + + err = OpenArchiveReadOnly(pState); + if (err != kNuErrNone) + goto bail; + pArchive = NState_GetNuArchive(pState); + assert(pArchive != nil); + + NState_SetMatchCount(pState, 0); + + /* + * If we're not interested in comments, just use the "bulk" extract + * call. If we want comments, we need to do this one at a time. + */ + if (!NState_GetModComments(pState)) { + err = NuExtract(pArchive); + if (err != kNuErrNone) + goto bail; + } else { + err = ExtractAllRecords(pState, pArchive); + if (err != kNuErrNone) + goto bail; + } + + if (!NState_GetMatchCount(pState)) + printf("%s: no records match\n", gProgName); + +bail: + if (pArchive != nil) + (void) NuClose(pArchive); + return err; +} + + +/* + * Extract the specified files to stdout. + */ +NuError +DoExtractToPipe(NulibState* pState) +{ + /* we handle the "to pipe" part farther down */ + return DoExtract(pState); +} + + +/* + * Do an integrity check on one or more records in the archive. + */ +NuError +DoTest(NulibState* pState) +{ + NuError err; + NuArchive* pArchive = nil; + + assert(pState != nil); + + err = OpenArchiveReadOnly(pState); + if (err != kNuErrNone) + goto bail; + pArchive = NState_GetNuArchive(pState); + assert(pArchive != nil); + + NState_SetMatchCount(pState, 0); + + err = NuTest(pArchive); + if (err != kNuErrNone) + goto bail; + + if (!NState_GetMatchCount(pState)) + printf("%s: no records match\n", gProgName); + +bail: + if (pArchive != nil) + (void) NuClose(pArchive); + return err; +} + diff --git a/nulib2/Filename.c b/nulib2/Filename.c new file mode 100644 index 0000000..d623e5a --- /dev/null +++ b/nulib2/Filename.c @@ -0,0 +1,660 @@ +/* + * 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. + * + * Filename manipulation, including file type preservation. + */ +#include "Nulib2.h" +#include + + +/* + * =========================================================================== + * Common definitions + * =========================================================================== + */ + +#define kPreserveIndic '#' /* use # rather than $ for hex indication */ +#define kFilenameExtDelim '.' /* separates extension from filename */ +#define kResourceFlag 'r' +#define kDiskImageFlag 'i' +#define kMaxExtLen 4 /* ".1234" */ +#define kResourceStr "_rsrc_" + +/* must be longer then strlen(kResourceStr)... no problem there */ +#define kMaxPathGrowth (sizeof("#XXXXXXXXYYYYYYYYZ")-1 + kMaxExtLen+1) + + +/* ProDOS file type names; must be entirely in upper case */ +static const char gFileTypeNames[256][4] = { + "NON", "BAD", "PCD", "PTX", "TXT", "PDA", "BIN", "FNT", + "FOT", "BA3", "DA3", "WPF", "SOS", "$0D", "$0E", "DIR", + "RPD", "RPI", "AFD", "AFM", "AFR", "SCL", "PFS", "$17", + "$18", "ADB", "AWP", "ASP", "$1C", "$1D", "$1E", "$1F", + "TDM", "$21", "$22", "$23", "$24", "$25", "$26", "$27", + "$28", "$29", "8SC", "8OB", "8IC", "8LD", "P8C", "$2F", + "$30", "$31", "$32", "$33", "$34", "$35", "$36", "$37", + "$38", "$39", "$3A", "$3B", "$3C", "$3D", "$3E", "$3F", + "DIC", "OCR", "FTD", "$43", "$44", "$45", "$46", "$47", + "$48", "$49", "$4A", "$4B", "$4C", "$4D", "$4E", "$4F", + "GWP", "GSS", "GDB", "DRW", "GDP", "HMD", "EDU", "STN", + "HLP", "COM", "CFG", "ANM", "MUM", "ENT", "DVU", "FIN", + "$60", "$61", "$62", "$63", "$64", "$65", "$66", "$67", + "$68", "$69", "$6A", "BIO", "$6C", "TDR", "PRE", "HDV", + "$70", "$71", "$72", "$73", "$74", "$75", "$76", "$77", + "$78", "$79", "$7A", "$7B", "$7C", "$7D", "$7E", "$7F", + "$80", "$81", "$82", "$83", "$84", "$85", "$86", "$87", + "$88", "$89", "$8A", "$8B", "$8C", "$8D", "$8E", "$8F", + "$90", "$91", "$92", "$93", "$94", "$95", "$96", "$97", + "$98", "$99", "$9A", "$9B", "$9C", "$9D", "$9E", "$9F", + "WP ", "$A1", "$A2", "$A3", "$A4", "$A5", "$A6", "$A7", + "$A8", "$A9", "$AA", "GSB", "TDF", "BDF", "$AE", "$AF", + "SRC", "OBJ", "LIB", "S16", "RTL", "EXE", "PIF", "TIF", + "NDA", "CDA", "TOL", "DVR", "LDF", "FST", "$BE", "DOC", + "PNT", "PIC", "ANI", "PAL", "$C4", "OOG", "SCR", "CDV", + "FON", "FND", "ICN", "$CB", "$CC", "$CD", "$CE", "$CF", + "$D0", "$D1", "$D2", "$D3", "$D4", "MUS", "INS", "MDI", + "SND", "$D9", "$DA", "DBM", "$DC", "DDD", "$DE", "$DF", + "LBR", "$E1", "ATK", "$E3", "$E4", "$E5", "$E6", "$E7", + "$E8", "$E9", "$EA", "$EB", "$EC", "$ED", "R16", "PAS", + "CMD", "$F1", "$F2", "$F3", "$F4", "$F5", "$F6", "$F7", + "$F8", "OS ", "INT", "IVR", "BAS", "VAR", "REL", "SYS" +}; + +/* + * Some file extensions we recognize. When adding files with "extended" + * preservation mode, we try to assign types to files that weren't + * explicitly preserved, but nevertheless have a recognizeable type. + * + * geoff@gwlink.net points out that this really ought to be in an external + * file rather than a hard-coded table. Ought to fix that someday. + */ +static const struct { + const char* label; + ushort fileType; + ulong auxType; + uchar flags; +} gRecognizedExtensions[] = { + { "ASM", 0xb0, 0x0003, 0 }, /* APW assembly source */ + { "C", 0xb0, 0x000a, 0 }, /* APW C source */ + { "H", 0xb0, 0x000a, 0 }, /* APW C header */ + { "BNY", 0xe0, 0x8000, 0 }, /* Binary II lib */ + { "BQY", 0xe0, 0x8000, 0 }, /* Binary II lib, w/ compress */ + { "BXY", 0xe0, 0x8000, 0 }, /* Binary II wrap around SHK */ + { "BSE", 0xe0, 0x8000, 0 }, /* Binary II wrap around SEA */ + { "SEA", 0xb3, 0xdb07, 0 }, /* GSHK SEA */ + { "GIF", 0xc0, 0x8006, 0 }, /* GIF image */ + { "JPG", 0x06, 0x0000, 0 }, /* JPEG (nicer than 'NON') */ + { "JPEG", 0x06, 0x0000, 0 }, /* JPEG (nicer than 'NON') */ + { "SHK", 0xe0, 0x8002, 0 }, /* ShrinkIt archive */ +}; + + +/* + * Return a pointer to the three-letter representation of the file type name. + */ +const char* +GetFileTypeString(ulong fileType) +{ + if (fileType < NELEM(gFileTypeNames)) + return gFileTypeNames[fileType]; + else + return "???"; +} + + +/* + * =========================================================================== + * File type preservation + * =========================================================================== + */ + +/* + * Add a preservation string. + * + * "pathBuf" is assumed to have enough space to hold the current path + * plus kMaxPathGrowth more. It will be modified in place. + */ +static void +AddPreservationString(NulibState* pState, + const NuPathnameProposal* pPathProposal, char* pathBuf) +{ + char extBuf[kMaxPathGrowth +1]; + const NuRecord* pRecord; + const NuThread* pThread; + NuThreadID threadID; + char* cp; + + assert(pState != nil); + assert(pPathProposal != nil); + assert(pathBuf != nil); + assert(NState_GetModPreserveType(pState)); + + pRecord = pPathProposal->pRecord; + pThread = pPathProposal->pThread; + assert(pRecord != nil); + assert(pThread != nil); + + cp = extBuf; + + /* + * Cons up a preservation string. On some platforms "sprintf" doesn't + * return the #of characters written, so we add it up manually. + */ + if (pRecord->recFileType < 0x100 && pRecord->recExtraType < 0x10000) { + sprintf(cp, "%c%02lx%04lx", kPreserveIndic, pRecord->recFileType, + pRecord->recExtraType); + cp += 7; + } else { + sprintf(cp, "%c%08lx%08lx", kPreserveIndic, pRecord->recFileType, + pRecord->recExtraType); + cp += 17; + } + + threadID = NuMakeThreadID(pThread->thThreadClass, pThread->thThreadKind); + if (threadID == kNuThreadIDRsrcFork) + *cp++ = kResourceFlag; + else if (threadID == kNuThreadIDDiskImage) + *cp++ = kDiskImageFlag; + + /* + * If they've asked for "extended" type preservation, then we need + * to retain either the existing extension or append an extension + * based on the ProDOS file type. + */ + if (NState_GetModPreserveTypeExtended(pState)) { + const char* pExt; + char* end; + + /* + * Find extension. Note FindExtension guarantees there's at least + * one char after '.'. + * + * It's hard to know when this is right and when it isn't. It's + * fairly likely that a text file really ought to end in ".txt", + * and it's fairly unlikely that a BIN file should be ".bin", but + * where do the rest fall in? We might want to force TXT files + * to be ".txt", and perhaps do something clever for some others. + */ + if (pRecord->recFileType == 0x04) + pExt = nil; + else + pExt = FindExtension(pState, pathBuf); + if (pExt != nil && strlen(pExt+1) <= kMaxExtLen) { + pExt++; /* skip past the '.' */ + + /* if it's strictly decimal-numeric, don't use it (.1, .2, etc) */ + (void) strtoul(pExt, &end, 10); + if (*end == '\0') { + pExt = nil; + } else { + /* if '#' appears in it, don't use it -- it'll confuse us */ + const char* ccp = pExt; + while (*ccp != '\0') { + if (*ccp == '#') { + pExt = nil; + break; + } + ccp++; + } + } + + } else { + /* + * There's no extension on the filename. Use the standard + * ProDOS type, if one exists for this entry. We don't use + * the table if it's "NON" or a hex value. + */ + if (pRecord->recFileType) { + pExt = GetFileTypeString(pRecord->recFileType); + if (pExt[0] == '?' || pExt[0] == '$') + pExt = nil; + } + } + + if (pExt != nil) { + *cp++ = kFilenameExtDelim; + strcpy(cp, pExt); + cp += strlen(pExt); + } + } + + /* make sure it's terminated */ + *cp = '\0'; + + assert(cp - extBuf <= kMaxPathGrowth); + strcat(pathBuf, extBuf); +} + +/* + * Normalize a path for the conventions on the output filesystem. This + * adds optional file type preservation. + * + * The path from the archive is in "pPathProposal". Thew new pathname + * will be placed in the "new pathname" section of "pPathProposal". + * + * The new pathname may be shorter (because characters were removed) or + * longer (if we add a "#XXYYYYZ" extension or replace chars with '%' codes). + * + * This returns the new pathname, which is held in NState's temporary + * pathname buffer. + */ +const char* +NormalizePath(NulibState* pState, NuPathnameProposal* pPathProposal) +{ + NuError err = kNuErrNone; + char* pathBuf; + const char* startp; + const char* endp; + char* dstp; + char localFssep; + + assert(pState != nil); + assert(pPathProposal != nil); + assert(pPathProposal->pathname != nil); + + localFssep = NState_GetSystemPathSeparator(pState); + + /* + * Set up temporary buffer space. The maximum possible expansion + * requires converting all chars to '%' codes and adding the longest + * possible preservation string. + */ + NState_SetTempPathnameLen(pState, + strlen(pPathProposal->pathname)*3 + kMaxPathGrowth +1); + pathBuf = NState_GetTempPathnameBuf(pState); + assert(pathBuf != nil); + if (pathBuf == nil) + return nil; + + startp = pPathProposal->pathname; + dstp = pathBuf; + while (*startp == pPathProposal->filenameSeparator) { + /* ignore leading path sep; always extract to current dir */ + startp++; + } + + /* normalize all directory components and the filename component */ + while (startp != nil) { + endp = strchr(startp, pPathProposal->filenameSeparator); + if (endp != nil) { + /* normalize directory component */ + err = NormalizeDirectoryName(pState, startp, endp - startp, + pPathProposal->filenameSeparator, &dstp, + NState_GetTempPathnameLen(pState)); + if (err != kNuErrNone) + goto bail; + + *dstp++ = localFssep; + + startp = endp +1; + } else { + /* normalize filename */ + err = NormalizeFileName(pState, startp, strlen(startp), + pPathProposal->filenameSeparator, &dstp, + NState_GetTempPathnameLen(pState)); + if (err != kNuErrNone) + goto bail; + + /* add/replace extension if necessary */ + *dstp++ = '\0'; + if (NState_GetModPreserveType(pState)) { + AddPreservationString(pState, pPathProposal, pathBuf); + } else if (NuGetThreadID(pPathProposal->pThread) == kNuThreadIDRsrcFork) + { + /* add this in lieu of the preservation extension */ + strcat(pathBuf, kResourceStr); + } + + startp = nil; /* we're done */ + } + } + + pPathProposal->newPathname = pathBuf; + pPathProposal->newFilenameSeparator = localFssep; + + /* check for overflow */ + assert(dstp - pathBuf <= + (int)(strlen(pPathProposal->pathname) + kMaxPathGrowth)); + + /* + * If "junk paths" is set, drop everything but the last component. + */ + if (NState_GetModJunkPaths(pState)) { + char* lastFssep; + lastFssep = strrchr(pathBuf, localFssep); + if (lastFssep != nil) { + assert(*(lastFssep+1) != '\0'); /* should already have been caught*/ + memmove(pathBuf, lastFssep+1, strlen(lastFssep+1)+1); + } + } + +bail: + if (err != kNuErrNone) + return nil; + return pathBuf; +} + + +/* + * =========================================================================== + * File type restoration + * =========================================================================== + */ + +/* + * Try to figure out what file type is associated with a filename extension. + * + * This checks the standard list of ProDOS types (which should catch things + * like "TXT" and "BIN") and the separate list of recognized extensions. + */ +static void +LookupExtension(NulibState* pState, const char* ext, ulong* pFileType, + ulong* pAuxType) +{ + char uext3[4]; + int i, extLen; + + extLen = strlen(ext); + assert(extLen > 0); + + /* + * First step is to try to find it in the recognized types list. + */ + for (i = 0; i < NELEM(gRecognizedExtensions); i++) { + if (strcasecmp(ext, gRecognizedExtensions[i].label) == 0) { + *pFileType = gRecognizedExtensions[i].fileType; + *pAuxType = gRecognizedExtensions[i].auxType; + goto bail; + } + } + + /* + * Second step is to try to find it in the ProDOS types list. + * + * The extension is converted to upper case and padded with spaces. + * + * [do we want to obstruct matching on things like '$f7' here?] + */ + if (extLen <= 3) { + for (i = 2; i >= extLen; i--) + uext3[i] = ' '; + for ( ; i >= 0; i--) + uext3[i] = toupper(ext[i]); + uext3[3] = '\0'; + + /*printf("### converted '%s' to '%s'\n", ext, uext3);*/ + + for (i = 0; i < NELEM(gFileTypeNames); i++) { + if (strcmp(uext3, gFileTypeNames[i]) == 0) { + *pFileType = i; + goto bail; + } + } + } + +bail: + return; +} + +/* + * Try to associate some meaning with the file extension. + */ +void +InterpretExtension(NulibState* pState, const char* pathName, ulong* pFileType, + ulong* pAuxType) +{ + const char* pExt; + + assert(pState != nil); + assert(pathName != nil); + assert(pFileType != nil); + assert(pAuxType != nil); + + pExt = FindExtension(pState, pathName); + if (pExt != nil) + LookupExtension(pState, pExt+1, pFileType, pAuxType); +} + + +/* + * Check to see if there's a preservation string on the filename. If so, + * set the filetype and auxtype information, and trim the preservation + * string off. + * + * We have to be careful not to trip on false-positive occurrences of '#' + * in the filename. + */ +Boolean +ExtractPreservationString(NulibState* pState, char* pathname, ulong* pFileType, + ulong* pAuxType, NuThreadID* pThreadID) +{ + char numBuf[9]; + ulong fileType, auxType; + NuThreadID threadID; + char* pPreserve; + char* cp; + int digitCount; + + assert(pState != nil); + assert(pathname != nil); + assert(pFileType != nil); + assert(pAuxType != nil); + assert(pThreadID != nil); + + pPreserve = strrchr(pathname, kPreserveIndic); + if (pPreserve == nil) + return false; + + /* count up the #of hex digits */ + digitCount = 0; + for (cp = pPreserve+1; *cp != '\0' && isxdigit((int)*cp); cp++) + digitCount++; + + /* extract the file and aux type */ + switch (digitCount) { + case 6: + /* ProDOS 1-byte type and 2-byte aux */ + memcpy(numBuf, pPreserve+1, 2); + numBuf[2] = 0; + fileType = strtoul(numBuf, &cp, 16); + assert(cp == numBuf + 2); + + auxType = strtoul(pPreserve+3, &cp, 16); + assert(cp == pPreserve + 7); + break; + case 16: + /* HFS 4-byte type and 4-byte creator */ + memcpy(numBuf, pPreserve+1, 8); + numBuf[8] = 0; + fileType = strtoul(numBuf, &cp, 16); + assert(cp == numBuf + 8); + + auxType = strtoul(pPreserve+9, &cp, 16); + assert(cp == pPreserve + 17); + break; + default: + /* not valid */ + return false; + } + + /* check for a threadID specifier */ + threadID = kNuThreadIDDataFork; + switch (*cp) { + case kResourceFlag: + threadID = kNuThreadIDRsrcFork; + cp++; + break; + case kDiskImageFlag: + threadID = kNuThreadIDDiskImage; + cp++; + break; + default: + /* do nothing... yet */ + break; + } + + /* make sure we were the very last component */ + switch (*cp) { + case kFilenameExtDelim: /* redundant "-ee" extension */ + case '\0': /* end of string! */ + break; + default: + return false; + } + + /* truncate the original string, and return what we got */ + *pPreserve = '\0'; + *pFileType = fileType; + *pAuxType = auxType; + *pThreadID = threadID; + + return true; +} + + +/* + * Remove NuLib2's normalization magic (e.g. "%2f" for '/'). + * + * This always results in the filename staying the same length or getting + * smaller, so we can do it in place in the buffer. + */ +void +DenormalizePath(NulibState* pState, char* pathBuf) +{ + const char* srcp; + char* dstp; + char ch; + + srcp = pathBuf; + dstp = pathBuf; + + while (*srcp != '\0') { + if (*srcp == kForeignIndic) { + srcp++; + if (*srcp == kForeignIndic) { + *dstp++ = kForeignIndic; + srcp++; + } else if (isxdigit((int)*srcp)) { + ch = HexDigit(*srcp) << 4; + srcp++; + if (isxdigit((int)*srcp)) { + /* valid, output char */ + ch += HexDigit(*srcp); + *dstp++ = ch; + srcp++; + } else { + /* bogus '%' with trailing hex digit found! */ + *dstp++ = kForeignIndic; + *dstp++ = *(srcp-1); + } + } else { + /* bogus lone '%s' found! */ + *dstp++ = kForeignIndic; + } + + } else { + *dstp++ = *srcp++; + } + } + + *dstp = '\0'; + assert(dstp <= srcp); +} + + +/* + * =========================================================================== + * Misc utils + * =========================================================================== + */ + +/* + * Find the filename component of a local pathname. Uses the fssep defined + * in pState. + * + * Always returns a pointer to a string; never returns nil. + */ +const char* +FilenameOnly(NulibState* pState, const char* pathname) +{ + const char* retstr; + const char* pSlash; + char* tmpStr = nil; + + assert(pState != nil); + assert(pathname != nil); + + pSlash = strrchr(pathname, NState_GetSystemPathSeparator(pState)); + if (pSlash == nil) { + retstr = pathname; /* whole thing is the filename */ + goto bail; + } + + pSlash++; + if (*pSlash == '\0') { + if (strlen(pathname) < 2) { + retstr = pathname; /* the pathname is just "/"? Whatever */ + goto bail; + } + + /* some bonehead put an fssep on the very end; back up before it */ + /* (not efficient, but this should be rare, and I'm feeling lazy) */ + tmpStr = strdup(pathname); + tmpStr[strlen(pathname)-1] = '\0'; + pSlash = strrchr(tmpStr, NState_GetSystemPathSeparator(pState)); + + if (pSlash == nil) { + retstr = pathname; /* just a filename with a '/' after it */ + goto bail; + } + + pSlash++; + if (*pSlash == '\0') { + retstr = pathname; /* I give up! */ + goto bail; + } + + retstr = pathname + (pSlash - tmpStr); + + } else { + retstr = pSlash; + } + +bail: + Free(tmpStr); + return retstr; +} + +/* + * Return the filename extension found in a full pathname. + * + * An extension is the stuff following the last '.' in the filename. If + * there is nothing following the last '.', then there is no extension. + * + * Returns a pointer to the '.' preceding the extension, or nil if no + * extension was found. + */ +const char* +FindExtension(NulibState* pState, const char* pathname) +{ + const char* pFilename; + const char* pExt; + + /* + * We have to isolate the filename so that we don't get excited + * about "/foo.bar/file". + */ + pFilename = FilenameOnly(pState, pathname); + assert(pFilename != nil); + pExt = strrchr(pFilename, kFilenameExtDelim); + + /* also check for "/blah/foo.", which doesn't count */ + if (pExt != nil && *(pExt+1) != '\0') + return pExt; + + return nil; +} + diff --git a/nulib2/INSTALL b/nulib2/INSTALL new file mode 100644 index 0000000..50dbe43 --- /dev/null +++ b/nulib2/INSTALL @@ -0,0 +1,183 @@ +Basic Installation +================== + + These are generic installation instructions. + + The `configure' shell script attempts to guess correct values for +various system-dependent variables used during compilation. It uses +those values to create a `Makefile' in each directory of the package. +It may also create one or more `.h' files containing system-dependent +definitions. Finally, it creates a shell script `config.status' that +you can run in the future to recreate the current configuration, a file +`config.cache' that saves the results of its tests to speed up +reconfiguring, and a file `config.log' containing compiler output +(useful mainly for debugging `configure'). + + If you need to do unusual things to compile the package, please try +to figure out how `configure' could check whether to do them, and mail +diffs or instructions to the address given in the `README' so they can +be considered for the next release. If at some point `config.cache' +contains results you don't want to keep, you may remove or edit it. + + The file `configure.in' is used to create `configure' by a program +called `autoconf'. You only need `configure.in' if you want to change +it or regenerate `configure' using a newer version of `autoconf'. + +The simplest way to compile this package is: + + 1. `cd' to the directory containing the package's source code and type + `./configure' to configure the package for your system. If you're + using `csh' on an old version of System V, you might need to type + `sh ./configure' instead to prevent `csh' from trying to execute + `configure' itself. + + Running `configure' takes awhile. While running, it prints some + messages telling which features it is checking for. + + 2. Type `make' to compile the package. + + 3. Optionally, type `make check' to run any self-tests that come with + the package. + + 4. Type `make install' to install the programs and any data files and + documentation. + + 5. You can remove the program binaries and object files from the + source code directory by typing `make clean'. To also remove the + files that `configure' created (so you can compile the package for + a different kind of computer), type `make distclean'. There is + also a `make maintainer-clean' target, but that is intended mainly + for the package's developers. If you use it, you may have to get + all sorts of other programs in order to regenerate files that came + with the distribution. + +Compilers and Options +===================== + + Some systems require unusual options for compilation or linking that +the `configure' script does not know about. You can give `configure' +initial values for variables by setting them in the environment. Using +a Bourne-compatible shell, you can do that on the command line like +this: + CC=c89 CFLAGS=-O2 LIBS=-lposix ./configure + +Or on systems that have the `env' program, you can do it like this: + env CPPFLAGS=-I/usr/local/include LDFLAGS=-s ./configure + +Compiling For Multiple Architectures +==================================== + + You can compile the package for more than one kind of computer at the +same time, by placing the object files for each architecture in their +own directory. To do this, you must use a version of `make' that +supports the `VPATH' variable, such as GNU `make'. `cd' to the +directory where you want the object files and executables to go and run +the `configure' script. `configure' automatically checks for the +source code in the directory that `configure' is in and in `..'. + + If you have to use a `make' that does not supports the `VPATH' +variable, you have to compile the package for one architecture at a time +in the source code directory. After you have installed the package for +one architecture, use `make distclean' before reconfiguring for another +architecture. + +Installation Names +================== + + By default, `make install' will install the package's files in +`/usr/local/bin', `/usr/local/man', etc. You can specify an +installation prefix other than `/usr/local' by giving `configure' the +option `--prefix=PATH'. + + You can specify separate installation prefixes for +architecture-specific files and architecture-independent files. If you +give `configure' the option `--exec-prefix=PATH', the package will use +PATH as the prefix for installing programs and libraries. +Documentation and other data files will still use the regular prefix. + + In addition, if you use an unusual directory layout you can give +options like `--bindir=PATH' to specify different values for particular +kinds of files. Run `configure --help' for a list of the directories +you can set and what kinds of files go in them. + + If the package supports it, you can cause programs to be installed +with an extra prefix or suffix on their names by giving `configure' the +option `--program-prefix=PREFIX' or `--program-suffix=SUFFIX'. + +Optional Features +================= + + Some packages pay attention to `--enable-FEATURE' options to +`configure', where FEATURE indicates an optional part of the package. +They may also pay attention to `--with-PACKAGE' options, where PACKAGE +is something like `gnu-as' or `x' (for the X Window System). The +`README' should mention any `--enable-' and `--with-' options that the +package recognizes. + + For packages that use the X Window System, `configure' can usually +find the X include and library files automatically, but if it doesn't, +you can use the `configure' options `--x-includes=DIR' and +`--x-libraries=DIR' to specify their locations. + +Specifying the System Type +========================== + + There may be some features `configure' can not figure out +automatically, but needs to determine by the type of host the package +will run on. Usually `configure' can figure that out, but if it prints +a message saying it can not guess the host type, give it the +`--host=TYPE' option. TYPE can either be a short name for the system +type, such as `sun4', or a canonical name with three fields: + CPU-COMPANY-SYSTEM + +See the file `config.sub' for the possible values of each field. If +`config.sub' isn't included in this package, then this package doesn't +need to know the host type. + + If you are building compiler tools for cross-compiling, you can also +use the `--target=TYPE' option to select the type of system they will +produce code for and the `--build=TYPE' option to select the type of +system on which you are compiling the package. + +Sharing Defaults +================ + + If you want to set default values for `configure' scripts to share, +you can create a site shell script called `config.site' that gives +default values for variables like `CC', `cache_file', and `prefix'. +`configure' looks for `PREFIX/share/config.site' if it exists, then +`PREFIX/etc/config.site' if it exists. Or, you can set the +`CONFIG_SITE' environment variable to the location of the site script. +A warning: not all `configure' scripts look for a site script. + +Operation Controls +================== + + `configure' recognizes the following options to control how it +operates. + +`--cache-file=FILE' + Use and save the results of the tests in FILE instead of + `./config.cache'. Set FILE to `/dev/null' to disable caching, for + debugging `configure'. + +`--help' + Print a summary of the options to `configure', and exit. + +`--quiet' +`--silent' +`-q' + Do not print messages saying which checks are being made. To + suppress all normal output, redirect it to `/dev/null' (any error + messages will still be shown). + +`--srcdir=DIR' + Look for the package's source code in directory DIR. Usually + `configure' can determine that directory automatically. + +`--version' + Print the version of Autoconf used to generate the `configure' + script, and exit. + +`configure' also accepts some other, not widely useful, options. + diff --git a/nulib2/List.c b/nulib2/List.c new file mode 100644 index 0000000..619afe8 --- /dev/null +++ b/nulib2/List.c @@ -0,0 +1,420 @@ +/* + * 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. + * + * List files in the archive. + */ +#include "Nulib2.h" + + +/* kinds of records */ +enum RecordKind { + kRecordKindUnknown = 0, + kRecordKindDisk, + kRecordKindFile, + kRecordKindForkedFile +}; + +static const char* gShortFormatNames[] = { + "unc", "squ", "lz1", "lz2", "u12", "u16" +}; + + +#if 0 +/* days of the week */ +static const char* gDayNames[] = { + "[ null ]", + "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" +}; +#endif +/* months of the year */ +static const char* gMonths[] = { + "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" +}; + +#define kNuDateOutputLen 64 + + +/* + * Compute a percentage. + */ +int +ComputePercent(ulong totalSize, ulong size) +{ + int perc; + + if (!totalSize && !size) + return 100; /* file is zero bytes long */ + + if (totalSize < 21474836) + perc = (totalSize * 100) / size; + else + perc = totalSize / (size/100); + + /* don't say "0%" if it's not actually zero... it looks dumb */ + if (!perc && size) + perc = 1; + + return perc; +} + + +/* + * 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. + * + * Returns "buffer" for the benefit of printf() calls. + */ +static char* +FormatDateShort(const NuDateTime* pDateTime, char* buffer) +{ + /* is it valid? */ + if (pDateTime->day > 30 || pDateTime->month > 11 || pDateTime->hour > 24 || + pDateTime->minute > 59) + { + strcpy(buffer, " "); + goto bail; + } + + /* is it empty? */ + if ((pDateTime->second | pDateTime->minute | pDateTime->hour | + pDateTime->year | pDateTime->day | pDateTime->month | + pDateTime->extra | pDateTime->weekDay) == 0) + { + strcpy(buffer, " [No Date] "); + goto bail; + } + + sprintf(buffer, "%02d-%s-%02d %02d:%02d", + pDateTime->day+1, gMonths[pDateTime->month], pDateTime->year % 100, + pDateTime->hour, pDateTime->minute); + +bail: + return buffer; +} + + +/* + * NuStream callback function. Displays the filename. + */ +static NuResult +ShowContentsShort(NuArchive* pArchive, void* vpRecord) +{ + const NuRecord* pRecord = (NuRecord*) vpRecord; + NulibState* pState; + + assert(pArchive != nil); + (void) NuGetExtraData(pArchive, (void**) &pState); + assert(pState != nil); + + if (!IsSpecified(pState, pRecord)) + goto bail; + + printf("%s\n", + pRecord->filename == nil ? "":(const char*)pRecord->filename); + +bail: + return kNuOK; +} + + +/* + * Analyze the contents of a record to determine if it's a disk, file, + * or "other". Compute the total compressed and uncompressed lengths + * of all data threads. Return the "best" format. + * + * The "best format" and "record type" stuff assume that the entire + * record contains only a disk thread or a file thread, and that any + * format is interesting so long as it isn't "no compression". In + * general these will be true, because ShrinkIt and NuLib create files + * this way. + * + * You could, of course, create a single record with a data thread and + * a disk image thread, but it's a fair bet ShrinkIt would ignore one + * or the other. + */ +static NuError +AnalyzeRecord(const NuRecord* pRecord, enum RecordKind* pRecordKind, + ushort* pFormat, ulong* pTotalLen, ulong* pTotalCompLen) +{ + const NuThread* pThread; + NuThreadID threadID; + ulong idx; + + *pRecordKind = kRecordKindUnknown; + *pTotalLen = *pTotalCompLen = 0; + *pFormat = kNuThreadFormatUncompressed; + + for (idx = 0; idx < pRecord->recTotalThreads; idx++) { + pThread = NuGetThread(pRecord, idx); + assert(pThread != nil); + + if (pThread->thThreadClass == kNuThreadClassData) { + /* replace what's there if this might be more interesting */ + if (*pFormat == kNuThreadFormatUncompressed) + *pFormat = pThread->thThreadFormat; + + threadID = NuMakeThreadID(pThread->thThreadClass, + pThread->thThreadKind); + if (threadID == kNuThreadIDRsrcFork) + *pRecordKind = kRecordKindForkedFile; + else if (threadID == kNuThreadIDDiskImage) + *pRecordKind = kRecordKindDisk; + else if (threadID == kNuThreadIDDataFork && + *pRecordKind == kRecordKindUnknown) + *pRecordKind = kRecordKindFile; + + /* sum up, so we get both forks of forked files */ + *pTotalLen += pThread->actualThreadEOF; + *pTotalCompLen += pThread->thCompThreadEOF; + } + } + + return kNuErrNone; +} + +/* + * NuStream callback function. Displays the filename and several attributes. + * + * This is intended to mimic the output of some old version of ProDOS 8 + * ShrinkIt. + */ +static NuResult +ShowContentsVerbose(NuArchive* pArchive, void* vpRecord) +{ + NuError err = kNuErrNone; + const NuRecord* pRecord = (NuRecord*) vpRecord; + enum RecordKind recordKind; + ulong totalLen, totalCompLen; + ushort format; + NulibState* pState; + char date1[kNuDateOutputLen]; + char tmpbuf[16]; + int len; + + assert(pArchive != nil); + (void) NuGetExtraData(pArchive, (void**) &pState); + assert(pState != nil); + + if (!IsSpecified(pState, pRecord)) + goto bail; + + err = AnalyzeRecord(pRecord, &recordKind, &format, &totalLen, + &totalCompLen); + if (err != kNuErrNone) + goto bail; + + len = strlen(pRecord->filename); + if (len <= 27) { + printf("%c%-27.27s ", IsRecordReadOnly(pRecord) ? '+' : ' ', + pRecord->filename); + } else { + printf("%c..%-25.25s ", IsRecordReadOnly(pRecord) ? '+' : ' ', + pRecord->filename + len - 25); + } + switch (recordKind) { + case kRecordKindUnknown: + printf("??? $%04lX ", + /*GetFileTypeString(pRecord->recFileType),*/ + pRecord->recExtraType); + break; + case kRecordKindDisk: + sprintf(tmpbuf, "%ldk", totalLen / 1024); + printf("Disk %-6s ", tmpbuf); + break; + case kRecordKindFile: + case kRecordKindForkedFile: + printf("%s%c $%04lX ", + GetFileTypeString(pRecord->recFileType), + recordKind == kRecordKindForkedFile ? '+' : ' ', + pRecord->recExtraType); + break; + default: + assert(0); + printf("ERROR "); + } + + printf("%s ", FormatDateShort(&pRecord->recArchiveWhen, date1)); + if (format >= NELEM(gShortFormatNames)) + printf("??? "); + else + printf("%s ", gShortFormatNames[format]); + + /* compute the percent size */ + if ((!totalLen && totalCompLen) || (totalLen && !totalCompLen)) + printf("--- "); /* weird */ + else if (totalLen < totalCompLen) + printf(">100%% "); /* compression failed? */ + else { + sprintf(tmpbuf, "%02d%%", ComputePercent(totalCompLen, totalLen)); + printf("%4s ", tmpbuf); + } + + if (!totalLen && totalCompLen) + printf(" ????"); /* weird */ + else + printf("%8ld", totalLen); + + printf("\n"); + + NState_AddToTotals(pState, totalLen, totalCompLen); + +bail: + if (err != kNuErrNone) { + printf("(ERROR on '%s')\n", pRecord->filename == nil ? + "" : (const char*)pRecord->filename); + } + return kNuOK; +} + +/* + * Print a short listing of the contents of an archive. + */ +NuError +DoListShort(NulibState* pState) +{ + NuError err; + NuArchive* pArchive = nil; + + assert(pState != nil); + + err = OpenArchiveReadOnly(pState); + if (err != kNuErrNone) + goto bail; + pArchive = NState_GetNuArchive(pState); + assert(pArchive != nil); + + err = NuContents(pArchive, ShowContentsShort); + /* fall through with err */ + +bail: + if (pArchive != nil) + (void) NuClose(pArchive); + return err; +} + + +/* + * Print a more verbose listing of the contents of an archive. + */ +NuError +DoListVerbose(NulibState* pState) +{ + NuError err; + NuArchive* pArchive = nil; + const NuMasterHeader* pHeader; + char date1[kNuDateOutputLen]; + char date2[kNuDateOutputLen]; + long totalLen, totalCompLen; + const char* cp; + + assert(pState != nil); + + err = OpenArchiveReadOnly(pState); + if (err != kNuErrNone) + goto bail; + pArchive = NState_GetNuArchive(pState); + assert(pArchive != nil); + + /* + * Try to get just the filename. + */ + if (IsFilenameStdin(NState_GetArchiveFilename(pState))) + cp = ""; + else + cp = FilenameOnly(pState, NState_GetArchiveFilename(pState)); + + /* grab the master header block */ + err = NuGetMasterHeader(pArchive, &pHeader); + if (err != kNuErrNone) + goto bail; + + printf(" %-15.15s Created:%s Mod:%s Recs:%5lu\n\n", + cp, + FormatDateShort(&pHeader->mhArchiveCreateWhen, date1), + FormatDateShort(&pHeader->mhArchiveModWhen, date2), + pHeader->mhTotalRecords); + printf(" Name Type Auxtyp Archived" + " Fmat Size Un-Length\n"); + printf("-------------------------------------------------" + "----------------------------\n"); + + err = NuContents(pArchive, ShowContentsVerbose); + if (err != kNuErrNone) + goto bail; + + /* + * Show the totals. NuFX overhead can be as much as 25% for archives + * with lots of small files. + */ + NState_GetTotals(pState, &totalLen, &totalCompLen); + printf("-------------------------------------------------" + "----------------------------\n"); + printf(" Uncomp: %ld Comp: %ld %%of orig: %d%%\n", + 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)); + #endif + + /*(void) NuDebugDumpArchive(pArchive);*/ + +bail: + if (pArchive != nil) + (void) NuClose(pArchive); + return err; +} + + + +/* + * Null callback, for those times when you don't really want to do anything. + */ +static NuResult +NullCallback(NuArchive* pArchive, void* vpRecord) +{ + return kNuOK; +} + +/* + * Print very detailed output, suitable for debugging (requires that + * debugging be enabled in nufxlib). + */ +NuError +DoListDebug(NulibState* pState) +{ + NuError err; + NuArchive* pArchive = nil; + + assert(pState != nil); + + err = OpenArchiveReadOnly(pState); + if (err != kNuErrNone) + goto bail; + pArchive = NState_GetNuArchive(pState); + assert(pArchive != nil); + + /* have to do something to force the library to scan the archive */ + err = NuContents(pArchive, NullCallback); + if (err != kNuErrNone) + goto bail; + + err = NuDebugDumpArchive(pArchive); + if (err != kNuErrNone) + fprintf(stderr, "ERROR: debugging not enabled in nufxlib\n"); + /* fall through with err */ + +bail: + if (pArchive != nil) + (void) NuClose(pArchive); + return err; +} + diff --git a/nulib2/Main.c b/nulib2/Main.c new file mode 100644 index 0000000..189ac93 --- /dev/null +++ b/nulib2/Main.c @@ -0,0 +1,409 @@ +/* + * 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. + * + * Main entry point and shell command argument processing. + */ +#include "Nulib2.h" +#include + + +/* + * Globals and constants. + */ +const char* gProgName = "Nulib2"; + + +/* + * Which modifiers are valid with which commands? + */ +typedef struct ValidCombo { + Command cmd; + Boolean okayForPipe; + Boolean filespecRequired; + const char* modifiers; +} ValidCombo; + +static const ValidCombo gValidCombos[] = { + { kCommandAdd, false, true, "ufrj0cke" }, + { 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" }, +}; + + +/* + * Find an entry in the gValidCombos table matching the specified command. + * + * Returns nil if not found. + */ +static const ValidCombo* +FindValidComboEntry(Command cmd) +{ + int i; + + for (i = 0; i < NELEM(gValidCombos); i++) { + if (gValidCombos[i].cmd == cmd) + return &gValidCombos[i]; + } + + return nil; +} + +/* + * Determine whether the specified modifier is valid when used with the + * current command. + */ +static Boolean +IsValidModifier(Command cmd, char modifier) +{ + const ValidCombo* pvc; + + pvc = FindValidComboEntry(cmd); + if (pvc != nil) { + if (strchr(pvc->modifiers, modifier) == nil) + return false; + else + return true; + } else + return false; +} + +/* + * Determine whether the specified command can be used with stdin as input. + */ +static Boolean +IsValidOnPipe(Command cmd) +{ + const ValidCombo* pvc; + + pvc = FindValidComboEntry(cmd); + if (pvc != nil) { + return pvc->okayForPipe; + } else + return false; +} + +/* + * Determine whether the specified command can be used with stdin as input. + */ +static Boolean +IsFilespecRequired(Command cmd) +{ + const ValidCombo* pvc; + + pvc = FindValidComboEntry(cmd); + if (pvc != nil) { + return pvc->filespecRequired; + } else { + /* command not found? warn about it here... */ + fprintf(stderr, "%s: Command %d not found in gValidCombos table\n", + gProgName, cmd); + return false; + } +} + + +/* + * Separate the program name out of argv[0]. + */ +static const char* +GetProgName(const NulibState* pState, const char* argv0) +{ + const char* result; + char sep; + + /* use the appropriate system pathname separator */ + sep = NState_GetSystemPathSeparator(pState); + + result = strrchr(argv0, sep); + if (result == nil) + result = argv0; + else + result++; /* advance past the separator */ + + return result; +} + + +/* + * Print program usage. + */ +static void +Usage(const NulibState* pState) +{ + long majorVersion, minorVersion, bugVersion; + const char* nufxLibDate; + const char* nufxLibFlags; + + (void) NuGetVersion(&majorVersion, &minorVersion, &bugVersion, + &nufxLibDate, &nufxLibFlags); + + printf("\nNulib2 v%s, linked with NufxLib v%ld.%ld.%ld [%s]\n", + NState_GetProgramVersion(pState), + majorVersion, minorVersion, bugVersion, nufxLibFlags); + printf("This software is distributed under terms of the GNU General Public License.\n"); + printf("Written by Andy McFadden, http://www.nulib.com/.\n\n"); + printf("Usage: %s -command[modifiers] archive [filename-list]\n\n", + gProgName); + printf( + " -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" + "\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" + " -l auto-convert text files -ll auto-convert ALL files\n" + " -s stomp existing files w/o asking -k store files as disk images\n" + " -e preserve ProDOS file types -ee extend preserved names\n" + ); +} + + +/* + * Process the command-line options. The results are placed into "pState". + */ +static int +ProcessOptions(NulibState* pState, int argc, char* const* argv) +{ + const char* cp; + int idx; + + /* + * Must have at least a command letter and an archive filename. + */ + if (argc < 3) { + Usage(pState); + return -1; + } + + /* + * Argv[1] and any subsequent entries that have a leading hyphen + * are options. Anything after that is a filename. Parse until we + * think we've hit the filename. + * + * By UNIX convention, however, stdin is specified as a file called "-". + */ + for (idx = 1; idx < argc; idx++) { + cp = argv[idx]; + + if (idx > 1 && *cp != '-') + break; + + if (*cp == '-') + cp++; + if (*cp == '\0') { + if (idx == 1) { + fprintf(stderr, + "%s: You must specify a command after the '-'\n", + gProgName); + goto fail; + } else { + /* they're using '-' for the filename */ + break; + } + } + + if (idx == 1) { + switch (tolower(*cp)) { + case 'a': NState_SetCommand(pState, kCommandAdd); break; + case 'x': NState_SetCommand(pState, kCommandExtract); break; + case 'p': NState_SetCommand(pState, kCommandExtractToPipe); break; + case 't': NState_SetCommand(pState, kCommandListShort); break; + case 'v': NState_SetCommand(pState, kCommandListVerbose); break; + case 'z': NState_SetCommand(pState, kCommandListDebug); break; + case 'i': NState_SetCommand(pState, kCommandTest); break; + case 'd': NState_SetCommand(pState, kCommandDelete); break; + default: + fprintf(stderr, "%s: Unknown command '%c'\n", gProgName, *cp); + goto fail; + } + + cp++; + } + + while (*cp != '\0') { + switch (tolower(*cp)) { + case 'u': NState_SetModUpdate(pState, true); break; + case 'f': NState_SetModFreshen(pState, true); break; + 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 'e': + if (*(cp-1) == 'e') /* should never point at invalid */ + NState_SetModPreserveTypeExtended(pState, true); + else + NState_SetModPreserveType(pState, true); + break; + case 'l': + if (*(cp-1) == 'l') /* should never point at invalid */ + NState_SetModConvertAll(pState, true); + else + NState_SetModConvertText(pState, true); + break; + default: + fprintf(stderr, "%s: Unknown modifier '%c'\n", gProgName, *cp); + goto fail; + } + + if (!IsValidModifier(NState_GetCommand(pState), (char)tolower(*cp))) + { + fprintf(stderr, + "%s: The '%c' modifier doesn't make sense here\n", + gProgName, tolower(*cp)); + goto fail; + } + + cp++; + } + } + + /* + * See if we have an archive name. If it's "-", see if we allow that. + */ + assert(idx < argc); + NState_SetArchiveFilename(pState, argv[idx]); + if (IsFilenameStdin(argv[idx])) { + if (!IsValidOnPipe(NState_GetCommand(pState))) { + fprintf(stderr, "%s: You can't do that with a pipe\n", + gProgName); + goto fail; + } + } + idx++; + + /* + * See if we have a file specification. Some of the commands require + * a filespec; others just perform the requested operation on all of + * the records in the archive if none is provided. + */ + if (idx < argc) { + /* got one or more */ + NState_SetFilespecPointer(pState, &argv[idx]); + NState_SetFilespecCount(pState, argc - idx); + } else { + assert(idx == argc); + if (IsFilespecRequired(NState_GetCommand(pState))) { + fprintf(stderr, "%s: This command requires a list of files\n", + gProgName); + goto fail; + } + NState_SetFilespecPointer(pState, nil); + NState_SetFilespecCount(pState, 0); + } + + +#ifdef DEBUG_VERBOSE + NState_DebugDump(pState); +#endif + + return 0; + +fail: + fprintf(stderr, + "%s: (invoke without arguments to see usage information)\n", + gProgName); + return -1; +} + + +/* + * We have all of the parsed command line options in "pState". Now we just + * have to do something useful with it. + * + * Returns 0 on success, 1 on error. + */ +int +DoWork(NulibState* pState) +{ + NuError err; + + switch (NState_GetCommand(pState)) { + case kCommandAdd: + err = DoAdd(pState); + break; + case kCommandExtract: + err = DoExtract(pState); + break; + case kCommandExtractToPipe: + err = DoExtractToPipe(pState); + break; + case kCommandTest: + err = DoTest(pState); + break; + case kCommandListShort: + err = DoListShort(pState); + break; + case kCommandListVerbose: + err = DoListVerbose(pState); + break; + case kCommandListDebug: + err = DoListDebug(pState); + break; + case kCommandDelete: + err = DoDelete(pState); + break; + default: + fprintf(stderr, "ERROR: unexpected command %d\n", + NState_GetCommand(pState)); + err = kNuErrInternal; + assert(0); + break; + } + + return (err != kNuErrNone); +} + +/* + * Entry point. + */ +int +main(int argc, char** argv) +{ + NulibState* pState = nil; + int result = 0; + + #if 0 + extern NuResult ErrorMessageHandler(NuArchive* pArchive, + void* vErrorMessage); + NuSetGlobalErrorMessageHandler(ErrorMessageHandler); + #endif + + if (NState_Init(&pState) != kNuErrNone) { + fprintf(stderr, "ERROR: unable to initialize globals\n"); + exit(1); + } + + gProgName = GetProgName(pState, argv[0]); + + if (ProcessOptions(pState, argc, argv) < 0) { + result = 2; + goto bail; + } + + if (NState_ExtraInit(pState) != kNuErrNone) { + fprintf(stderr, "ERROR: additional initialization failed\n"); + exit(1); + } + + result = DoWork(pState); + if (result) + printf("Failed.\n"); + +bail: + NState_Free(pState); + exit(result); +} + diff --git a/nulib2/Makefile.in b/nulib2/Makefile.in new file mode 100644 index 0000000..12e8c0a --- /dev/null +++ b/nulib2/Makefile.in @@ -0,0 +1,140 @@ +# +# Makefile for nulib2 stuff (should work with non-GNU make). +# +# You can use: +# make (builds nulib2 and checks for libnufx.a) +# make shared (builds nulib2 and checks for libnufx.so) +# +# This will try to link against the library in $(NUFXSRCDIR) first, then +# look for a copy in the standard system install location (usually +# /usr/local/lib). +# +# Note that this really wants to find $(NUFXLIB) for dependency checking. +# If you're building against a copy in /usr/local/lib, just put a '#' in +# front of the "NUFXLIB" line below. +# + +# set this to where the NuFX library and ".h" file live +VERSION = 100 +NUFXSRCDIR = ../nufxlib-$(VERSION) +NUFXLIB = $(NUFXSRCDIR)/$(LIB_PRODUCT) + +# NuLib2 install location. The man page will go into $(mandir)/man1. +prefix = @prefix@ +exec_prefix = @exec_prefix@ +includedir = @includedir@ +libdir = @libdir@ +bindir = @bindir@ +mandir = @mandir@ +srcdir = @srcdir@ + +SHELL = @SHELL@ +INSTALL = @INSTALL@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_DATA = @INSTALL_DATA@ +CC = @CC@ +#OPT = @CFLAGS@ -DNDEBUG +OPT = @CFLAGS@ +#OPT = @CFLAGS@ -DDEBUG_MSGS +#OPT = @CFLAGS@ -DDEBUG_VERBOSE +GCC_FLAGS = -Wall -Wwrite-strings -Wstrict-prototypes -Wpointer-arith -Wshadow +CFLAGS = @BUILD_FLAGS@ -I. -I$(NUFXSRCDIR) -I$(includedir) @DEFS@ + +#ifdef PURIFY_BUILD +# PURIFY = purify +# CFLAGS += -DPURIFY +#endif +#ifdef QUANTIFY_BUILD +# QUANTIFY = quantify +# 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 + +PRODUCT = nulib2 + +# this is used for dependency checking +LIB_PRODUCT = libnufx.a + + +# +# Build stuff +# + +all: $(PRODUCT) + @true + +install: $(PRODUCT) + $(srcdir)/mkinstalldirs $(bindir) + $(INSTALL_PROGRAM) $(PRODUCT) $(bindir) + $(srcdir)/mkinstalldirs $(mandir)/man1 + $(INSTALL_DATA) nulib2.1 $(mandir)/man1/ + +install-shared: + LIB_PRODUCT="libnufx.so" $(MAKE) -e install + +# Link against the shared version of libnufx. This is only needed so +# the dependency checking does the right thing. +shared:: + LIB_PRODUCT="libnufx.so" $(MAKE) -e + +quantify: + -rm -f $(PRODUCT) + @$(MAKE) QUANTIFY_BUILD=1 + +purify: + -rm -f $(PRODUCT) + @$(MAKE) PURIFY_BUILD=1 + +$(PRODUCT): $(OBJS) $(NUFXLIB) + $(PURIFY) $(QUANTIFY) $(CC) -o $@ $(OBJS) -L$(NUFXSRCDIR) -L$(libdir) -lnufx @DMALLOC@ + +clean: + -rm -f *.o core + -rm -f $(PRODUCT) + +tags:: + ctags -R --totals * $(NUFXSRCDIR)/* + @#ctags *.[ch] $(NUFXSRCDIR)/*.[ch] + +distclean: clean + -rm -f Makefile Makefile.bak + -rm -f config.log config.cache config.status config.h + -rm -f tags + -rm -f nulib2-$(VERSION)-@host_alias@ nulib2-$(VERSION)-@host_alias@.tar.Z + +# Copy all of the binaries into a directory and tar them up for distribution. +# All binaries except "nulib2" are stripped to reduce their size. +distbin: $(PRODUCT) + @ \ + builddir="nulib2-$(VERSION)-@host_alias@"; \ + samples=$(NUFXSRCDIR)/samples; \ + echo "building $$builddir.tar.Z"; \ + rm -rf $$builddir; \ + mkdir -p $$builddir; \ + cp -p $(PRODUCT) nulib2.1 README.txt COPYING $$samples/README-S.txt \ + $$samples/exerciser $$samples/imgconv $$samples/launder \ + $$samples/test-basic $$samples/test-extract \ + $$samples/test-simple \ + $$builddir; \ + strip $$builddir/$(PRODUCT) \ + $$builddir/exerciser $$builddir/imgconv $$builddir/launder \ + $$builddir/test-basic $$builddir/test-extract \ + $$builddir/test-simple; \ + tar -cf - $$builddir | compress > $$builddir.tar.Z; \ + rm -rf $$builddir + +baktar: + @tar cvf nulib2.tar *.txt COPYING INSTALL nulib2.1 configure *.in Makefile \ + Makefile.msc install-sh config.guess config.sub mkinstalldirs *.[ch] + @gzip -9 nulib2.tar + @mv -i nulib2.tar.gz /home/fadden/BAK/ + +depend: + makedepend -- $(CFLAGS) -- $(SRCS) + +# DO NOT DELETE THIS LINE -- make depend depends on it. + diff --git a/nulib2/Makefile.msc b/nulib2/Makefile.msc new file mode 100644 index 0000000..b71bb6f --- /dev/null +++ b/nulib2/Makefile.msc @@ -0,0 +1,61 @@ +# +# Makefile for Microsoft C compilers. Tested against Visual C++ 6.0. +# Not pretty but it seems to work. +# +# Run with "nmake /f Makefile.msc". Expects NufxLib to have been built +# in "..\nufxlib-$(VERSION)". +# +# To build without debugging info, use "nmake nodebug=1". +# + +# Windows magic +TARGETOS = BOTH +!include + +VERSION=100 +NUFXSRCDIR = ..\nufxlib-$(VERSION) + +# object files +OBJS1 = Add.obj ArcUtils.obj Delete.obj Extract.obj Filename.obj List.obj +OBJS2 = Main.obj MiscStuff.obj MiscUtils.obj State.obj SysUtils.obj +OBJS = $(OBJS1) $(OBJS2) + +!ifdef NODEBUG +#OPT = $(cdebug) /D NDEBUG /ML +OPT = $(cdebug) /ML +LIB_FLAGS = /nodefaultlib:libcd.lib /nologo libc.lib setargv.obj +!else +OPT = $(cdebug) /MLd +LIB_FLAGS = /nodefaultlib:libc.lib /nologo libcd.lib setargv.obj +!endif + +BUILD_FLAGS = /W3 /GX /D "WIN32" /D "_CONSOLE" /I "$(NUFXSRCDIR)" + +# how to compile sources +.c.obj: + @$(cc) $(cdebug) $(OPT) $(BUILD_FLAGS) $(cflags) $(cvars) -o $@ $< + + +PRODUCT = nulib2.exe + +all: $(PRODUCT) + +nulib2.exe: $(OBJS) $(NUFXSRCDIR)\nufxlib.lib + $(link) $(ldebug) $** -out:$@ $(NUFXSRCDIR)\nufxlib.lib $(LIB_FLAGS) + +clean: + del *.obj + del $(PRODUCT) + +Add.obj: Add.c NuLib2.h $(NUFXSRCDIR)\NufxLib.h SysDefs.h State.h MiscStuff.h +ArcUtils.obj: ArcUtils.c NuLib2.h $(NUFXSRCDIR)\NufxLib.h SysDefs.h State.h MiscStuff.h +Delete.obj: Delete.c NuLib2.h $(NUFXSRCDIR)\NufxLib.h SysDefs.h State.h MiscStuff.h +Extract.obj: Extract.c NuLib2.h $(NUFXSRCDIR)\NufxLib.h SysDefs.h State.h MiscStuff.h +Filename.obj: Filename.c NuLib2.h $(NUFXSRCDIR)\NufxLib.h SysDefs.h State.h MiscStuff.h +List.obj: List.c NuLib2.h $(NUFXSRCDIR)\NufxLib.h SysDefs.h State.h MiscStuff.h +Main.obj: Main.c NuLib2.h $(NUFXSRCDIR)\NufxLib.h SysDefs.h State.h MiscStuff.h +MiscStuff.obj: MiscStuff.c NuLib2.h $(NUFXSRCDIR)\NufxLib.h SysDefs.h State.h MiscStuff.h +MiscUtils.obj: MiscUtils.c NuLib2.h $(NUFXSRCDIR)\NufxLib.h SysDefs.h State.h MiscStuff.h +State.obj: State.c NuLib2.h $(NUFXSRCDIR)\NufxLib.h SysDefs.h State.h MiscStuff.h +SysUtils.obj: SysUtils.c NuLib2.h $(NUFXSRCDIR)\NufxLib.h SysDefs.h State.h MiscStuff.h + diff --git a/nulib2/MiscStuff.c b/nulib2/MiscStuff.c new file mode 100644 index 0000000..a14af3f --- /dev/null +++ b/nulib2/MiscStuff.c @@ -0,0 +1,120 @@ +/* + * 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 Library General Public License, see the file COPYING.LIB. + * + * Misc stuff (shared between nufxlib and nulib2). This is a collection + * of standard functions that aren't available in libc on this system. + */ +#include "SysDefs.h" +#include "MiscStuff.h" +#include + + +#ifndef HAVE_STRERROR +/* + * Return a pointer to the appropriate string in the system table, or NULL + * if the value is out of bounds. + */ +const char* +Nu_strerror(int errnum) +{ + extern int sys_nerr; + extern char *sys_errlist[]; + + if (errnum < 0 || errnum > sys_nerr) + return NULL; + + return sys_errlist[errnum]; +} +#endif + +#ifndef HAVE_MEMMOVE +/* + * Move a block of memory. Unlike memcpy, this is expected to work + * correctly with overlapping blocks. + * + * This is a straightforward implementation. A much faster implementation, + * from BSD, is available in the PGP 2.6.2 distribution, but this should + * suffice for those few systems that don't have memmove. + */ +void* +Nu_memmove(void* dst, const void* src, size_t n) +{ + void* retval = dst; + char* srcp = (char*)src; + char* dstp = (char*)dst; + + /* you can normally get away with this if n==0 */ + assert(dst != NULL); + assert(src != NULL); + + if (dstp == srcp || !n) { + /* nothing to do */ + } else if (dstp > srcp) { + /* start from the end */ + (char*)dstp += n-1; + (char*)srcp += n-1; + while (n--) + *dstp-- = *srcp--; + } else { + /* start from the front */ + while (n--) + *dstp++ = *srcp++; + } + + return retval; +} +#endif + +#ifndef HAVE_STRTOUL +/* + * Perform strtol, but on an unsigned long. + * + * On systems that have strtol but don't have strtoul, the strtol + * function doesn't clamp the return value, making it similar in + * function to strtoul. The comparison is not exact, however, + * because strtoul is expected to lots of fancy things (like set + * errno to ERANGE). + * + * For our purposes here, strtol does all we need it to. Someday + * we should replace this with a "real" version. + */ +unsigned long +Nu_strtoul(const char *nptr, char **endptr, int base) +{ + return strtol(nptr, endptr, base); +} +#endif + +#ifndef HAVE_STRCASECMP +/* + * Compare two strings, case-insensitive. + */ +int +Nu_strcasecmp(const char *str1, const char *str2) +{ + while (*str1 && *str2 && toupper(*str1) == toupper(*str2)) + str1++, str2++; + return (toupper(*str1) - toupper(*str2)); +} + +#endif + +#ifndef HAVE_STRNCASECMP +/* + * Compare two strings, case-insensitive, stopping after "n" chars. + */ +int +Nu_strncasecmp(const char *str1, const char *str2, size_t n) +{ + while (n && *str1 && *str2 && toupper(*str1) == toupper(*str2)) + str1++, str2++, n--; + + if (n) + return (toupper(*str1) - toupper(*str2)); + else + return 0; /* no mismatch in first n chars */ +} +#endif + diff --git a/nulib2/MiscStuff.h b/nulib2/MiscStuff.h new file mode 100644 index 0000000..1f35ea0 --- /dev/null +++ b/nulib2/MiscStuff.h @@ -0,0 +1,108 @@ +/* + * 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 Library General Public License, see the file COPYING.LIB. + * + * Misc stuff (shared between nufxlib and nulib2). This is a collection + * of miscellaneous types and macros that I find generally useful. + */ +#ifndef __MiscStuff__ +#define __MiscStuff__ + +#include "SysDefs.h" + +/* + * Use our versions of functions if they don't exist locally. + */ +#ifndef HAVE_STRERROR +#define strerror Nu_strerror +const char* Nu_strerror(int errnum); +#endif +#ifndef HAVE_MEMMOVE +#define memmove Nu_memmove +void* Nu_memmove(void *dest, const void *src, size_t n); +#endif +#ifndef HAVE_STRTOUL +#define strtoul Nu_strtoul +unsigned long Nu_strtoul(const char *nptr, char **endptr, int base); +#endif +#ifndef HAVE_STRCASECMP +#define strcasecmp Nu_strcasecmp +int Nu_strcasecmp(const char *s1, const char *s2); +#endif +#ifndef HAVE_STRNCASECMP +#define strncasecmp Nu_strncasecmp +int Nu_strncasecmp(const char *s1, const char *s2, size_t n); +#endif + + +/* + * Misc types. + */ + +#include + +#define nil NULL /* I can't seem to stop typing 'nil' now */ + +typedef uchar Boolean; +#define false (0) +#define true (!false) + + +/* + * Handy macros. + */ + +/* compute #of elements in a static array */ +#define NELEM(x) (sizeof(x) / sizeof((x)[0])) + +/* convert single hex digit char to number */ +#define HexDigit(x) ( !isxdigit((int)(x)) ? -1 : \ + (x) <= '9' ? (x) - '0' : toupper(x) +10 - 'A' ) + +/* convert number from 0-15 to hex digit */ +#define HexConv(x) ( ((uint)(x)) <= 15 ? \ + ( (x) <= 9 ? (x) + '0' : (x) -10 + 'A') : -1 ) + + +/* + * Debug stuff. + */ + +/* + * Redefine this if you want assertions to do something other than default. + * Changing the definition of assert is tough, because assert.h redefines + * it every time it's included. On a Solaris 2.7 system I was using, gcc + * pulled assert.h in with some of the system headers, and their definition + * resulted in corrupted core dumps. + */ +#define Assert assert + +#if defined(DEBUG_VERBOSE) + /* quick debug printf macro */ + #define DBUG(args) printf args +#else + #define DBUG(args) ((void)0) +#endif + + +#if defined(NDEBUG) + #define DebugFill(addr, len) ((void)0) + + #define DebugAbort() ((void)0) + +#else + /* when debugging, fill Malloc blocks with junk, unless we're using Purify */ + #if !defined(PURIFY) + #define DebugFill(addr, len) memset(addr, 0xa3, len) + #else + #define DebugFill(addr, len) ((void)0) + #endif + + #define DebugAbort() abort() +#endif + +#define kInvalidFill (0xa3) +#define kInvalidPtr ((void*)0xa3a3a3a3) + +#endif /*__MiscStuff__*/ diff --git a/nulib2/MiscUtils.c b/nulib2/MiscUtils.c new file mode 100644 index 0000000..73dfb4f --- /dev/null +++ b/nulib2/MiscUtils.c @@ -0,0 +1,110 @@ +/* + * 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. + * + * Misc support functions. + */ +#define __MiscUtils_c__ +#include "Nulib2.h" + + +/* + * Similar to perror(), but takes the error as an argument, and knows + * about NufxLib errors as well as system errors. + * + * If "format" is nil, just the error message itself is printed. + */ +void +ReportError(NuError err, const char* format, ...) +{ + const char* msg; + va_list args; + + assert(format != nil); + + va_start(args, format); + + /* print the message, if any */ + if (format != nil) { + fprintf(stderr, "%s: ERROR: ", gProgName); + vfprintf(stderr, format, args); + } + + /* print the error code data, if any */ + if (err == kNuErrNone) + fprintf(stderr, "\n"); + else { + if (format != nil) + fprintf(stderr, ": "); + + msg = nil; + if (err >= 0) + msg = strerror(err); + if (msg == nil) + msg = NuStrError(err); + + if (msg == nil) + fprintf(stderr, "(unknown err=%d)\n", err); + else + fprintf(stderr, "%s\n", msg); + } + + va_end(args); +} + + +/* + * Memory allocation wrappers. + * + * Under gcc these would be macros, but not all compilers can handle that. + */ + +#ifndef USE_DMALLOC +void* +Malloc(size_t size) +{ + void* _result; + + Assert(size > 0); + _result = malloc(size); + if (_result == nil) { + ReportError(kNuErrMalloc, "malloc(%u) failed", (uint) size); + DebugAbort(); /* leave a core dump if we're built for it */ + } + DebugFill(_result, size); + return _result; +} + +void* +Calloc(size_t size) +{ + void* _cresult = Malloc(size); + memset(_cresult, 0, size); + return _cresult; +} + +void* +Realloc(void* ptr, size_t size) +{ + void* _result; + + Assert(ptr != nil); /* disallow this usage */ + Assert(size > 0); /* disallow this usage */ + _result = realloc(ptr, size); + if (_result == nil) { + ReportError(kNuErrMalloc, "realloc(%u) failed", (uint) size); + DebugAbort(); /* leave a core dump if we're built for it */ + } + return _result; +} + +void +Free(void* ptr) +{ + if (ptr != nil) + free(ptr); +} +#endif + diff --git a/nulib2/Nulib2.h b/nulib2/Nulib2.h new file mode 100644 index 0000000..899ebde --- /dev/null +++ b/nulib2/Nulib2.h @@ -0,0 +1,97 @@ +/* + * 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. + */ +#ifndef __Nulib2__ +#define __Nulib2__ + +#include "SysDefs.h" /* system-dependent defs; must come first */ +#include +#include "State.h" +#include "MiscStuff.h" + +#ifdef USE_DMALLOC +/* enable with something like "dmalloc -l logfile -i 100 medium" */ +# include "dmalloc.h" +#endif + +/* replace unsupported chars with '%xx' */ +#define kForeignIndic '%' + +/* make our one-line comments this big */ +#define kDefaultCommentLen 200 + + +/* + * Function prototypes. + */ + +/* Add.c */ +NuError DoAdd(NulibState* pState); + +/* ArcUtils.c */ +char* GetSimpleComment(NulibState* pState, const char* pathname, int maxLen); +Boolean IsFilenameStdin(const char* archiveName); +Boolean IsSpecified(NulibState* pState, const NuRecord* pRecord); +NuError OpenArchiveReadOnly(NulibState* pState); +NuError OpenArchiveReadWrite(NulibState* pState); +const NuThread* GetThread(const NuRecord* pRecord, ulong idx); +Boolean IsRecordReadOnly(const NuRecord* pRecord); + +/* Delete.c */ +NuError DoDelete(NulibState* pState); + +/* Extract.c */ +NuError DoExtract(NulibState* pState); +NuError DoExtractToPipe(NulibState* pState); +NuError DoTest(NulibState* pState); + +/* Filename.c */ +const char* GetFileTypeString(ulong fileType); +const char* NormalizePath(NulibState* pState, NuPathnameProposal* pathProposal); +void InterpretExtension(NulibState* pState, const char* pathName, + ulong* pFileType, ulong* pAuxType); +Boolean ExtractPreservationString(NulibState* pState, char* pathname, + ulong* pFileType, ulong* pAuxType, NuThreadID* pThreadID); +void DenormalizePath(NulibState* pState, char* pathBuf); +const char* FilenameOnly(NulibState* pState, const char* pathname); +const char* FindExtension(NulibState* pState, const char* pathname); + +/* List.c */ +NuError DoListShort(NulibState* pState); +NuError DoListVerbose(NulibState* pState); +NuError DoListDebug(NulibState* pState); + +/* Main.c */ +extern const char* gProgName; + +/* MiscUtils.c */ +void ReportError(NuError err, const char* format, ...) + #if defined(__GNUC__) + __attribute__ ((format(printf, 2, 3))) + #endif + ; +#ifdef USE_DMALLOC /* want file and line numbers for calls */ +# define Malloc(size) malloc(size) +# define Calloc(size) calloc(1, size) +# define Realloc(ptr, size) realloc(ptr, size) +# define Free(ptr) (ptr != nil ? free(ptr) : (void)0) +#else +void* Malloc(size_t size); +void* Calloc(size_t size); +void* Realloc(void* ptr, size_t size); +void Free(void* ptr); +#endif + +/* SysUtils.c */ +NuError NormalizeFileName(NulibState* pState, const char* srcp, long srcLen, + char fssep, char** pDstp, long dstLen); +NuError NormalizeDirectoryName(NulibState* pState, const char* srcp, + long srcLen, char fssep, char** pDstp, long dstLen); +char* MakeTempArchiveName(NulibState* pState); +NuError AddFile(NulibState* pState, NuArchive* pArchive, + const char* pathname); + +#endif /*__Nulib2__*/ diff --git a/nulib2/README.txt b/nulib2/README.txt new file mode 100644 index 0000000..2c5f7ef --- /dev/null +++ b/nulib2/README.txt @@ -0,0 +1,122 @@ +NuLib2 README, updated 2000/05/18 +http://www.nulib.com/ + +See "COPYING" for distribution restrictions. + +To build NuLib2, you will also need a copy of NufxLib. This may have come +in the same .tar.gz file. Build the library first. + + +UNIX +==== + +Make sure that the "NUFXSRCDIR" define in Makefile.in points to the correct +directory, or that the library has been installed in a standard location +such as /usr/local/lib/. If you received NuLib2 and NufxLib in a single +".tar.gz" file, the variable is already set correctly. The makefile will +look in $(NUFXSRCDIR) first, /usr/local/lib second. + +Run the "configure" script. Read through "INSTALL" if you haven't used +one of these before, especially if you want to use a specific compiler +or a particular set of compiler flags. + +Run "make depend" if you have makedepend, and then type "make". +This should leave you with an executable called "nulib2". If you like, +"make install" will put things into your install directory, usually +/usr/local/bin/ and /usr/local/man/. + +You may want to fiddle with the "OPT" setting in Makefile to enable or +disable optimizations and assertions. Because almost all of the hard +work is done by NufxLib, turning compiler optimizations on in NuLib2 has +little impact on performance. + + +A man page for nulib2 is in "nulib2.1", which you can format for viewing +with "nroff -man nulib2.1". A full manual for NuLib2 is available from +the www.nulib.com web site. + + +BeOS +==== + +This works just like the UNIX version, but certain defaults have been +changed. Running configure without arguments under BeOS is equivalent to: + + ./configure --prefix=/boot --includedir='${prefix}/develop/headers' + --libdir='${exec_prefix}/home/config/lib' --mandir='/tmp' + --bindir='${exec_prefix}/home/config/bin' + +If you're using BeOS/PPC, it will also do: + + CC=cc CFLAGS='-proc 603 -opt full' + + +Win32 +===== + +If you're using an environment that supports "configure" scripts, such as +DJGPP, follow the UNIX instructions. + +NuLib2 has been tested with Microsoft Visual C++ 6.0. To build NuLib2, +start up a DOS shell and run vcvars32.bat to set your environment. Run: + nmake -f makefile.msc +to build with debugging info, or + nmake -f makefile.msc nodebug=1 +to build optimized. + + +Other Notes +=========== + +All of the source code was formatted with four-space hard tabs. + + +Fun benchmark of the day: + +Time to compress 1525 files, totaling 19942152 bytes, on an Apple IIgs +with an 8MHz ZipGS accelerator and Apple HS SCSI card, running System +6.0.1, from a 20MB ProDOS partition to a 13.9MB archive on an HFS volume, +with GS/ShrinkIt 1.1: about 40 minutes. + +Time to compress the same files, on a 128MB 500MHz Pentium-III running +Red Hat Linux 6.0, with NuLib2 v0.3: about six seconds. + + + +Here's a nifty way to evaluate GSHK vs NuLib2 (as well as Linux NuLib2 +vs Win32 NuLib2): + + - Archive a whole bunch of files from a ProDOS volume with GS/ShrinkIt. + I used a 20MB partition, which resulted in a 14MB archive. Transfer + the archive to a machine running NuLib2 (perhaps a Linux system). + - Create a new subdirectory, cd into it, and extract the entire archive + with "nulib2 xe ../foo.shk". + - Now create a new archive with all of the files, using + "nulib2 aer ../new.shk *". + - Change back to the directory above, and use "nulib2 v" to see what's + in them, e.g. "nulib2 v foo.shk > out.orig" and + "nulib2 v new.shk > out.new". + - Edit both of the "out" files with vi. Do a global search-and-replace + to change '/' to ':' in out.new (:%s/\//:/g) so the filename separator + doesn't mess up the comparison. + - Do a global search-and-replace in both files to set the file dates + to be the same. I used ":%s/..-...-.. ..:../01-Jan-00 00:00/". This + is necessary because, like ShrinkIt, NuLib displays the date on which + the files were archived, not when they were last modified. + - Sort both files, with ":%!sort". This is necessary because you + added the files with '*' up above, so the NuLib2-created archive + has the top-level files alphabetized. + - Quit out of vi. Diff the two files. + +I did this for a 20MB hard drive partition with 1500 files on it. +The only discrepancies (accounting for a total difference of 116 bytes) +were a zero-byte "Kangaroo.data" file that GSHK stored improperly and some +semi-random GSHK behavior that I can't mimic. When the "Mimic ShrinkIt" +flag is disabled, the resulting archive is 13K smaller. + + +The largest archive I've tried had 4700 files for a total of 76MB +(compressed down to 47MB), and contained the entire contents of four 20MB +ProDOS hard drive partitions. NuLib2 under Linux handled it without +breaking a sweat. + diff --git a/nulib2/State.c b/nulib2/State.c new file mode 100644 index 0000000..bcdb381 --- /dev/null +++ b/nulib2/State.c @@ -0,0 +1,499 @@ +/* + * 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. + * + * Global-ish state object. + */ +#include "Nulib2.h" + + +static const char* gProgramVersion = "1.0.0"; + + +/* + * Allocate and initialize the semi-global Nulib2 state object. + */ +NuError +NState_Init(NulibState** ppState) +{ + assert(ppState != nil); + + *ppState = Calloc(sizeof(**ppState)); + if (*ppState == nil) + return kNuErrMalloc; + + /* + * Initialize the contents to default values. + */ + (*ppState)->systemPathSeparator = PATH_SEP; + (*ppState)->programVersion = gProgramVersion; + + return kNuErrNone; +} + +/* + * A little extra initialization, performed after arguments are parsed. + */ +NuError +NState_ExtraInit(NulibState* pState) +{ + NuError err; + NuValue convertEOL; + + /* + * Create a data sink for "stdout", in case we use the "-p" command. + * Set the EOL conversion according to the "-l" modifier. + */ + convertEOL = kNuConvertOff; + if (pState->modConvertText) + convertEOL = kNuConvertAuto; + if (pState->modConvertAll) + convertEOL = kNuConvertOn; + + err = NuCreateDataSinkForFP(true, convertEOL, stdout, &pState->pPipeSink); + if (err != kNuErrNone) + return err; + + /* + * Create a data sink for "stdout", in case we use the "-c" modifier. + * The EOL conversion is always on. + */ + err = NuCreateDataSinkForFP(true, kNuConvertOn, stdout, + &pState->pCommentSink); + return err; +} + + +/* + * Free up the state structure and its contents. + */ +void +NState_Free(NulibState* pState) +{ + if (pState == nil) + return; + + Free(pState->renameToStr); /* ?? */ + Free(pState->tempPathnameBuf); + if (pState->pPipeSink != nil) + NuFreeDataSink(pState->pPipeSink); + if (pState->pCommentSink != nil) + NuFreeDataSink(pState->pCommentSink); + Free(pState); +} + + +#ifdef DEBUG_MSGS +void +NState_DebugDump(const NulibState* pState) +{ + /* this will break when the code changes, but it's just for debugging */ + static const char* kCommandNames[] = { + "", + "add", + "delete", + "extract", + "extractToPipe", + "listShort", + "listVerbose", + "listDebug", + "test", + }; + + assert(pState != nil); + + printf("NState:\n"); + printf(" programVersion: '%s'\n", pState->programVersion); + printf(" systemPathSeparator: '%c'\n", pState->systemPathSeparator); + printf(" archiveFilename: '%s'\n", pState->archiveFilename); + printf(" filespec: %ld (%s ...)\n", pState->filespecCount, + !pState->filespecCount ? "" : *pState->filespecPointer); + + printf(" command: %d (%s); modifiers:\n", pState->command, + kCommandNames[pState->command]); + if (pState->modUpdate) + printf(" update\n"); + if (pState->modFreshen) + printf(" freshen\n"); + if (pState->modRecurse) + printf(" recurse\n"); + if (pState->modJunkPaths) + printf(" junkPaths\n"); + if (pState->modNoCompression) + printf(" noCompression\n"); + if (pState->modComments) + printf(" comments\n"); + if (pState->modConvertText) + printf(" convertText\n"); + if (pState->modConvertAll) + printf(" convertAll\n"); + if (pState->modOverwriteExisting) + printf(" overwriteExisting\n"); + if (pState->modPreserveType) + printf(" preserveType\n"); + if (pState->modPreserveTypeExtended) + printf(" preserveTypeExtended\n"); + + printf("\n"); +} +#endif + + +/* + * =========================================================================== + * Simple set/get functions + * =========================================================================== + */ + +char +NState_GetSystemPathSeparator(const NulibState* pState) +{ + return pState->systemPathSeparator; +} + +const char* +NState_GetProgramVersion(const NulibState* pState) +{ + return pState->programVersion; +} + +NuArchive* +NState_GetNuArchive(const NulibState* pState) +{ + return pState->pArchive; +} + +void +NState_SetNuArchive(NulibState* pState, NuArchive* pArchive) +{ + pState->pArchive = pArchive; +} + + +Boolean +NState_GetSuppressOutput(const NulibState* pState) +{ + return pState->suppressOutput; +} + +void +NState_SetSuppressOutput(NulibState* pState, Boolean doSuppress) +{ + pState->suppressOutput = doSuppress; +} + +Boolean +NState_GetInputUnavailable(const NulibState* pState) +{ + return pState->inputUnavailable; +} + +void +NState_SetInputUnavailable(NulibState* pState, Boolean isUnavailable) +{ + pState->inputUnavailable = isUnavailable; +} + +NuRecordIdx +NState_GetRenameFromIdx(const NulibState* pState) +{ + return pState->renameFromIdx; +} + +void +NState_SetRenameFromIdx(NulibState* pState, NuRecordIdx recordIdx) +{ + pState->renameFromIdx = recordIdx; +} + +char* +NState_GetRenameToStr(const NulibState* pState) +{ + return pState->renameToStr; +} + +void +NState_SetRenameToStr(NulibState* pState, char* str) +{ + Free(pState->renameToStr); + pState->renameToStr = str; +} + + +NuDataSink* +NState_GetPipeSink(const NulibState* pState) +{ + return pState->pPipeSink; +} + +NuDataSink* +NState_GetCommentSink(const NulibState* pState) +{ + return pState->pCommentSink; +} + +long +NState_GetMatchCount(const NulibState* pState) +{ + return pState->matchCount; +} + +void +NState_SetMatchCount(NulibState* pState, long count) +{ + pState->matchCount = count; +} + +void +NState_IncMatchCount(NulibState* pState) +{ + pState->matchCount++; +} + +void +NState_AddToTotals(NulibState* pState, long len, long compLen) +{ + pState->totalLen += len; + pState->totalCompLen += compLen; +} + +void +NState_GetTotals(NulibState* pState, long* pTotalLen, long* pTotalCompLen) +{ + *pTotalLen = pState->totalLen; + *pTotalCompLen = pState->totalCompLen; +} + +long +NState_GetTempPathnameLen(NulibState* pState) +{ + return pState->tempPathnameAlloc; +} + +void +NState_SetTempPathnameLen(NulibState* pState, long len) +{ + char* newBuf; + + len++; /* add one for the '\0' */ + + if (pState->tempPathnameAlloc < len) { + if (pState->tempPathnameBuf == nil) + newBuf = Malloc(len); + else + newBuf = Realloc(pState->tempPathnameBuf, len); + assert(newBuf != nil); + if (newBuf == nil) { + Free(pState->tempPathnameBuf); + pState->tempPathnameBuf = nil; + pState->tempPathnameAlloc = 0; + ReportError(kNuErrMalloc, "buf realloc failed (%ld)", len); + return; + } + + pState->tempPathnameBuf = newBuf; + pState->tempPathnameAlloc = len; + } +} + +char* +NState_GetTempPathnameBuf(NulibState* pState) +{ + return pState->tempPathnameBuf; +} + + +Command +NState_GetCommand(const NulibState* pState) +{ + return pState->command; +} + +void +NState_SetCommand(NulibState* pState, Command cmd) +{ + pState->command = cmd; +} + +const char* +NState_GetArchiveFilename(const NulibState* pState) +{ + return pState->archiveFilename; +} + +void +NState_SetArchiveFilename(NulibState* pState, const char* archiveFilename) +{ + pState->archiveFilename = archiveFilename; +} + +char* const* +NState_GetFilespecPointer(const NulibState* pState) +{ + return pState->filespecPointer; +} + +void +NState_SetFilespecPointer(NulibState* pState, char* const* filespecPointer) +{ + pState->filespecPointer = filespecPointer; +} + +long +NState_GetFilespecCount(const NulibState* pState) +{ + return pState->filespecCount; +} + +void +NState_SetFilespecCount(NulibState* pState, long filespecCount) +{ + pState->filespecCount = filespecCount; +} + +Boolean +NState_GetModUpdate(const NulibState* pState) +{ + return pState->modUpdate; +} + +void +NState_SetModUpdate(NulibState* pState, Boolean val) +{ + pState->modUpdate = val; +} + +Boolean +NState_GetModFreshen(const NulibState* pState) +{ + return pState->modFreshen; +} + +void +NState_SetModFreshen(NulibState* pState, Boolean val) +{ + pState->modFreshen = val; +} + +Boolean +NState_GetModRecurse(const NulibState* pState) +{ + return pState->modRecurse; +} + +void +NState_SetModRecurse(NulibState* pState, Boolean val) +{ + pState->modRecurse = val; +} + +Boolean +NState_GetModJunkPaths(const NulibState* pState) +{ + return pState->modJunkPaths; +} + +void +NState_SetModJunkPaths(NulibState* pState, Boolean val) +{ + pState->modJunkPaths = val; +} + +Boolean +NState_GetModNoCompression(const NulibState* pState) +{ + return pState->modNoCompression; +} + +void +NState_SetModNoCompression(NulibState* pState, Boolean val) +{ + pState->modNoCompression = val; +} + +Boolean +NState_GetModComments(const NulibState* pState) +{ + return pState->modComments; +} + +void +NState_SetModComments(NulibState* pState, Boolean val) +{ + pState->modComments = val; +} + +Boolean +NState_GetModConvertText(const NulibState* pState) +{ + return pState->modConvertText; +} + +void +NState_SetModConvertText(NulibState* pState, Boolean val) +{ + pState->modConvertText = val; +} + +Boolean +NState_GetModConvertAll(const NulibState* pState) +{ + return pState->modConvertAll; +} + +void +NState_SetModConvertAll(NulibState* pState, Boolean val) +{ + pState->modConvertAll = val; +} + +Boolean +NState_GetModOverwriteExisting(const NulibState* pState) +{ + return pState->modOverwriteExisting; +} + +void +NState_SetModOverwriteExisting(NulibState* pState, Boolean val) +{ + pState->modOverwriteExisting = val; +} + +Boolean +NState_GetModAddAsDisk(const NulibState* pState) +{ + return pState->modAddAsDisk; +} + +void +NState_SetModAddAsDisk(NulibState* pState, Boolean val) +{ + pState->modAddAsDisk = val; +} + +Boolean +NState_GetModPreserveType(const NulibState* pState) +{ + return pState->modPreserveType; +} + +void +NState_SetModPreserveType(NulibState* pState, Boolean val) +{ + pState->modPreserveType = val; +} + +Boolean +NState_GetModPreserveTypeExtended(const NulibState* pState) +{ + return pState->modPreserveTypeExtended; +} + +void +NState_SetModPreserveTypeExtended(NulibState* pState, Boolean val) +{ + pState->modPreserveTypeExtended = val; +} + diff --git a/nulib2/State.h b/nulib2/State.h new file mode 100644 index 0000000..94d15f9 --- /dev/null +++ b/nulib2/State.h @@ -0,0 +1,145 @@ +/* + * 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. + */ +#ifndef __State__ +#define __State__ + +#include "MiscStuff.h" +#include "NufxLib.h" + +/* + * Things you can tell Nulib2 to do. + * + * (Some debug code in NState_DebugDump() is sensitive to the order here.) + */ +typedef enum Command { + kCommandUnknown = 0, + kCommandAdd, + kCommandDelete, + kCommandExtract, + kCommandExtractToPipe, + kCommandListShort, + kCommandListVerbose, + kCommandListDebug, + kCommandTest +} Command; + + +/* + * Program-wide state. + */ +typedef struct NulibState { + /* global goodness */ + const char* programVersion; + + /* system-specific values */ + char systemPathSeparator; + + /* pointer to archive we're working with */ + NuArchive* pArchive; + + /* misc state */ + Boolean suppressOutput; + Boolean inputUnavailable; + NuRecordIdx renameFromIdx; + char* renameToStr; + NuDataSink* pPipeSink; + NuDataSink* pCommentSink; + long matchCount; + long totalLen; + long totalCompLen; + + /* temp storage */ + long tempPathnameAlloc; + char* tempPathnameBuf; + + /* command-line options */ + Command command; + Boolean modUpdate; + Boolean modFreshen; + Boolean modRecurse; + Boolean modJunkPaths; + Boolean modNoCompression; + Boolean modComments; + Boolean modConvertText; + Boolean modConvertAll; + Boolean modOverwriteExisting; + Boolean modAddAsDisk; + Boolean modPreserveType; + Boolean modPreserveTypeExtended; + + const char* archiveFilename; + char* const* filespecPointer; + long filespecCount; +} NulibState; + +NuError NState_Init(NulibState** ppState); +NuError NState_ExtraInit(NulibState* pState); +void NState_Free(NulibState* pState); +#ifdef DEBUG_MSGS +void NState_DebugDump(const NulibState* pState); +#endif + +char NState_GetSystemPathSeparator(const NulibState* pState); +const char* NState_GetProgramVersion(const NulibState* pState); +NuArchive* NState_GetNuArchive(const NulibState* pState); +void NState_SetNuArchive(NulibState* pState, NuArchive* pArchive); + +Boolean NState_GetSuppressOutput(const NulibState* pState); +void NState_SetSuppressOutput(NulibState* pState, Boolean doSuppress); +Boolean NState_GetInputUnavailable(const NulibState* pState); +void NState_SetInputUnavailable(NulibState* pState, Boolean isUnavailable); +NuRecordIdx NState_GetRenameFromIdx(const NulibState* pState); +void NState_SetRenameFromIdx(NulibState* pState, NuRecordIdx recordIdx); +char* NState_GetRenameToStr(const NulibState* pState); +void NState_SetRenameToStr(NulibState* pState, char* str); +NuDataSink* NState_GetPipeSink(const NulibState* pState); +NuDataSink* NState_GetCommentSink(const NulibState* pState); +long NState_GetMatchCount(const NulibState* pState); +void NState_SetMatchCount(NulibState* pState, long count); +void NState_IncMatchCount(NulibState* pState); +void NState_AddToTotals(NulibState* pState, long len, long compLen); +void NState_GetTotals(NulibState* pState, long* pTotalLen, long* pTotalCompLen); + +long NState_GetTempPathnameLen(NulibState* pState); +void NState_SetTempPathnameLen(NulibState* pState, long len); +char* NState_GetTempPathnameBuf(NulibState* pState); + +Command NState_GetCommand(const NulibState* pState); +void NState_SetCommand(NulibState* pState, Command cmd); +const char* NState_GetArchiveFilename(const NulibState* pState); +void NState_SetArchiveFilename(NulibState* pState, const char* archiveFilename); +char* const* NState_GetFilespecPointer(const NulibState* pState); +void NState_SetFilespecPointer(NulibState* pState, char* const* filespec); +long NState_GetFilespecCount(const NulibState* pState); +void NState_SetFilespecCount(NulibState* pState, long filespecCount); + +Boolean NState_GetModUpdate(const NulibState* pState); +void NState_SetModUpdate(NulibState* pState, Boolean val); +Boolean NState_GetModFreshen(const NulibState* pState); +void NState_SetModFreshen(NulibState* pState, Boolean val); +Boolean NState_GetModRecurse(const NulibState* pState); +void NState_SetModRecurse(NulibState* pState, Boolean val); +Boolean NState_GetModJunkPaths(const NulibState* pState); +void NState_SetModJunkPaths(NulibState* pState, Boolean val); +Boolean NState_GetModNoCompression(const NulibState* pState); +void NState_SetModNoCompression(NulibState* pState, Boolean val); +Boolean NState_GetModComments(const NulibState* pState); +void NState_SetModComments(NulibState* pState, Boolean val); +Boolean NState_GetModConvertText(const NulibState* pState); +void NState_SetModConvertText(NulibState* pState, Boolean val); +Boolean NState_GetModConvertAll(const NulibState* pState); +void NState_SetModConvertAll(NulibState* pState, Boolean val); +Boolean NState_GetModOverwriteExisting(const NulibState* pState); +void NState_SetModOverwriteExisting(NulibState* pState, Boolean val); +Boolean NState_GetModAddAsDisk(const NulibState* pState); +void NState_SetModAddAsDisk(NulibState* pState, Boolean val); +Boolean NState_GetModPreserveType(const NulibState* pState); +void NState_SetModPreserveType(NulibState* pState, Boolean val); +Boolean NState_GetModPreserveTypeExtended(const NulibState* pState); +void NState_SetModPreserveTypeExtended(NulibState* pState, Boolean val); + +#endif /*__State__*/ diff --git a/nulib2/SunOS4.h b/nulib2/SunOS4.h new file mode 100644 index 0000000..76161ea --- /dev/null +++ b/nulib2/SunOS4.h @@ -0,0 +1,47 @@ +/* + * NuFX archive manipulation library + * 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 Library General Public License, see the file COPYING.LIB. + * + * This file was adapted from Devin Reade's "sunos4.h" in NuLib 3.2.5. + * It is provided for compilation under SunOS 4.x, when an ANSI compiler + * (such as gcc) is used. The system header files aren't quite sufficient + * to eliminate hordes of warnings. + */ +#ifndef __SunOS4__ +#define __SunOS4__ + +#ifdef __GNUC__ +extern int _flsbuf(int, FILE*); +extern int _filbuf(FILE*); +#endif + +extern void bcopy(char*, char*, int); +extern int fclose(FILE*); +extern int fflush(FILE*); +extern int fprintf(FILE*, const char*, ...); +extern int fread(char*, int, int, FILE *); +extern int fseek(FILE*, long, int); +extern int ftruncate(int, off_t); +extern int fwrite(const char*, int, int, FILE*); +extern char* mktemp(char *template); +extern time_t mktime(struct tm*); +extern int perror(const char*); +extern int printf(const char*, ...); +extern int remove(const char*); +extern int rename(const char*, const char*); +extern int tolower(int); +extern int setvbuf(FILE*, char*, int, int); +extern int sscanf(char*, const char*, ...); +extern int strcasecmp(const char*, const char*); +extern int strncasecmp(const char*, const char*, size_t); +extern long strtol(const char *, char **, int); +extern int system(const char*); +extern time_t timelocal(struct tm*); +extern time_t time(time_t*); +extern int toupper(int); +extern int vfprintf(FILE*, const char *, va_list); +extern char* vsprintf(char *str, const char *format, va_list ap); + +#endif /*__SunOS4__*/ diff --git a/nulib2/SysDefs.h b/nulib2/SysDefs.h new file mode 100644 index 0000000..7228bd7 --- /dev/null +++ b/nulib2/SysDefs.h @@ -0,0 +1,213 @@ +/* + * 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. + * + * This was adapted from gzip's "tailor.h" and NuLib's "nudefs.h". + */ +#ifndef __SysDefs__ +#define __SysDefs__ + +#ifdef HAVE_CONFIG_H +# include +#endif + +#ifdef DEBUG_VERBOSE +# define DEBUG_MSGS +#endif + +/* these should exist everywhere */ +#include +#include +#include +#include +#include +#include +#include + +/* basic Win32 stuff -- info-zip has much more complete defs */ +#if defined(WIN32) || defined(MSDOS) +# define WINDOWS_LIKE + +# ifndef HAVE_CONFIG_H +# define HAVE_FCNTL_H +# define HAVE_MALLOC_H +# define HAVE_STDLIB_H +# define HAVE_SYS_STAT_H +# undef HAVE_SYS_TIME_H +# define HAVE_SYS_TYPES_H +# undef HAVE_UNISTD_H +# undef HAVE_UTIME_H +# define HAVE_SYS_UTIME_H +# define HAVE_WINDOWS_H +# define HAVE_FDOPEN +# undef HAVE_FTRUNCATE +# define HAVE_MEMMOVE +# undef HAVE_MKSTEMP +# define HAVE_MKTIME +# define HAVE_SNPRINTF +# undef HAVE_STRCASECMP +# undef HAVE_STRNCASECMP +# define HAVE_STRERROR +# define HAVE_STRTOUL +# define HAVE_VSNPRINTF +# define SNPRINTF_DECLARED +# define VSNPRINTF_DECLARED +# define SPRINTF_RETURNS_INT +# define uchar unsigned char +# define ushort unsigned short +# define uint unsigned int +# define ulong unsigned long +# define inline /*Visual C++6.0 can't inline ".c" files*/ +# define mode_t int +# endif + +# include +# define FOPEN_WANTS_B +# define HAVE_CHSIZE +# define snprintf _snprintf +# define vsnprintf _vsnprintf + +#endif + + +#ifdef HAVE_MALLOC_H +# include +#endif +#ifdef HAVE_STDLIB_H +# include +#endif +#ifdef HAVE_SYS_STAT_H +# include +#endif +#ifdef HAVE_SYS_TIME_H +# include +#endif +#ifdef HAVE_SYS_TYPES_H +# include +#endif +#ifdef HAVE_UNISTD_H +# include +#endif + +#if defined(WINDOWS_LIKE) +# ifndef F_OK +# define F_OK 02 +# endif +# ifndef R_OK +# define R_OK 04 +# endif +#endif + + +#if defined(__unix__) || defined(__unix) || defined(__BEOS__) || \ + defined(__hpux) || defined(_AIX) || defined(__APPLE__) +# define UNIX_LIKE +#endif + + +#if defined(__MSDOS__) && !defined(MSDOS) +# define MSDOS +#endif + +#if defined(__OS2__) && !defined(OS2) +# define OS2 +#endif + +#if defined(OS2) && defined(MSDOS) /* MS C under OS/2 */ +# undef MSDOS +#endif + +/* this ought to get trimmed down */ +#ifdef MSDOS +# ifndef __GNUC__ +# ifdef __TURBOC__ +# define NO_OFF_T +# ifdef __BORLANDC__ +# define DIRENT +# else +# define NO_UTIME +# endif +# else /* MSC */ +# define HAVE_SYS_UTIME_H +# define NO_UTIME_H +# endif +# endif +# define PATH_SEP '\\' +# define PATH_SEP2 '/' +# define PATH_SEP3 ':' +# ifdef MAX_PATH +# define MAX_PATH_LEN MAX_PATH +# else +# define MAX_PATH_LEN 128 +# endif +# define NO_MULTIPLE_DOTS +# define MAX_EXT_CHARS 3 +# define NO_CHOWN +# define PROTO +# ifndef HAVE_CONFIG_H +# define STDC_HEADERS +# endif +# define NO_SIZE_CHECK +# define SYSTEM_DEFAULT_EOL "\r\n" +#endif + +#ifdef WIN32 /* Windows 95/98/NT */ +# define HAVE_SYS_UTIME_H +# define NO_UTIME_H +# define PATH_SEP '\\' +# define PATH_SEP2 '/' +# define PATH_SEP3 ':' +# ifdef MAX_PATH +# define MAX_PATH_LEN MAX_PATH +# else +# define MAX_PATH_LEN 260 +# endif +# define PROTO +# define STDC_HEADERS +# define SYSTEM_DEFAULT_EOL "\r\n" +#endif + +#ifdef MACOS +# define PATH_SEP ':' +# define NO_CHOWN +# define NO_UTIME +# define SYSTEM_DEFAULT_EOL "\r" +#endif + +#if defined(APW) || defined(__ORCAC__) +# define __appleiigs__ +# pragma lint -1 +# pragma memorymodel 1 +# pragma optimize 7 +/*# pragma debug 25 */ +# define PATH_SEP ':' +# define SYSTEM_DEFAULT_EOL "\r" + +# ifdef GNO +# define HAS_DIRENT +# endif +#endif + +#ifdef __GNUC__ /* this was missing from BeOS __MWERKS__, and probably others */ +# define HAS__FUNCTION__ +#endif + +#if defined(__sun__) && !defined(__SVR4) +# include "SunOS4.h" +#endif + +/* general defaults, mainly for UNIX */ + +#ifndef PATH_SEP +# define PATH_SEP '/' +#endif +#ifndef SYSTEM_DEFAULT_EOL +# define SYSTEM_DEFAULT_EOL "\n" +#endif +#ifndef MAX_PATH_LEN +# define MAX_PATH_LEN 1024 +#endif + +#endif /*__SysDefs__*/ diff --git a/nulib2/SysUtils.c b/nulib2/SysUtils.c new file mode 100644 index 0000000..0381056 --- /dev/null +++ b/nulib2/SysUtils.c @@ -0,0 +1,1009 @@ +/* + * 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. + * + * System-dependent utility functions. + */ +#include "Nulib2.h" + +#ifdef HAVE_WINDOWS_H +# include +#endif + +/* get a grip on this opendir/readdir stuff */ +#if defined(UNIX_LIKE) +# if defined(HAVE_DIRENT_H) +# include +# define DIR_NAME_LEN(dirent) ((int)strlen((dirent)->d_name)) + typedef struct dirent DIR_TYPE; +# elif defined(HAVE_SYS_DIR_H) +# include +# define DIR_NAME_LEN(direct) ((direct)->d_namlen) + typedef struct direct DIR_TYPE; +# elif defined(HAVE_NDIR_H) +# include +# define DIR_NAME_LEN(direct) ((direct)->d_namlen) + typedef struct direct DIR_TYPE; +# else +# error "Port this?" +# endif +#endif + +/* + * For systems (e.g. Visual C++ 6.0) that don't have these standard values. + */ +#ifndef S_IRUSR +# define S_IRUSR 0400 +# define S_IWUSR 0200 +# define S_IXUSR 0100 +# define S_IRWXU (S_IRUSR|S_IWUSR|S_IXUSR) +# define S_IRGRP (S_IRUSR >> 3) +# define S_IWGRP (S_IWUSR >> 3) +# define S_IXGRP (S_IXUSR >> 3) +# define S_IRWXG (S_IRWXU >> 3) +# define S_IROTH (S_IRGRP >> 3) +# define S_IWOTH (S_IWGRP >> 3) +# define S_IXOTH (S_IXGRP >> 3) +# define S_IRWXO (S_IRWXG >> 3) +#endif +#ifndef S_ISREG +# define S_ISREG(m) (((m) & S_IFMT) == S_IFREG) +# define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR) +#endif + + +/* + * =========================================================================== + * System-specific filename stuff + * =========================================================================== + */ + +#define kTempFileNameLen 20 + + +#if defined(UNIX_LIKE) + +/* + * Filename normalization for typical UNIX filesystems. Only '/' is + * forbidden. Maximum filename length is large enough that we might + * as well just let the filesystem truncate if it gets too long, rather + * than worry about truncating it cleverly. + */ +static NuError +UNIXNormalizeFileName(NulibState* pState, const char* srcp, long srcLen, + char fssep, char** pDstp, long dstLen) +{ + char* dstp = *pDstp; + + while (srcLen--) { /* don't go until null found! */ + assert(*srcp != '\0'); + + if (*srcp == '%') { + /* change '%' to "%%" */ + *dstp++ = *srcp; + *dstp++ = *srcp++; + } else if (*srcp == '/') { + /* change '/' to "%2f" */ + if (NState_GetModPreserveType(pState)) { + *dstp++ = kForeignIndic; + *dstp++ = HexConv(*srcp >> 4 & 0x0f); + *dstp++ = HexConv(*srcp & 0x0f); + } else { + *dstp++ = '_'; + } + srcp++; + } else { + /* no need to fiddle with it */ + *dstp++ = *srcp++; + } + } + + *dstp = '\0'; /* end the string, but don't advance past the null */ + assert(*pDstp - dstp <= dstLen); /* make sure we didn't overflow */ + *pDstp = dstp; + + return kNuErrNone; +} + +#elif defined(WINDOWS_LIKE) +/* + * You can't create files or directories with these names on a FAT filesystem, + * because they're MS-DOS "device special files". + * + * The list comes from the Linux kernel's fs/msdos/namei.c. + */ +static const char* fatReservedNames3[] = { + "CON", "PRN", "NUL", "AUX", nil +}; +static const char* fatReservedNames4[] = { + "LPT1", "LPT2", "LPT3", "LPT4", "COM1", "COM2", "COM3", "COM4", nil +}; + +/* + * Filename normalization for Win32 filesystems. You can't use [ \/:*?"<>| ]. + */ +static NuError +Win32NormalizeFileName(NulibState* pState, const char* srcp, long srcLen, + char fssep, char** pDstp, long dstLen) +{ + char* dstp = *pDstp; + const char* startp = srcp; + static const char* kInvalid = "\\/:*?\"<>|"; + + /* look for an exact match */ + if (srcLen == 3) { + const char** ppcch; + + for (ppcch = fatReservedNames3; *ppcch != nil; ppcch++) { + if (strncasecmp(srcp, *ppcch, srcLen) == 0) { + DBUG(("--- fixing '%s'\n", *ppcch)); + *dstp++ = '_'; + break; + } + } + } else if (srcLen == 4) { + const char** ppcch; + + for (ppcch = fatReservedNames4; *ppcch != nil; ppcch++) { + if (strncasecmp(srcp, *ppcch, srcLen) == 0) { + DBUG(("--- fixing '%s'\n", *ppcch)); + *dstp++ = '_'; + break; + } + } + } + + + while (srcLen--) { /* don't go until null found! */ + assert(*srcp != '\0'); + + if (*srcp == '%') { + /* change '%' to "%%" */ + *dstp++ = *srcp; + *dstp++ = *srcp++; + } else if (strchr(kInvalid, *srcp) != nil) { + /* change invalid char to "%2f" or '_' */ + if (NState_GetModPreserveType(pState)) { + *dstp++ = kForeignIndic; + *dstp++ = HexConv(*srcp >> 4 & 0x0f); + *dstp++ = HexConv(*srcp & 0x0f); + } else { + *dstp++ = '_'; + } + srcp++; + } else { + /* no need to fiddle with it */ + *dstp++ = *srcp++; + } + } + + *dstp = '\0'; /* end the string, but don't advance past the null */ + assert(*pDstp - dstp <= dstLen); /* make sure we didn't overflow */ + *pDstp = dstp; + + return kNuErrNone; +} +#endif + + +/* + * Normalize a file name to local filesystem conventions. The input + * is quite possibly *NOT* null-terminated, since it may represent a + * substring of a full pathname. Use "srcLen". + * + * The output filename is copied to *pDstp, which is advanced forward. + * + * The output buffer must be able to hold 3x the original string length. + */ +NuError +NormalizeFileName(NulibState* pState, const char* srcp, long srcLen, + char fssep, char** pDstp, long dstLen) +{ + NuError err; + + 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); +#elif defined(WINDOWS_LIKE) + err = Win32NormalizeFileName(pState, srcp, srcLen, fssep, pDstp, dstLen); +#else + #error "port this" +#endif + + return err; +} + + +/* + * Normalize a directory name to local filesystem conventions. + */ +NuError +NormalizeDirectoryName(NulibState* pState, const char* srcp, long srcLen, + char fssep, char** pDstp, long dstLen) +{ + /* in general, directories and filenames are the same */ + return NormalizeFileName(pState, srcp, srcLen, fssep, pDstp, dstLen); +} + + +/* + * Given the archive filename and the file system separator, strip off the + * archive filename and replace it with the name of a nonexistent file + * in the same directory. + * + * Under UNIX we just need the file to be on the same filesystem, but + * under GS/OS it has to be in the same directory. Not sure what Mac OS + * or Windows requires, so it's safest to just put it in the same dir. + */ +char* +MakeTempArchiveName(NulibState* pState) +{ + const char* archivePathname; + char fssep; + const char* nameStart; + char* newName = nil; + char* namePtr; + char* resultName = nil; + long len; + + archivePathname = NState_GetArchiveFilename(pState); + assert(archivePathname != nil); + fssep = NState_GetSystemPathSeparator(pState); + assert(fssep != 0); + + /* we'll get confused if the archive pathname looks like "/foo/bar/" */ + len = strlen(archivePathname); + if (len < 1) + goto bail; + if (archivePathname[len-1] == fssep) { + ReportError(kNuErrNone, "archive pathname can't end in '%c'", fssep); + goto bail; + } + + /* figure out where the filename ends */ + nameStart = strrchr(archivePathname, fssep); + if (nameStart == nil) { + /* nothing but a filename */ + newName = Malloc(kTempFileNameLen +1); + namePtr = newName; + } else { + nameStart++; /* advance past the fssep */ + newName = Malloc((nameStart - archivePathname) + kTempFileNameLen +1); + strcpy(newName, archivePathname); + namePtr = newName + (nameStart - archivePathname); + } + if (newName == nil) + goto bail; + + /* + * Create a new name with a mktemp-style template. + */ + strcpy(namePtr, "nulibtmpXXXXXX"); + + resultName = newName; + +bail: + if (resultName == nil) + Free(newName); + return resultName; +} + + +/* + * =========================================================================== + * Add a set of files + * =========================================================================== + */ + +/* + * AddFile() and supporting functions. + * + * When adding one or more files, we need to add the file's attributes too, + * including file type and access permissions. We may want to recurse + * into subdirectories. + * + * Because UNIX and GS/OS have rather different schemes for scanning + * directories, I'm defining the whole thing as system-dependent instead + * of trying to put an OS-dependent callback inside an OS-independent + * wrapper. The GS/OS directory scanning mechanism does everything stat() + * does, plus picks up file types, so AddDirectory will want to pass a + * lot more stuff into AddFile than the UNIX version. And the UNIX and + * Windows versions need to make filetype assumptions based on filename + * extensions. + * + * We could force GS/OS to do an opendir/readdir/stat sort of thing, and + * pass around some file type info that doesn't really get set under + * UNIX or Windows, but that would be slower and more clumsy. + */ + + +#if defined(UNIX_LIKE) || defined(WINDOWS_LIKE) +/* + * Check a file's status. + * + * [ Someday we may want to modify this to handle symbolic links. ] + */ +NuError +CheckFileStatus(const char* pathname, struct stat* psb, Boolean* pExists, + Boolean* pIsReadable, Boolean* pIsDir) +{ + NuError err = kNuErrNone; + int cc; + + assert(pathname != nil); + assert(pExists != nil); + assert(pIsReadable != nil); + assert(pIsDir != nil); + + *pExists = true; + *pIsReadable = true; + *pIsDir = false; + + cc = stat(pathname, psb); + if (cc) { + if (errno == ENOENT) + *pExists = false; + else + err = kNuErrFileStat; + goto bail; + } + + if (S_ISDIR(psb->st_mode)) + *pIsDir = true; + + /* + * Test if we can read this file. How do we do that? The easy but slow + * way is to call access(2), the harder way is to figure out + * what user/group we are and compare the appropriate file mode. + */ + if (access(pathname, R_OK) < 0) + *pIsReadable = false; + +bail: + return err; +} +#endif + +#if defined(UNIX_LIKE) || defined(WINDOWS_LIKE) +/* + * Convert from time in seconds to DateTime format. + */ +static void +UNIXTimeToDateTime(const time_t* pWhen, NuDateTime *pDateTime) +{ + struct tm* ptm; + + Assert(pWhen != nil); + Assert(pDateTime != nil); + + ptm = localtime(pWhen); + pDateTime->second = ptm->tm_sec; + pDateTime->minute = ptm->tm_min; + pDateTime->hour = ptm->tm_hour; + pDateTime->day = ptm->tm_mday -1; + pDateTime->month = ptm->tm_mon; + pDateTime->year = ptm->tm_year; + pDateTime->extra = 0; + pDateTime->weekDay = ptm->tm_wday +1; +} +#endif + +#if defined(UNIX_LIKE) || defined(WINDOWS_LIKE) +/* + * Set the contents of a NuFileDetails structure, based on the pathname + * and characteristics of the file. + */ +static NuError +SetFileDetails(NulibState* pState, const char* pathname, struct stat* psb, + NuFileDetails* pDetails) +{ + Boolean wasPreserved; + Boolean doJunk = false; + char* livePathStr; + char slashDotDotSlash[5] = "_.._"; + time_t now; + + 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); + strcpy(livePathStr, pathname); + + /* init to defaults */ + memset(pDetails, 0, sizeof(*pDetails)); + pDetails->threadID = kNuThreadIDDataFork; + pDetails->storageName = livePathStr; /* point at temp buffer */ + pDetails->fileSysID = kNuFileSysUnknown; + pDetails->fileSysInfo = NState_GetSystemPathSeparator(pState); + pDetails->fileType = 0; + pDetails->extraType = 0; + pDetails->storageType = kNuStorageUnknown; /* let NufxLib worry about it */ + if (psb->st_mode & S_IWUSR) + pDetails->access = kNuAccessUnlocked; + else + pDetails->access = kNuAccessLocked; + + /* if this is a disk image, fill in disk-specific fields */ + if (NState_GetModAddAsDisk(pState)) { + if ((psb->st_size & 0x1ff) != 0) { + /* reject anything whose size isn't a multiple of 512 bytes */ + printf("NOT storing odd-sized (%ld) file as disk image: %s\n", + (long)psb->st_size, livePathStr); + } else { + /* set fields; note the "preserve" stuff will override this */ + pDetails->threadID = kNuThreadIDDiskImage; + pDetails->storageType = 512; + pDetails->extraType = psb->st_size / 512; + } + } + + now = time(nil); + UNIXTimeToDateTime(&now, &pDetails->archiveWhen); + UNIXTimeToDateTime(&psb->st_mtime, &pDetails->modWhen); + UNIXTimeToDateTime(&psb->st_mtime, &pDetails->createWhen); + + /* + * Check for file type preservation info in the filename. If present, + * set the file type values and truncate the filename. + */ + wasPreserved = false; + if (NState_GetModPreserveType(pState)) { + wasPreserved = ExtractPreservationString(pState, livePathStr, + &pDetails->fileType, &pDetails->extraType, + &pDetails->threadID); + } + + /* + * Do a "denormalization" pass, where we convert invalid chars (such + * as '/') from percent-codes back to 8-bit characters. The filename + * will always be the same size or smaller, so we can do it in place. + */ + if (wasPreserved) + DenormalizePath(pState, livePathStr); + + /* + * If we're in "extended" mode, and the file wasn't preserved, take a + * guess at what the file type should be based on the file extension. + */ + if (!wasPreserved && NState_GetModPreserveTypeExtended(pState)) { + InterpretExtension(pState, livePathStr, &pDetails->fileType, + &pDetails->extraType); + } + + /* + * Check for other unpleasantness, such as a leading fssep. + */ + 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)); + } + + /* + * Remove leading "./". + */ + while (livePathStr[0] == '.' && + livePathStr[1] == NState_GetSystemPathSeparator(pState)) + { + /* slide it down, len is strlen +1 (for null) -2 (dropping two chars) */ + memmove(livePathStr, livePathStr+2, strlen(livePathStr)-1); + } + + /* + * If there's a "/../" present anywhere in the name, junk everything + * but the filename. + * + * This won't catch "foo/bar/..", but that should've been caught as + * a directory anyway. + */ + slashDotDotSlash[0] = NState_GetSystemPathSeparator(pState); + slashDotDotSlash[3] = NState_GetSystemPathSeparator(pState); + if ((livePathStr[0] == '.' && livePathStr[1] == '.') || + (strstr(livePathStr, slashDotDotSlash) != nil)) + { + DBUG(("Found dot dot in '%s', keeping only filename\n", livePathStr)); + doJunk = true; + } + + /* + * If "junk paths" is set, drop everything before the last fssep char. + */ + if (NState_GetModJunkPaths(pState) || doJunk) { + char* lastFssep; + lastFssep = strrchr(livePathStr, NState_GetSystemPathSeparator(pState)); + if (lastFssep != nil) { + assert(*(lastFssep+1) != '\0'); /* should already have been caught*/ + memmove(livePathStr, lastFssep+1, strlen(lastFssep+1)+1); + } + } + +/*bail:*/ + return kNuErrNone; +} +#endif + + +/* + * Do the system-independent part of the file add, including things like + * adding comments. + */ +NuError +DoAddFile(NulibState* pState, NuArchive* pArchive, const char* pathname, + const NuFileDetails* pDetails) +{ + NuError err; + NuRecordIdx recordIdx = 0; + + err = NuAddFile(pArchive, pathname, pDetails, false, &recordIdx); + + if (err == kNuErrNone) { + NState_IncMatchCount(pState); + } else if (err == kNuErrSkipped) { + /* "maybe overwrite" UI causes this if user declines */ + err = kNuErrNone; + goto bail; + } else if (err == kNuErrNotNewer) { + /* if we were expecting this, it's okay */ + if (NState_GetModFreshen(pState) || NState_GetModUpdate(pState)) { + printf("SKIP older file: %s\n", pathname); + err = kNuErrNone; + goto bail; + } + } else if (err == kNuErrDuplicateNotFound) { + /* if we were expecting this, it's okay */ + if (NState_GetModFreshen(pState)) { + printf("SKIP file not in archive: %s\n", pathname); + err = kNuErrNone; + goto bail; + } + } else if (err == kNuErrRecordExists) { + printf("FAIL same filename added twice: '%s'\n", + NState_GetTempPathnameBuf(pState)); + goto bail_quiet; + } + if (err != kNuErrNone) + goto bail; + + /* add a one-line comment if requested */ + if (NState_GetModComments(pState)) { + char* comment; + + DBUG(("Preparing comment for recordIdx=%ld\n", recordIdx)); + assert(recordIdx != 0); + comment = GetSimpleComment(pState, pathname, kDefaultCommentLen); + if (comment != nil) { + NuDataSource* pDataSource; + + err = NuCreateDataSourceForBuffer(kNuThreadFormatUncompressed, + true, kDefaultCommentLen, (unsigned char*)comment, 0, + strlen(comment), &pDataSource); + if (err != kNuErrNone) { + ReportError(err, "comment buffer create failed"); + Free(comment); + err = kNuErrNone; /* oh well */ + } else { + comment = nil; /* now owned by the data source */ + err = NuAddThread(pArchive, recordIdx, kNuThreadIDComment, + pDataSource, nil); + if (err != kNuErrNone) { + ReportError(err, "comment thread add failed"); + NuFreeDataSource(pDataSource); + err = kNuErrNone; /* oh well */ + } else { + pDataSource = nil; /* now owned by NufxLib */ + } + } + } + } + +bail: + if (err != kNuErrNone) + ReportError(err, "Unable to add file"); +bail_quiet: + return err; +} + + +#if defined(UNIX_LIKE) +static NuError UNIXAddFile(NulibState* pState, NuArchive* pArchive, + const char* pathname); + +/* + * UNIX-style recursive directory descent. Scan the contents of a directory. + * If a subdirectory is found, follow it; otherwise, call UNIXAddFile to + * add the file. + */ +static NuError +UNIXAddDirectory(NulibState* pState, NuArchive* pArchive, const char* dirName) +{ + NuError err = kNuErrNone; + DIR* dirp = nil; + DIR_TYPE* entry; + char nbuf[MAX_PATH_LEN]; /* malloc might be better; this soaks stack */ + char fssep; + int len; + + assert(pState != nil); + assert(pArchive != nil); + assert(dirName != nil); + + DBUG(("+++ DESCEND: '%s'\n", dirName)); + + dirp = opendir(dirName); + if (dirp == nil) { + if (errno == ENOTDIR) + err = kNuErrNotDir; + else + err = errno ? errno : kNuErrOpenDir; + ReportError(err, "failed on '%s'", dirName); + goto bail; + } + + fssep = NState_GetSystemPathSeparator(pState); + + /* could use readdir_r, but we don't care about reentrancy here */ + while ((entry = readdir(dirp)) != nil) { + /* skip the dotsies */ + if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) + continue; + + len = strlen(dirName); + if (len + DIR_NAME_LEN(entry) +2 > MAX_PATH_LEN) { + err = kNuErrInternal; + ReportError(err, "Filename exceeds %d bytes: %s%c%s", + MAX_PATH_LEN, dirName, fssep, entry->d_name); + goto bail; + } + + /* form the new name, inserting an fssep if needed */ + strcpy(nbuf, dirName); + if (dirName[len-1] != fssep) + nbuf[len++] = fssep; + strcpy(nbuf+len, entry->d_name); + + err = UNIXAddFile(pState, pArchive, nbuf); + if (err != kNuErrNone) + goto bail; + } + +bail: + if (dirp != nil) + (void)closedir(dirp); + return err; +} + +/* + * Add a file to the list we're adding to the archive. + * + * If the file is a directory, and we allow recursing into subdirectories, + * this calls UNIXAddDirectory. If we don't allow recursion, this just + * returns without an error. + * + * Returns with an error if the file doesn't exist or isn't readable. + */ +static NuError +UNIXAddFile(NulibState* pState, NuArchive* pArchive, const char* pathname) +{ + NuError err = kNuErrNone; + Boolean exists, isDir, isReadable; + NuFileDetails details; + struct stat sb; + + assert(pState != nil); + assert(pArchive != nil); + assert(pathname != nil); + + err = CheckFileStatus(pathname, &sb, &exists, &isReadable, &isDir); + if (err != kNuErrNone) { + ReportError(err, "unexpected error while examining '%s'", pathname); + goto bail; + } + + if (!exists) { + err = kNuErrFileNotFound; + ReportError(err, "couldn't find '%s'", pathname); + goto bail; + } + if (!isReadable) { + ReportError(kNuErrNone, "file '%s' isn't readable", pathname); + err = kNuErrFileNotReadable; + goto bail; + } + if (isDir) { + if (NState_GetModRecurse(pState)) + err = UNIXAddDirectory(pState, pArchive, pathname); + goto bail_quiet; + } + + /* + * We've found a file that we want to add. We need to decide what + * filetype and auxtype it has, and whether or not it's actually the + * resource fork of another file. + */ + DBUG(("+++ ADD '%s'\n", pathname)); + + err = SetFileDetails(pState, pathname, &sb, &details); + if (err != kNuErrNone) + goto bail; + + err = DoAddFile(pState, pArchive, pathname, &details); + if (err != kNuErrNone) + goto bail_quiet; + +bail: + if (err != kNuErrNone) + ReportError(err, "Unable to add file"); +bail_quiet: + return err; +} + +#elif defined(WINDOWS_LIKE) + +/* + * Directory structure and functions, based on zDIR in Info-Zip sources. + */ +typedef struct Win32dirent { + char d_attr; + char d_name[MAX_PATH_LEN]; + int d_first; + HANDLE d_hFindFile; +} Win32dirent; + +static const char* kWildMatchAll = "*.*"; + +/* + * Prepare a directory for reading. + */ +static Win32dirent* +OpenDir(const char* name) +{ + Win32dirent* dir = nil; + char* tmpStr = nil; + char* cp; + WIN32_FIND_DATA fnd; + + dir = Malloc(sizeof(*dir)); + tmpStr = Malloc(strlen(name) + (2 + sizeof(kWildMatchAll))); + if (dir == nil || tmpStr == nil) + goto failed; + + strcpy(tmpStr, name); + cp = tmpStr + strlen(tmpStr); + + /* don't end in a colon (e.g. "C:") */ + if ((cp - tmpStr) > 0 && strrchr(tmpStr, ':') == (cp - 1)) + *cp++ = '.'; + /* must end in a slash */ + if ((cp - tmpStr) > 0 && strrchr(tmpStr, PATH_SEP) != (cp - 1)) + *cp++ = PATH_SEP; + + strcpy(cp, kWildMatchAll); + + dir->d_hFindFile = FindFirstFile(tmpStr, &fnd); + if (dir->d_hFindFile == INVALID_HANDLE_VALUE) + goto failed; + + strcpy(dir->d_name, fnd.cFileName); + dir->d_attr = (uchar) fnd.dwFileAttributes; + dir->d_first = 1; + +bail: + Free(tmpStr); + return dir; + +failed: + Free(dir); + dir = nil; + goto bail; +} + +/* + * Get an entry from an open directory. + * + * Returns a nil pointer after the last entry has been read. + */ +static Win32dirent* +ReadDir(Win32dirent* dir) +{ + if (dir->d_first) + dir->d_first = 0; + else { + WIN32_FIND_DATA fnd; + + if (!FindNextFile(dir->d_hFindFile, &fnd)) + return nil; + strcpy(dir->d_name, fnd.cFileName); + dir->d_attr = (uchar) fnd.dwFileAttributes; + } + + return dir; +} + +/* + * Close a directory. + */ +static void +CloseDir(Win32dirent* dir) +{ + if (dir == nil) + return; + + FindClose(dir->d_hFindFile); + Free(dir); +} + + +/* might as well blend in with the UNIX version */ +#define DIR_NAME_LEN(dirent) ((int)strlen((dirent)->d_name)) + +static NuError Win32AddFile(NulibState* pState, NuArchive* pArchive, + const char* pathname); + + +/* + * Win32 recursive directory descent. Scan the contents of a directory. + * If a subdirectory is found, follow it; otherwise, call Win32AddFile to + * add the file. + */ +static NuError +Win32AddDirectory(NulibState* pState, NuArchive* pArchive, const char* dirName) +{ + NuError err = kNuErrNone; + Win32dirent* dirp = nil; + Win32dirent* entry; + char nbuf[MAX_PATH_LEN]; /* malloc might be better; this soaks stack */ + char fssep; + int len; + + assert(pState != nil); + assert(pArchive != nil); + assert(dirName != nil); + + DBUG(("+++ DESCEND: '%s'\n", dirName)); + + dirp = OpenDir(dirName); + if (dirp == nil) { + if (errno == ENOTDIR) + err = kNuErrNotDir; + else + err = errno ? errno : kNuErrOpenDir; + ReportError(err, "failed on '%s'", dirName); + goto bail; + } + + fssep = NState_GetSystemPathSeparator(pState); + + /* could use readdir_r, but we don't care about reentrancy here */ + while ((entry = ReadDir(dirp)) != nil) { + /* skip the dotsies */ + if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) + continue; + + len = strlen(dirName); + if (len + DIR_NAME_LEN(entry) +2 > MAX_PATH_LEN) { + err = kNuErrInternal; + ReportError(err, "Filename exceeds %d bytes: %s%c%s", + MAX_PATH_LEN, dirName, fssep, entry->d_name); + goto bail; + } + + /* form the new name, inserting an fssep if needed */ + strcpy(nbuf, dirName); + if (dirName[len-1] != fssep) + nbuf[len++] = fssep; + strcpy(nbuf+len, entry->d_name); + + err = Win32AddFile(pState, pArchive, nbuf); + if (err != kNuErrNone) + goto bail; + } + +bail: + if (dirp != nil) + (void)CloseDir(dirp); + return err; +} + +/* + * Add a file to the list we're adding to the archive. + * + * I haven't figured out the recursive tree traversal stuff yet, so for + * now this ignores directories. + * + * Returns with an error if the file doesn't exist or isn't readable. + */ +static NuError +Win32AddFile(NulibState* pState, NuArchive* pArchive, const char* pathname) +{ + NuError err = kNuErrNone; + Boolean exists, isDir, isReadable; + NuFileDetails details; + struct stat sb; + + assert(pState != nil); + assert(pArchive != nil); + assert(pathname != nil); + + err = CheckFileStatus(pathname, &sb, &exists, &isReadable, &isDir); + if (err != kNuErrNone) { + ReportError(err, "unexpected error while examining '%s'", pathname); + goto bail; + } + + if (!exists) { + err = kNuErrFileNotFound; + ReportError(err, "couldn't find '%s'", pathname); + goto bail; + } + if (!isReadable) { + ReportError(kNuErrNone, "file '%s' isn't readable", pathname); + err = kNuErrFileNotReadable; + goto bail; + } + if (isDir) { + if (NState_GetModRecurse(pState)) + err = Win32AddDirectory(pState, pArchive, pathname); + goto bail_quiet; + } + + /* + * We've found a file that we want to add. We need to decide what + * filetype and auxtype it has, and whether or not it's actually the + * resource fork of another file. + */ + DBUG(("+++ ADD '%s'\n", pathname)); + + err = SetFileDetails(pState, pathname, &sb, &details); + if (err != kNuErrNone) + goto bail; + + err = DoAddFile(pState, pArchive, pathname, &details); + if (err != kNuErrNone) + goto bail_quiet; + +bail: + if (err != kNuErrNone) + ReportError(err, "Unable to add file"); +bail_quiet: + return err; +} + +#else +# error "Port this (AddFile/AddDirectory)" +#endif + + +/* + * External entry point; just calls the system-specific version. + * + * [ I figure the GS/OS version will want to pass a copy of the file + * info from the GSOSAddDirectory function back into GSOSAddFile, so we'd + * want to call it from here with a nil pointer indicating that we + * don't yet have the file info. That way we can get the file info + * from the directory read call and won't have to check it again in + * GSOSAddFile. ] + */ +NuError +AddFile(NulibState* pState, NuArchive* pArchive, const char* pathname) +{ +#if defined(UNIX_LIKE) + return UNIXAddFile(pState, pArchive, pathname); +#elif defined(WINDOWS_LIKE) + return Win32AddFile(pState, pArchive, pathname); +#else + #error "Port this" +#endif +} + diff --git a/nulib2/config.guess b/nulib2/config.guess new file mode 100644 index 0000000..cca6757 --- /dev/null +++ b/nulib2/config.guess @@ -0,0 +1,1088 @@ +#! /bin/sh +# Attempt to guess a canonical system name. +# Copyright (C) 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999 +# Free Software Foundation, Inc. +# +# This file is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +# As a special exception to the GNU General Public License, if you +# distribute this file as part of a program that contains a +# configuration script generated by Autoconf, you may include it under +# the same distribution terms that you use for the rest of that program. + +# Written by Per Bothner . +# The master version of this file is at the FSF in /home/gd/gnu/lib. +# Please send patches to . +# +# This script attempts to guess a canonical system name similar to +# config.sub. If it succeeds, it prints the system name on stdout, and +# exits with 0. Otherwise, it exits with 1. +# +# The plan is that this can be called by configure scripts if you +# don't specify an explicit system type (host/target name). +# +# Only a few systems have been added to this list; please add others +# (but try to keep the structure clean). +# + +# Use $HOST_CC if defined. $CC may point to a cross-compiler +if test x"$CC_FOR_BUILD" = x; then + if test x"$HOST_CC" != x; then + CC_FOR_BUILD="$HOST_CC" + else + if test x"$CC" != x; then + CC_FOR_BUILD="$CC" + else + CC_FOR_BUILD=cc + fi + fi +fi + + +# This is needed to find uname on a Pyramid OSx when run in the BSD universe. +# (ghazi@noc.rutgers.edu 8/24/94.) +if (test -f /.attbin/uname) >/dev/null 2>&1 ; then + PATH=$PATH:/.attbin ; export PATH +fi + +UNAME_MACHINE=`(uname -m) 2>/dev/null` || UNAME_MACHINE=unknown +UNAME_RELEASE=`(uname -r) 2>/dev/null` || UNAME_RELEASE=unknown +UNAME_SYSTEM=`(uname -s) 2>/dev/null` || UNAME_SYSTEM=unknown +UNAME_VERSION=`(uname -v) 2>/dev/null` || UNAME_VERSION=unknown + +dummy=dummy-$$ +trap 'rm -f $dummy.c $dummy.o $dummy; exit 1' 1 2 15 + +# Note: order is significant - the case branches are not exclusive. + +case "${UNAME_MACHINE}:${UNAME_SYSTEM}:${UNAME_RELEASE}:${UNAME_VERSION}" in + alpha:OSF1:*:*) + if test $UNAME_RELEASE = "V4.0"; then + UNAME_RELEASE=`/usr/sbin/sizer -v | awk '{print $3}'` + fi + # A Vn.n version is a released version. + # A Tn.n version is a released field test version. + # A Xn.n version is an unreleased experimental baselevel. + # 1.2 uses "1.2" for uname -r. + cat <$dummy.s + .globl main + .ent main +main: + .frame \$30,0,\$26,0 + .prologue 0 + .long 0x47e03d80 # implver $0 + lda \$2,259 + .long 0x47e20c21 # amask $2,$1 + srl \$1,8,\$2 + sll \$2,2,\$2 + sll \$0,3,\$0 + addl \$1,\$0,\$0 + addl \$2,\$0,\$0 + ret \$31,(\$26),1 + .end main +EOF + $CC_FOR_BUILD $dummy.s -o $dummy 2>/dev/null + if test "$?" = 0 ; then + ./$dummy + case "$?" in + 7) + UNAME_MACHINE="alpha" + ;; + 15) + UNAME_MACHINE="alphaev5" + ;; + 14) + UNAME_MACHINE="alphaev56" + ;; + 10) + UNAME_MACHINE="alphapca56" + ;; + 16) + UNAME_MACHINE="alphaev6" + ;; + esac + fi + rm -f $dummy.s $dummy + echo ${UNAME_MACHINE}-dec-osf`echo ${UNAME_RELEASE} | sed -e 's/^[VTX]//' | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz'` + exit 0 ;; + Alpha\ *:Windows_NT*:*) + # How do we know it's Interix rather than the generic POSIX subsystem? + # Should we change UNAME_MACHINE based on the output of uname instead + # of the specific Alpha model? + echo alpha-pc-interix + exit 0 ;; + 21064:Windows_NT:50:3) + echo alpha-dec-winnt3.5 + exit 0 ;; + Amiga*:UNIX_System_V:4.0:*) + echo m68k-cbm-sysv4 + exit 0;; + amiga:NetBSD:*:*) + echo m68k-cbm-netbsd${UNAME_RELEASE} + exit 0 ;; + amiga:OpenBSD:*:*) + echo m68k-unknown-openbsd${UNAME_RELEASE} + exit 0 ;; + *:[Aa]miga[Oo][Ss]:*:*) + echo ${UNAME_MACHINE}-unknown-amigaos + exit 0 ;; + arc64:OpenBSD:*:*) + echo mips64el-unknown-openbsd${UNAME_RELEASE} + exit 0 ;; + arc:OpenBSD:*:*) + echo mipsel-unknown-openbsd${UNAME_RELEASE} + exit 0 ;; + hkmips:OpenBSD:*:*) + echo mips-unknown-openbsd${UNAME_RELEASE} + exit 0 ;; + pmax:OpenBSD:*:*) + echo mipsel-unknown-openbsd${UNAME_RELEASE} + exit 0 ;; + sgi:OpenBSD:*:*) + echo mips-unknown-openbsd${UNAME_RELEASE} + exit 0 ;; + wgrisc:OpenBSD:*:*) + echo mipsel-unknown-openbsd${UNAME_RELEASE} + exit 0 ;; + arm:RISC*:1.[012]*:*|arm:riscix:1.[012]*:*) + echo arm-acorn-riscix${UNAME_RELEASE} + exit 0;; + arm32:NetBSD:*:*) + echo arm-unknown-netbsd`echo ${UNAME_RELEASE}|sed -e 's/[-_].*/\./'` + exit 0 ;; + SR2?01:HI-UX/MPP:*:*) + echo hppa1.1-hitachi-hiuxmpp + exit 0;; + Pyramid*:OSx*:*:* | MIS*:OSx*:*:* | MIS*:SMP_DC-OSx*:*:*) + # akee@wpdis03.wpafb.af.mil (Earle F. Ake) contributed MIS and NILE. + if test "`(/bin/universe) 2>/dev/null`" = att ; then + echo pyramid-pyramid-sysv3 + else + echo pyramid-pyramid-bsd + fi + exit 0 ;; + NILE*:*:*:dcosx) + echo pyramid-pyramid-svr4 + exit 0 ;; + sun4H:SunOS:5.*:*) + echo sparc-hal-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` + exit 0 ;; + sun4*:SunOS:5.*:* | tadpole*:SunOS:5.*:*) + echo sparc-sun-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` + exit 0 ;; + i86pc:SunOS:5.*:*) + echo i386-pc-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` + exit 0 ;; + sun4*:SunOS:6*:*) + # According to config.sub, this is the proper way to canonicalize + # SunOS6. Hard to guess exactly what SunOS6 will be like, but + # it's likely to be more like Solaris than SunOS4. + echo sparc-sun-solaris3`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` + exit 0 ;; + sun4*:SunOS:*:*) + case "`/usr/bin/arch -k`" in + Series*|S4*) + UNAME_RELEASE=`uname -v` + ;; + esac + # Japanese Language versions have a version number like `4.1.3-JL'. + echo sparc-sun-sunos`echo ${UNAME_RELEASE}|sed -e 's/-/_/'` + exit 0 ;; + sun3*:SunOS:*:*) + echo m68k-sun-sunos${UNAME_RELEASE} + exit 0 ;; + sun*:*:4.2BSD:*) + UNAME_RELEASE=`(head -1 /etc/motd | awk '{print substr($5,1,3)}') 2>/dev/null` + test "x${UNAME_RELEASE}" = "x" && UNAME_RELEASE=3 + case "`/bin/arch`" in + sun3) + echo m68k-sun-sunos${UNAME_RELEASE} + ;; + sun4) + echo sparc-sun-sunos${UNAME_RELEASE} + ;; + esac + exit 0 ;; + aushp:SunOS:*:*) + echo sparc-auspex-sunos${UNAME_RELEASE} + exit 0 ;; + atari*:NetBSD:*:*) + echo m68k-atari-netbsd${UNAME_RELEASE} + exit 0 ;; + atari*:OpenBSD:*:*) + echo m68k-unknown-openbsd${UNAME_RELEASE} + exit 0 ;; + # The situation for MiNT is a little confusing. The machine name + # can be virtually everything (everything which is not + # "atarist" or "atariste" at least should have a processor + # > m68000). The system name ranges from "MiNT" over "FreeMiNT" + # to the lowercase version "mint" (or "freemint"). Finally + # the system name "TOS" denotes a system which is actually not + # MiNT. But MiNT is downward compatible to TOS, so this should + # be no problem. + atarist[e]:*MiNT:*:* | atarist[e]:*mint:*:* | atarist[e]:*TOS:*:*) + echo m68k-atari-mint${UNAME_RELEASE} + exit 0 ;; + atari*:*MiNT:*:* | atari*:*mint:*:* | atarist[e]:*TOS:*:*) + echo m68k-atari-mint${UNAME_RELEASE} + exit 0 ;; + *falcon*:*MiNT:*:* | *falcon*:*mint:*:* | *falcon*:*TOS:*:*) + echo m68k-atari-mint${UNAME_RELEASE} + exit 0 ;; + milan*:*MiNT:*:* | milan*:*mint:*:* | *milan*:*TOS:*:*) + echo m68k-milan-mint${UNAME_RELEASE} + exit 0 ;; + hades*:*MiNT:*:* | hades*:*mint:*:* | *hades*:*TOS:*:*) + echo m68k-hades-mint${UNAME_RELEASE} + exit 0 ;; + *:*MiNT:*:* | *:*mint:*:* | *:*TOS:*:*) + echo m68k-unknown-mint${UNAME_RELEASE} + exit 0 ;; + sun3*:NetBSD:*:*) + echo m68k-sun-netbsd${UNAME_RELEASE} + exit 0 ;; + sun3*:OpenBSD:*:*) + echo m68k-unknown-openbsd${UNAME_RELEASE} + exit 0 ;; + mac68k:NetBSD:*:*) + echo m68k-apple-netbsd${UNAME_RELEASE} + exit 0 ;; + mac68k:OpenBSD:*:*) + echo m68k-unknown-openbsd${UNAME_RELEASE} + exit 0 ;; + mvme68k:OpenBSD:*:*) + echo m68k-unknown-openbsd${UNAME_RELEASE} + exit 0 ;; + mvme88k:OpenBSD:*:*) + echo m88k-unknown-openbsd${UNAME_RELEASE} + exit 0 ;; + powerpc:machten:*:*) + echo powerpc-apple-machten${UNAME_RELEASE} + exit 0 ;; + macppc:NetBSD:*:*) + echo powerpc-apple-netbsd${UNAME_RELEASE} + exit 0 ;; + RISC*:Mach:*:*) + echo mips-dec-mach_bsd4.3 + exit 0 ;; + RISC*:ULTRIX:*:*) + echo mips-dec-ultrix${UNAME_RELEASE} + exit 0 ;; + VAX*:ULTRIX*:*:*) + echo vax-dec-ultrix${UNAME_RELEASE} + exit 0 ;; + 2020:CLIX:*:* | 2430:CLIX:*:*) + echo clipper-intergraph-clix${UNAME_RELEASE} + exit 0 ;; + mips:*:*:UMIPS | mips:*:*:RISCos) + sed 's/^ //' << EOF >$dummy.c +#ifdef __cplusplus + int main (int argc, char *argv[]) { +#else + int main (argc, argv) int argc; char *argv[]; { +#endif + #if defined (host_mips) && defined (MIPSEB) + #if defined (SYSTYPE_SYSV) + printf ("mips-mips-riscos%ssysv\n", argv[1]); exit (0); + #endif + #if defined (SYSTYPE_SVR4) + printf ("mips-mips-riscos%ssvr4\n", argv[1]); exit (0); + #endif + #if defined (SYSTYPE_BSD43) || defined(SYSTYPE_BSD) + printf ("mips-mips-riscos%sbsd\n", argv[1]); exit (0); + #endif + #endif + exit (-1); + } +EOF + $CC_FOR_BUILD $dummy.c -o $dummy \ + && ./$dummy `echo "${UNAME_RELEASE}" | sed -n 's/\([0-9]*\).*/\1/p'` \ + && rm $dummy.c $dummy && exit 0 + rm -f $dummy.c $dummy + echo mips-mips-riscos${UNAME_RELEASE} + exit 0 ;; + Night_Hawk:Power_UNIX:*:*) + echo powerpc-harris-powerunix + exit 0 ;; + m88k:CX/UX:7*:*) + echo m88k-harris-cxux7 + exit 0 ;; + m88k:*:4*:R4*) + echo m88k-motorola-sysv4 + exit 0 ;; + m88k:*:3*:R3*) + echo m88k-motorola-sysv3 + exit 0 ;; + AViiON:dgux:*:*) + # DG/UX returns AViiON for all architectures + UNAME_PROCESSOR=`/usr/bin/uname -p` + if [ $UNAME_PROCESSOR = mc88100 -o $UNAME_PROCESSOR = mc88110 ] ; then + if [ ${TARGET_BINARY_INTERFACE}x = m88kdguxelfx \ + -o ${TARGET_BINARY_INTERFACE}x = x ] ; then + echo m88k-dg-dgux${UNAME_RELEASE} + else + echo m88k-dg-dguxbcs${UNAME_RELEASE} + fi + else echo i586-dg-dgux${UNAME_RELEASE} + fi + exit 0 ;; + M88*:DolphinOS:*:*) # DolphinOS (SVR3) + echo m88k-dolphin-sysv3 + exit 0 ;; + M88*:*:R3*:*) + # Delta 88k system running SVR3 + echo m88k-motorola-sysv3 + exit 0 ;; + XD88*:*:*:*) # Tektronix XD88 system running UTekV (SVR3) + echo m88k-tektronix-sysv3 + exit 0 ;; + Tek43[0-9][0-9]:UTek:*:*) # Tektronix 4300 system running UTek (BSD) + echo m68k-tektronix-bsd + exit 0 ;; + *:IRIX*:*:*) + echo mips-sgi-irix`echo ${UNAME_RELEASE}|sed -e 's/-/_/g'` + exit 0 ;; + ????????:AIX?:[12].1:2) # AIX 2.2.1 or AIX 2.1.1 is RT/PC AIX. + echo romp-ibm-aix # uname -m gives an 8 hex-code CPU id + exit 0 ;; # Note that: echo "'`uname -s`'" gives 'AIX ' + i?86:AIX:*:*) + echo i386-ibm-aix + exit 0 ;; + *:AIX:2:3) + if grep bos325 /usr/include/stdio.h >/dev/null 2>&1; then + sed 's/^ //' << EOF >$dummy.c + #include + + main() + { + if (!__power_pc()) + exit(1); + puts("powerpc-ibm-aix3.2.5"); + exit(0); + } +EOF + $CC_FOR_BUILD $dummy.c -o $dummy && ./$dummy && rm $dummy.c $dummy && exit 0 + rm -f $dummy.c $dummy + echo rs6000-ibm-aix3.2.5 + elif grep bos324 /usr/include/stdio.h >/dev/null 2>&1; then + echo rs6000-ibm-aix3.2.4 + else + echo rs6000-ibm-aix3.2 + fi + exit 0 ;; + *:AIX:*:4) + IBM_CPU_ID=`/usr/sbin/lsdev -C -c processor -S available | head -1 | awk '{ print $1 }'` + if /usr/sbin/lsattr -EHl ${IBM_CPU_ID} | grep POWER >/dev/null 2>&1; then + IBM_ARCH=rs6000 + else + IBM_ARCH=powerpc + fi + if [ -x /usr/bin/oslevel ] ; then + IBM_REV=`/usr/bin/oslevel` + else + IBM_REV=4.${UNAME_RELEASE} + fi + echo ${IBM_ARCH}-ibm-aix${IBM_REV} + exit 0 ;; + *:AIX:*:*) + echo rs6000-ibm-aix + exit 0 ;; + ibmrt:4.4BSD:*|romp-ibm:BSD:*) + echo romp-ibm-bsd4.4 + exit 0 ;; + ibmrt:*BSD:*|romp-ibm:BSD:*) # covers RT/PC NetBSD and + echo romp-ibm-bsd${UNAME_RELEASE} # 4.3 with uname added to + exit 0 ;; # report: romp-ibm BSD 4.3 + *:BOSX:*:*) + echo rs6000-bull-bosx + exit 0 ;; + DPX/2?00:B.O.S.:*:*) + echo m68k-bull-sysv3 + exit 0 ;; + 9000/[34]??:4.3bsd:1.*:*) + echo m68k-hp-bsd + exit 0 ;; + hp300:4.4BSD:*:* | 9000/[34]??:4.3bsd:2.*:*) + echo m68k-hp-bsd4.4 + exit 0 ;; + 9000/[34678]??:HP-UX:*:*) + case "${UNAME_MACHINE}" in + 9000/31? ) HP_ARCH=m68000 ;; + 9000/[34]?? ) HP_ARCH=m68k ;; + 9000/[678][0-9][0-9]) + sed 's/^ //' << EOF >$dummy.c + #include + #include + + int main () + { + #if defined(_SC_KERNEL_BITS) + long bits = sysconf(_SC_KERNEL_BITS); + #endif + long cpu = sysconf (_SC_CPU_VERSION); + + switch (cpu) + { + case CPU_PA_RISC1_0: puts ("hppa1.0"); break; + case CPU_PA_RISC1_1: puts ("hppa1.1"); break; + case CPU_PA_RISC2_0: + #if defined(_SC_KERNEL_BITS) + switch (bits) + { + case 64: puts ("hppa2.0w"); break; + case 32: puts ("hppa2.0n"); break; + default: puts ("hppa2.0"); break; + } break; + #else /* !defined(_SC_KERNEL_BITS) */ + puts ("hppa2.0"); break; + #endif + default: puts ("hppa1.0"); break; + } + exit (0); + } +EOF + ($CC_FOR_BUILD $dummy.c -o $dummy 2>/dev/null ) && HP_ARCH=`./$dummy` + rm -f $dummy.c $dummy + esac + HPUX_REV=`echo ${UNAME_RELEASE}|sed -e 's/[^.]*.[0B]*//'` + echo ${HP_ARCH}-hp-hpux${HPUX_REV} + exit 0 ;; + 3050*:HI-UX:*:*) + sed 's/^ //' << EOF >$dummy.c + #include + int + main () + { + long cpu = sysconf (_SC_CPU_VERSION); + /* The order matters, because CPU_IS_HP_MC68K erroneously returns + true for CPU_PA_RISC1_0. CPU_IS_PA_RISC returns correct + results, however. */ + if (CPU_IS_PA_RISC (cpu)) + { + switch (cpu) + { + case CPU_PA_RISC1_0: puts ("hppa1.0-hitachi-hiuxwe2"); break; + case CPU_PA_RISC1_1: puts ("hppa1.1-hitachi-hiuxwe2"); break; + case CPU_PA_RISC2_0: puts ("hppa2.0-hitachi-hiuxwe2"); break; + default: puts ("hppa-hitachi-hiuxwe2"); break; + } + } + else if (CPU_IS_HP_MC68K (cpu)) + puts ("m68k-hitachi-hiuxwe2"); + else puts ("unknown-hitachi-hiuxwe2"); + exit (0); + } +EOF + $CC_FOR_BUILD $dummy.c -o $dummy && ./$dummy && rm $dummy.c $dummy && exit 0 + rm -f $dummy.c $dummy + echo unknown-hitachi-hiuxwe2 + exit 0 ;; + 9000/7??:4.3bsd:*:* | 9000/8?[79]:4.3bsd:*:* ) + echo hppa1.1-hp-bsd + exit 0 ;; + 9000/8??:4.3bsd:*:*) + echo hppa1.0-hp-bsd + exit 0 ;; + *9??*:MPE/iX:*:*) + echo hppa1.0-hp-mpeix + exit 0 ;; + hp7??:OSF1:*:* | hp8?[79]:OSF1:*:* ) + echo hppa1.1-hp-osf + exit 0 ;; + hp8??:OSF1:*:*) + echo hppa1.0-hp-osf + exit 0 ;; + i?86:OSF1:*:*) + if [ -x /usr/sbin/sysversion ] ; then + echo ${UNAME_MACHINE}-unknown-osf1mk + else + echo ${UNAME_MACHINE}-unknown-osf1 + fi + exit 0 ;; + parisc*:Lites*:*:*) + echo hppa1.1-hp-lites + exit 0 ;; + hppa*:OpenBSD:*:*) + echo hppa-unknown-openbsd + exit 0 ;; + C1*:ConvexOS:*:* | convex:ConvexOS:C1*:*) + echo c1-convex-bsd + exit 0 ;; + C2*:ConvexOS:*:* | convex:ConvexOS:C2*:*) + if getsysinfo -f scalar_acc + then echo c32-convex-bsd + else echo c2-convex-bsd + fi + exit 0 ;; + C34*:ConvexOS:*:* | convex:ConvexOS:C34*:*) + echo c34-convex-bsd + exit 0 ;; + C38*:ConvexOS:*:* | convex:ConvexOS:C38*:*) + echo c38-convex-bsd + exit 0 ;; + C4*:ConvexOS:*:* | convex:ConvexOS:C4*:*) + echo c4-convex-bsd + exit 0 ;; + CRAY*X-MP:*:*:*) + echo xmp-cray-unicos + exit 0 ;; + CRAY*Y-MP:*:*:*) + echo ymp-cray-unicos${UNAME_RELEASE} + exit 0 ;; + CRAY*[A-Z]90:*:*:*) + echo ${UNAME_MACHINE}-cray-unicos${UNAME_RELEASE} \ + | sed -e 's/CRAY.*\([A-Z]90\)/\1/' \ + -e y/ABCDEFGHIJKLMNOPQRSTUVWXYZ/abcdefghijklmnopqrstuvwxyz/ + exit 0 ;; + CRAY*TS:*:*:*) + echo t90-cray-unicos${UNAME_RELEASE} + exit 0 ;; + CRAY*T3E:*:*:*) + echo alpha-cray-unicosmk${UNAME_RELEASE} + exit 0 ;; + CRAY-2:*:*:*) + echo cray2-cray-unicos + exit 0 ;; + F300:UNIX_System_V:*:*) + FUJITSU_SYS=`uname -p | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz' | sed -e 's/\///'` + FUJITSU_REL=`echo ${UNAME_RELEASE} | sed -e 's/ /_/'` + echo "f300-fujitsu-${FUJITSU_SYS}${FUJITSU_REL}" + exit 0 ;; + F301:UNIX_System_V:*:*) + echo f301-fujitsu-uxpv`echo $UNAME_RELEASE | sed 's/ .*//'` + exit 0 ;; + hp3[0-9][05]:NetBSD:*:*) + echo m68k-hp-netbsd${UNAME_RELEASE} + exit 0 ;; + hp300:OpenBSD:*:*) + echo m68k-unknown-openbsd${UNAME_RELEASE} + exit 0 ;; + i?86:BSD/386:*:* | i?86:BSD/OS:*:*) + echo ${UNAME_MACHINE}-pc-bsdi${UNAME_RELEASE} + exit 0 ;; + sparc*:BSD/OS:*:*) + echo sparc-unknown-bsdi${UNAME_RELEASE} + exit 0 ;; + *:BSD/OS:*:*) + echo ${UNAME_MACHINE}-unknown-bsdi${UNAME_RELEASE} + exit 0 ;; + *:FreeBSD:*:*) + if test -x /usr/bin/objformat; then + if test "elf" = "`/usr/bin/objformat`"; then + echo ${UNAME_MACHINE}-unknown-freebsdelf`echo ${UNAME_RELEASE}|sed -e 's/[-_].*//'` + exit 0 + fi + fi + echo ${UNAME_MACHINE}-unknown-freebsd`echo ${UNAME_RELEASE}|sed -e 's/[-(].*//'` + exit 0 ;; + *:NetBSD:*:*) + echo ${UNAME_MACHINE}-unknown-netbsd`echo ${UNAME_RELEASE}|sed -e 's/[-_].*/\./'` + exit 0 ;; + *:OpenBSD:*:*) + echo ${UNAME_MACHINE}-unknown-openbsd`echo ${UNAME_RELEASE}|sed -e 's/[-_].*/\./'` + exit 0 ;; + i*:CYGWIN*:*) + echo ${UNAME_MACHINE}-pc-cygwin + exit 0 ;; + i*:MINGW*:*) + echo ${UNAME_MACHINE}-pc-mingw32 + exit 0 ;; + i*:Windows_NT*:* | Pentium*:Windows_NT*:*) + # How do we know it's Interix rather than the generic POSIX subsystem? + # It also conflicts with pre-2.0 versions of AT&T UWIN. Should we + # UNAME_MACHINE based on the output of uname instead of i386? + echo i386-pc-interix + exit 0 ;; + i*:UWIN*:*) + echo ${UNAME_MACHINE}-pc-uwin + exit 0 ;; + p*:CYGWIN*:*) + echo powerpcle-unknown-cygwin + exit 0 ;; + prep*:SunOS:5.*:*) + echo powerpcle-unknown-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` + exit 0 ;; + *:GNU:*:*) + echo `echo ${UNAME_MACHINE}|sed -e 's,[-/].*$,,'`-unknown-gnu`echo ${UNAME_RELEASE}|sed -e 's,/.*$,,'` + exit 0 ;; + *:Linux:*:*) + # uname on the ARM produces all sorts of strangeness, and we need to + # filter it out. + case "$UNAME_MACHINE" in + armv*) UNAME_MACHINE=$UNAME_MACHINE ;; + arm* | sa110*) UNAME_MACHINE="arm" ;; + esac + + # The BFD linker knows what the default object file format is, so + # first see if it will tell us. cd to the root directory to prevent + # problems with other programs or directories called `ld' in the path. + ld_help_string=`cd /; ld --help 2>&1` + ld_supported_emulations=`echo $ld_help_string \ + | sed -ne '/supported emulations:/!d + s/[ ][ ]*/ /g + s/.*supported emulations: *// + s/ .*// + p'` + case "$ld_supported_emulations" in + *ia64) echo "${UNAME_MACHINE}-unknown-linux" ; exit 0 ;; + i?86linux) echo "${UNAME_MACHINE}-pc-linux-gnuaout" ; exit 0 ;; + i?86coff) echo "${UNAME_MACHINE}-pc-linux-gnucoff" ; exit 0 ;; + sparclinux) echo "${UNAME_MACHINE}-unknown-linux-gnuaout" ; exit 0 ;; + armlinux) echo "${UNAME_MACHINE}-unknown-linux-gnuaout" ; exit 0 ;; + m68klinux) echo "${UNAME_MACHINE}-unknown-linux-gnuaout" ; exit 0 ;; + elf32ppc | elf32ppclinux) + # Determine Lib Version + cat >$dummy.c < +#if defined(__GLIBC__) +extern char __libc_version[]; +extern char __libc_release[]; +#endif +main(argc, argv) + int argc; + char *argv[]; +{ +#if defined(__GLIBC__) + printf("%s %s\n", __libc_version, __libc_release); +#else + printf("unkown\n"); +#endif + return 0; +} +EOF + LIBC="" + $CC_FOR_BUILD $dummy.c -o $dummy 2>/dev/null + if test "$?" = 0 ; then + ./$dummy | grep 1\.99 > /dev/null + if test "$?" = 0 ; then + LIBC="libc1" + fi + fi + rm -f $dummy.c $dummy + echo powerpc-unknown-linux-gnu${LIBC} ; exit 0 ;; + esac + + if test "${UNAME_MACHINE}" = "alpha" ; then + sed 's/^ //' <$dummy.s + .globl main + .ent main + main: + .frame \$30,0,\$26,0 + .prologue 0 + .long 0x47e03d80 # implver $0 + lda \$2,259 + .long 0x47e20c21 # amask $2,$1 + srl \$1,8,\$2 + sll \$2,2,\$2 + sll \$0,3,\$0 + addl \$1,\$0,\$0 + addl \$2,\$0,\$0 + ret \$31,(\$26),1 + .end main +EOF + LIBC="" + $CC_FOR_BUILD $dummy.s -o $dummy 2>/dev/null + if test "$?" = 0 ; then + ./$dummy + case "$?" in + 7) + UNAME_MACHINE="alpha" + ;; + 15) + UNAME_MACHINE="alphaev5" + ;; + 14) + UNAME_MACHINE="alphaev56" + ;; + 10) + UNAME_MACHINE="alphapca56" + ;; + 16) + UNAME_MACHINE="alphaev6" + ;; + esac + + objdump --private-headers $dummy | \ + grep ld.so.1 > /dev/null + if test "$?" = 0 ; then + LIBC="libc1" + fi + fi + rm -f $dummy.s $dummy + echo ${UNAME_MACHINE}-unknown-linux-gnu${LIBC} ; exit 0 + elif test "${UNAME_MACHINE}" = "mips" ; then + cat >$dummy.c </dev/null && ./$dummy "${UNAME_MACHINE}" && rm $dummy.c $dummy && exit 0 + rm -f $dummy.c $dummy + else + # Either a pre-BFD a.out linker (linux-gnuoldld) + # or one that does not give us useful --help. + # GCC wants to distinguish between linux-gnuoldld and linux-gnuaout. + # If ld does not provide *any* "supported emulations:" + # that means it is gnuoldld. + echo "$ld_help_string" | grep >/dev/null 2>&1 "supported emulations:" + test $? != 0 && echo "${UNAME_MACHINE}-pc-linux-gnuoldld" && exit 0 + + case "${UNAME_MACHINE}" in + i?86) + VENDOR=pc; + ;; + *) + VENDOR=unknown; + ;; + esac + # Determine whether the default compiler is a.out or elf + cat >$dummy.c < +#ifdef __cplusplus + int main (int argc, char *argv[]) { +#else + int main (argc, argv) int argc; char *argv[]; { +#endif +#ifdef __ELF__ +# ifdef __GLIBC__ +# if __GLIBC__ >= 2 + printf ("%s-${VENDOR}-linux-gnu\n", argv[1]); +# else + printf ("%s-${VENDOR}-linux-gnulibc1\n", argv[1]); +# endif +# else + printf ("%s-${VENDOR}-linux-gnulibc1\n", argv[1]); +# endif +#else + printf ("%s-${VENDOR}-linux-gnuaout\n", argv[1]); +#endif + return 0; +} +EOF + $CC_FOR_BUILD $dummy.c -o $dummy 2>/dev/null && ./$dummy "${UNAME_MACHINE}" && rm $dummy.c $dummy && exit 0 + rm -f $dummy.c $dummy + fi ;; +# ptx 4.0 does uname -s correctly, with DYNIX/ptx in there. earlier versions +# are messed up and put the nodename in both sysname and nodename. + i?86:DYNIX/ptx:4*:*) + echo i386-sequent-sysv4 + exit 0 ;; + i?86:UNIX_SV:4.2MP:2.*) + # Unixware is an offshoot of SVR4, but it has its own version + # number series starting with 2... + # I am not positive that other SVR4 systems won't match this, + # I just have to hope. -- rms. + # Use sysv4.2uw... so that sysv4* matches it. + echo ${UNAME_MACHINE}-pc-sysv4.2uw${UNAME_VERSION} + exit 0 ;; + i?86:*:4.*:* | i?86:SYSTEM_V:4.*:*) + if grep Novell /usr/include/link.h >/dev/null 2>/dev/null; then + echo ${UNAME_MACHINE}-univel-sysv${UNAME_RELEASE} + else + echo ${UNAME_MACHINE}-pc-sysv${UNAME_RELEASE} + fi + exit 0 ;; + i?86:*:5:7*) + UNAME_REL=`(/bin/uname -X|egrep Release|sed -e 's/.*= //')` + (/bin/uname -X|egrep i80486 >/dev/null) && UNAME_MACHINE=i486 + (/bin/uname -X|egrep '^Machine.*Pentium' >/dev/null) && UNAME_MACHINE=i586 + (/bin/uname -X|egrep '^Machine.*Pent.*II' >/dev/null) && UNAME_MACHINE=i686 + (/bin/uname -X|egrep '^Machine.*Pentium Pro' >/dev/null) && UNAME_MACHINE=i585 + echo ${UNAME_MACHINE}-${UNAME_SYSTEM}${UNAME_VERSION}-sysv${UNAME_RELEASE} + exit 0 ;; + i?86:*:3.2:*) + if test -f /usr/options/cb.name; then + UNAME_REL=`sed -n 's/.*Version //p' /dev/null >/dev/null ; then + UNAME_REL=`(/bin/uname -X|egrep Release|sed -e 's/.*= //')` + (/bin/uname -X|egrep i80486 >/dev/null) && UNAME_MACHINE=i486 + (/bin/uname -X|egrep '^Machine.*Pentium' >/dev/null) \ + && UNAME_MACHINE=i586 + (/bin/uname -X|egrep '^Machine.*Pent ?II' >/dev/null) \ + && UNAME_MACHINE=i686 + (/bin/uname -X|egrep '^Machine.*Pentium Pro' >/dev/null) \ + && UNAME_MACHINE=i686 + echo ${UNAME_MACHINE}-pc-sco$UNAME_REL + else + echo ${UNAME_MACHINE}-pc-sysv32 + fi + exit 0 ;; + pc:*:*:*) + # uname -m prints for DJGPP always 'pc', but it prints nothing about + # the processor, so we play safe by assuming i386. + echo i386-pc-msdosdjgpp + exit 0 ;; + Intel:Mach:3*:*) + echo i386-pc-mach3 + exit 0 ;; + paragon:*:*:*) + echo i860-intel-osf1 + exit 0 ;; + i860:*:4.*:*) # i860-SVR4 + if grep Stardent /usr/include/sys/uadmin.h >/dev/null 2>&1 ; then + echo i860-stardent-sysv${UNAME_RELEASE} # Stardent Vistra i860-SVR4 + else # Add other i860-SVR4 vendors below as they are discovered. + echo i860-unknown-sysv${UNAME_RELEASE} # Unknown i860-SVR4 + fi + exit 0 ;; + mini*:CTIX:SYS*5:*) + # "miniframe" + echo m68010-convergent-sysv + exit 0 ;; + M68*:*:R3V[567]*:*) + test -r /sysV68 && echo 'm68k-motorola-sysv' && exit 0 ;; + 3[34]??:*:4.0:3.0 | 3[34]??,*:*:4.0:3.0 | 4850:*:4.0:3.0) + OS_REL='' + test -r /etc/.relid \ + && OS_REL=.`sed -n 's/[^ ]* [^ ]* \([0-9][0-9]\).*/\1/p' < /etc/.relid` + /bin/uname -p 2>/dev/null | grep 86 >/dev/null \ + && echo i486-ncr-sysv4.3${OS_REL} && exit 0 + /bin/uname -p 2>/dev/null | /bin/grep entium >/dev/null \ + && echo i586-ncr-sysv4.3${OS_REL} && exit 0 ;; + 3[34]??:*:4.0:* | 3[34]??,*:*:4.0:*) + /bin/uname -p 2>/dev/null | grep 86 >/dev/null \ + && echo i486-ncr-sysv4 && exit 0 ;; + m68*:LynxOS:2.*:*) + echo m68k-unknown-lynxos${UNAME_RELEASE} + exit 0 ;; + mc68030:UNIX_System_V:4.*:*) + echo m68k-atari-sysv4 + exit 0 ;; + i?86:LynxOS:2.*:* | i?86:LynxOS:3.[01]*:*) + echo i386-unknown-lynxos${UNAME_RELEASE} + exit 0 ;; + TSUNAMI:LynxOS:2.*:*) + echo sparc-unknown-lynxos${UNAME_RELEASE} + exit 0 ;; + rs6000:LynxOS:2.*:* | PowerPC:LynxOS:2.*:*) + echo rs6000-unknown-lynxos${UNAME_RELEASE} + exit 0 ;; + SM[BE]S:UNIX_SV:*:*) + echo mips-dde-sysv${UNAME_RELEASE} + exit 0 ;; + RM*:ReliantUNIX-*:*:*) + echo mips-sni-sysv4 + exit 0 ;; + RM*:SINIX-*:*:*) + echo mips-sni-sysv4 + exit 0 ;; + *:SINIX-*:*:*) + if uname -p 2>/dev/null >/dev/null ; then + UNAME_MACHINE=`(uname -p) 2>/dev/null` + echo ${UNAME_MACHINE}-sni-sysv4 + else + echo ns32k-sni-sysv + fi + exit 0 ;; + PENTIUM:CPunix:4.0*:*) # Unisys `ClearPath HMP IX 4000' SVR4/MP effort + # says + echo i586-unisys-sysv4 + exit 0 ;; + *:UNIX_System_V:4*:FTX*) + # From Gerald Hewes . + # How about differentiating between stratus architectures? -djm + echo hppa1.1-stratus-sysv4 + exit 0 ;; + *:*:*:FTX*) + # From seanf@swdc.stratus.com. + echo i860-stratus-sysv4 + exit 0 ;; + mc68*:A/UX:*:*) + echo m68k-apple-aux${UNAME_RELEASE} + exit 0 ;; + news*:NEWS-OS:*:6*) + echo mips-sony-newsos6 + exit 0 ;; + R[34]000:*System_V*:*:* | R4000:UNIX_SYSV:*:* | R*000:UNIX_SV:*:*) + if [ -d /usr/nec ]; then + echo mips-nec-sysv${UNAME_RELEASE} + else + echo mips-unknown-sysv${UNAME_RELEASE} + fi + exit 0 ;; + BeBox:BeOS:*:*) # BeOS running on hardware made by Be, PPC only. + echo powerpc-be-beos + exit 0 ;; + BeMac:BeOS:*:*) # BeOS running on Mac or Mac clone, PPC only. + echo powerpc-apple-beos + exit 0 ;; + BePC:BeOS:*:*) # BeOS running on Intel PC compatible. + echo i586-pc-beos + exit 0 ;; + SX-4:SUPER-UX:*:*) + echo sx4-nec-superux${UNAME_RELEASE} + exit 0 ;; + SX-5:SUPER-UX:*:*) + echo sx5-nec-superux${UNAME_RELEASE} + exit 0 ;; + Power*:Rhapsody:*:*) + echo powerpc-apple-rhapsody${UNAME_RELEASE} + exit 0 ;; + *:Rhapsody:*:*) + echo ${UNAME_MACHINE}-apple-rhapsody${UNAME_RELEASE} + exit 0 ;; +esac + +#echo '(No uname command or uname output not recognized.)' 1>&2 +#echo "${UNAME_MACHINE}:${UNAME_SYSTEM}:${UNAME_RELEASE}:${UNAME_VERSION}" 1>&2 + +cat >$dummy.c < +# include +#endif +main () +{ +#if defined (sony) +#if defined (MIPSEB) + /* BFD wants "bsd" instead of "newsos". Perhaps BFD should be changed, + I don't know.... */ + printf ("mips-sony-bsd\n"); exit (0); +#else +#include + printf ("m68k-sony-newsos%s\n", +#ifdef NEWSOS4 + "4" +#else + "" +#endif + ); exit (0); +#endif +#endif + +#if defined (__arm) && defined (__acorn) && defined (__unix) + printf ("arm-acorn-riscix"); exit (0); +#endif + +#if defined (hp300) && !defined (hpux) + printf ("m68k-hp-bsd\n"); exit (0); +#endif + +#if defined (NeXT) +#if !defined (__ARCHITECTURE__) +#define __ARCHITECTURE__ "m68k" +#endif + int version; + version=`(hostinfo | sed -n 's/.*NeXT Mach \([0-9]*\).*/\1/p') 2>/dev/null`; + if (version < 4) + printf ("%s-next-nextstep%d\n", __ARCHITECTURE__, version); + else + printf ("%s-next-openstep%d\n", __ARCHITECTURE__, version); + exit (0); +#endif + +#if defined (MULTIMAX) || defined (n16) +#if defined (UMAXV) + printf ("ns32k-encore-sysv\n"); exit (0); +#else +#if defined (CMU) + printf ("ns32k-encore-mach\n"); exit (0); +#else + printf ("ns32k-encore-bsd\n"); exit (0); +#endif +#endif +#endif + +#if defined (__386BSD__) + printf ("i386-pc-bsd\n"); exit (0); +#endif + +#if defined (sequent) +#if defined (i386) + printf ("i386-sequent-dynix\n"); exit (0); +#endif +#if defined (ns32000) + printf ("ns32k-sequent-dynix\n"); exit (0); +#endif +#endif + +#if defined (_SEQUENT_) + struct utsname un; + + uname(&un); + + if (strncmp(un.version, "V2", 2) == 0) { + printf ("i386-sequent-ptx2\n"); exit (0); + } + if (strncmp(un.version, "V1", 2) == 0) { /* XXX is V1 correct? */ + printf ("i386-sequent-ptx1\n"); exit (0); + } + printf ("i386-sequent-ptx\n"); exit (0); + +#endif + +#if defined (vax) +#if !defined (ultrix) + printf ("vax-dec-bsd\n"); exit (0); +#else + printf ("vax-dec-ultrix\n"); exit (0); +#endif +#endif + +#if defined (alliant) && defined (i860) + printf ("i860-alliant-bsd\n"); exit (0); +#endif + + exit (1); +} +EOF + +$CC_FOR_BUILD $dummy.c -o $dummy 2>/dev/null && ./$dummy && rm $dummy.c $dummy && exit 0 +rm -f $dummy.c $dummy + +# Apollos put the system type in the environment. + +test -d /usr/apollo && { echo ${ISP}-apollo-${SYSTYPE}; exit 0; } + +# Convex versions that predate uname can use getsysinfo(1) + +if [ -x /usr/convex/getsysinfo ] +then + case `getsysinfo -f cpu_type` in + c1*) + echo c1-convex-bsd + exit 0 ;; + c2*) + if getsysinfo -f scalar_acc + then echo c32-convex-bsd + else echo c2-convex-bsd + fi + exit 0 ;; + c34*) + echo c34-convex-bsd + exit 0 ;; + c38*) + echo c38-convex-bsd + exit 0 ;; + c4*) + echo c4-convex-bsd + exit 0 ;; + esac +fi + +#echo '(Unable to guess system type)' 1>&2 + +exit 1 diff --git a/nulib2/config.h.in b/nulib2/config.h.in new file mode 100644 index 0000000..0625dd3 --- /dev/null +++ b/nulib2/config.h.in @@ -0,0 +1,109 @@ +/* config.h.in. Generated automatically from configure.in by autoheader. */ + +/* Define to empty if the keyword does not work. */ +#undef const + +/* Define if utime(file, NULL) sets file's timestamp to the present. */ +#undef HAVE_UTIME_NULL + +/* Define to `int' if doesn't define. */ +#undef mode_t + +/* Define to `long' if doesn't define. */ +#undef off_t + +/* Define if the setvbuf function takes the buffering type as its second + argument and the buffer pointer as the third, as on System V + before release 3. */ +#undef SETVBUF_REVERSED + +/* Define to `unsigned' if doesn't define. */ +#undef size_t + +/* Define if you have the ANSI C header files. */ +#undef STDC_HEADERS + +/* Define if your declares struct tm. */ +#undef TM_IN_SYS_TIME + +/* Define to `unsigned char' if doesn't define. */ +#undef uchar + +/* Define to `unsigned short' if doesn't define. */ +#undef ushort + +/* Define to `unsigned int' if doesn't define. */ +#undef uint + +/* Define to `unsigned long' if doesn't define. */ +#undef ulong + +/* Define to `int' if doesn't define. */ +#undef mode_t + +/* Define to `long' if doesn't define. */ +#undef off_t + +/* Define to `unsigned' if doesn't define. */ +#undef size_t + +/* Define if you have the memmove function. */ +#undef HAVE_MEMMOVE + +/* Define if you have the mkdir function. */ +#undef HAVE_MKDIR + +/* Define if you have the strcasecmp function. */ +#undef HAVE_STRCASECMP + +/* Define if you have the strncasecmp function. */ +#undef HAVE_STRNCASECMP + +/* Define if you have the strerror function. */ +#undef HAVE_STRERROR + +/* Define if you have the strtoul function. */ +#undef HAVE_STRTOUL + +/* Define if you have the header file. */ +#undef HAVE_DIRENT_H + +/* Define if you have the header file. */ +#undef HAVE_FCNTL_H + +/* Define if you have the header file. */ +#undef HAVE_LIMITS_H + +/* Define if you have the header file. */ +#undef HAVE_MALLOC_H + +/* Define if you have the header file. */ +#undef HAVE_STDLIB_H + +/* Define if you have the header file. */ +#undef HAVE_NDIR_H + +/* Define if you have the header file. */ +#undef HAVE_STRINGS_H + +/* Define if you have the header file. */ +#undef HAVE_SYS_DIR_H + +/* Define if you have the header file. */ +#undef HAVE_SYS_NDIR_H + +/* Define if you have the header file. */ +#undef HAVE_SYS_STAT_H + +/* Define if you have the header file. */ +#undef HAVE_SYS_TIME_H + +/* Define if you have the header file. */ +#undef HAVE_SYS_TYPES_H + +/* Define if you have the header file. */ +#undef HAVE_UNISTD_H + +/* Define if we want to use the dmalloc library (--enable-dmalloc). */ +#undef USE_DMALLOC + diff --git a/nulib2/config.sub b/nulib2/config.sub new file mode 100644 index 0000000..ec508a5 --- /dev/null +++ b/nulib2/config.sub @@ -0,0 +1,1220 @@ +#! /bin/sh +# Configuration validation subroutine script, version 1.1. +# Copyright (C) 1991, 92-97, 1998, 1999 Free Software Foundation, Inc. +# This file is (in principle) common to ALL GNU software. +# The presence of a machine in this file suggests that SOME GNU software +# can handle that machine. It does not imply ALL GNU software can. +# +# This file is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, +# Boston, MA 02111-1307, USA. + +# As a special exception to the GNU General Public License, if you +# distribute this file as part of a program that contains a +# configuration script generated by Autoconf, you may include it under +# the same distribution terms that you use for the rest of that program. + +# Configuration subroutine to validate and canonicalize a configuration type. +# Supply the specified configuration type as an argument. +# If it is invalid, we print an error message on stderr and exit with code 1. +# Otherwise, we print the canonical config type on stdout and succeed. + +# This file is supposed to be the same for all GNU packages +# and recognize all the CPU types, system types and aliases +# that are meaningful with *any* GNU software. +# Each package is responsible for reporting which valid configurations +# it does not support. The user should be able to distinguish +# a failure to support a valid configuration from a meaningless +# configuration. + +# The goal of this file is to map all the various variations of a given +# machine specification into a single specification in the form: +# CPU_TYPE-MANUFACTURER-OPERATING_SYSTEM +# or in some cases, the newer four-part form: +# CPU_TYPE-MANUFACTURER-KERNEL-OPERATING_SYSTEM +# It is wrong to echo any other type of specification. + +if [ x$1 = x ] +then + echo Configuration name missing. 1>&2 + echo "Usage: $0 CPU-MFR-OPSYS" 1>&2 + echo "or $0 ALIAS" 1>&2 + echo where ALIAS is a recognized configuration type. 1>&2 + exit 1 +fi + +# First pass through any local machine types. +case $1 in + *local*) + echo $1 + exit 0 + ;; + *) + ;; +esac + +# Separate what the user gave into CPU-COMPANY and OS or KERNEL-OS (if any). +# Here we must recognize all the valid KERNEL-OS combinations. +maybe_os=`echo $1 | sed 's/^\(.*\)-\([^-]*-[^-]*\)$/\2/'` +case $maybe_os in + linux-gnu*) + os=-$maybe_os + basic_machine=`echo $1 | sed 's/^\(.*\)-\([^-]*-[^-]*\)$/\1/'` + ;; + *) + basic_machine=`echo $1 | sed 's/-[^-]*$//'` + if [ $basic_machine != $1 ] + then os=`echo $1 | sed 's/.*-/-/'` + else os=; fi + ;; +esac + +### Let's recognize common machines as not being operating systems so +### that things like config.sub decstation-3100 work. We also +### recognize some manufacturers as not being operating systems, so we +### can provide default operating systems below. +case $os in + -sun*os*) + # Prevent following clause from handling this invalid input. + ;; + -dec* | -mips* | -sequent* | -encore* | -pc532* | -sgi* | -sony* | \ + -att* | -7300* | -3300* | -delta* | -motorola* | -sun[234]* | \ + -unicom* | -ibm* | -next | -hp | -isi* | -apollo | -altos* | \ + -convergent* | -ncr* | -news | -32* | -3600* | -3100* | -hitachi* |\ + -c[123]* | -convex* | -sun | -crds | -omron* | -dg | -ultra | -tti* | \ + -harris | -dolphin | -highlevel | -gould | -cbm | -ns | -masscomp | \ + -apple) + os= + basic_machine=$1 + ;; + -sim | -cisco | -oki | -wec | -winbond) + os= + basic_machine=$1 + ;; + -scout) + ;; + -wrs) + os=-vxworks + basic_machine=$1 + ;; + -hiux*) + os=-hiuxwe2 + ;; + -sco5) + os=-sco3.2v5 + basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` + ;; + -sco4) + os=-sco3.2v4 + basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` + ;; + -sco3.2.[4-9]*) + os=`echo $os | sed -e 's/sco3.2./sco3.2v/'` + basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` + ;; + -sco3.2v[4-9]*) + # Don't forget version if it is 3.2v4 or newer. + basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` + ;; + -sco*) + os=-sco3.2v2 + basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` + ;; + -udk*) + basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` + ;; + -isc) + os=-isc2.2 + basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` + ;; + -clix*) + basic_machine=clipper-intergraph + ;; + -isc*) + basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` + ;; + -lynx*) + os=-lynxos + ;; + -ptx*) + basic_machine=`echo $1 | sed -e 's/86-.*/86-sequent/'` + ;; + -windowsnt*) + os=`echo $os | sed -e 's/windowsnt/winnt/'` + ;; + -psos*) + os=-psos + ;; + -mint | -mint[0-9]*) + basic_machine=m68k-atari + os=-mint + ;; +esac + +# Decode aliases for certain CPU-COMPANY combinations. +case $basic_machine in + # Recognize the basic CPU types without company name. + # Some are omitted here because they have special meanings below. + tahoe | i860 | ia64 | m32r | m68k | m68000 | m88k | ns32k | arc | arm \ + | arme[lb] | pyramid | mn10200 | mn10300 | tron | a29k \ + | 580 | i960 | h8300 \ + | hppa | hppa1.0 | hppa1.1 | hppa2.0 | hppa2.0w | hppa2.0n \ + | alpha | alphaev[4-7] | alphaev56 | alphapca5[67] \ + | we32k | ns16k | clipper | i370 | sh | powerpc | powerpcle \ + | 1750a | dsp16xx | pdp11 | mips16 | mips64 | mipsel | mips64el \ + | mips64orion | mips64orionel | mipstx39 | mipstx39el \ + | mips64vr4300 | mips64vr4300el | mips64vr4100 | mips64vr4100el \ + | mips64vr5000 | miprs64vr5000el | mcore \ + | sparc | sparclet | sparclite | sparc64 | sparcv9 | v850 | c4x \ + | thumb | d10v) + basic_machine=$basic_machine-unknown + ;; + m88110 | m680[12346]0 | m683?2 | m68360 | m5200 | z8k | v70 | h8500 | w65) + ;; + + # We use `pc' rather than `unknown' + # because (1) that's what they normally are, and + # (2) the word "unknown" tends to confuse beginning users. + i[34567]86) + basic_machine=$basic_machine-pc + ;; + # Object if more than one company name word. + *-*-*) + echo Invalid configuration \`$1\': machine \`$basic_machine\' not recognized 1>&2 + exit 1 + ;; + # Recognize the basic CPU types with company name. + # FIXME: clean up the formatting here. + vax-* | tahoe-* | i[34567]86-* | i860-* | ia64-* | m32r-* | m68k-* | m68000-* \ + | m88k-* | sparc-* | ns32k-* | fx80-* | arc-* | arm-* | c[123]* \ + | mips-* | pyramid-* | tron-* | a29k-* | romp-* | rs6000-* \ + | power-* | none-* | 580-* | cray2-* | h8300-* | h8500-* | i960-* \ + | xmp-* | ymp-* \ + | hppa-* | hppa1.0-* | hppa1.1-* | hppa2.0-* | hppa2.0w-* | hppa2.0n-* \ + | alpha-* | alphaev[4-7]-* | alphaev56-* | alphapca5[67]-* \ + | we32k-* | cydra-* | ns16k-* | pn-* | np1-* | xps100-* \ + | clipper-* | orion-* \ + | sparclite-* | pdp11-* | sh-* | powerpc-* | powerpcle-* \ + | sparc64-* | sparcv9-* | sparc86x-* | mips16-* | mips64-* | mipsel-* \ + | mips64el-* | mips64orion-* | mips64orionel-* \ + | mips64vr4100-* | mips64vr4100el-* | mips64vr4300-* | mips64vr4300el-* \ + | mipstx39-* | mipstx39el-* | mcore-* \ + | f301-* | armv*-* | t3e-* \ + | m88110-* | m680[01234]0-* | m683?2-* | m68360-* | z8k-* | d10v-* \ + | thumb-* | v850-* | d30v-* | tic30-* | c30-* ) + ;; + # Recognize the various machine names and aliases which stand + # for a CPU type and a company and sometimes even an OS. + 386bsd) + basic_machine=i386-unknown + os=-bsd + ;; + 3b1 | 7300 | 7300-att | att-7300 | pc7300 | safari | unixpc) + basic_machine=m68000-att + ;; + 3b*) + basic_machine=we32k-att + ;; + a29khif) + basic_machine=a29k-amd + os=-udi + ;; + adobe68k) + basic_machine=m68010-adobe + os=-scout + ;; + alliant | fx80) + basic_machine=fx80-alliant + ;; + altos | altos3068) + basic_machine=m68k-altos + ;; + am29k) + basic_machine=a29k-none + os=-bsd + ;; + amdahl) + basic_machine=580-amdahl + os=-sysv + ;; + amiga | amiga-*) + basic_machine=m68k-cbm + ;; + amigaos | amigados) + basic_machine=m68k-cbm + os=-amigaos + ;; + amigaunix | amix) + basic_machine=m68k-cbm + os=-sysv4 + ;; + apollo68) + basic_machine=m68k-apollo + os=-sysv + ;; + apollo68bsd) + basic_machine=m68k-apollo + os=-bsd + ;; + aux) + basic_machine=m68k-apple + os=-aux + ;; + balance) + basic_machine=ns32k-sequent + os=-dynix + ;; + convex-c1) + basic_machine=c1-convex + os=-bsd + ;; + convex-c2) + basic_machine=c2-convex + os=-bsd + ;; + convex-c32) + basic_machine=c32-convex + os=-bsd + ;; + convex-c34) + basic_machine=c34-convex + os=-bsd + ;; + convex-c38) + basic_machine=c38-convex + os=-bsd + ;; + cray | ymp) + basic_machine=ymp-cray + os=-unicos + ;; + cray2) + basic_machine=cray2-cray + os=-unicos + ;; + [ctj]90-cray) + basic_machine=c90-cray + os=-unicos + ;; + crds | unos) + basic_machine=m68k-crds + ;; + da30 | da30-*) + basic_machine=m68k-da30 + ;; + decstation | decstation-3100 | pmax | pmax-* | pmin | dec3100 | decstatn) + basic_machine=mips-dec + ;; + delta | 3300 | motorola-3300 | motorola-delta \ + | 3300-motorola | delta-motorola) + basic_machine=m68k-motorola + ;; + delta88) + basic_machine=m88k-motorola + os=-sysv3 + ;; + dpx20 | dpx20-*) + basic_machine=rs6000-bull + os=-bosx + ;; + dpx2* | dpx2*-bull) + basic_machine=m68k-bull + os=-sysv3 + ;; + ebmon29k) + basic_machine=a29k-amd + os=-ebmon + ;; + elxsi) + basic_machine=elxsi-elxsi + os=-bsd + ;; + encore | umax | mmax) + basic_machine=ns32k-encore + ;; + es1800 | OSE68k | ose68k | ose | OSE) + basic_machine=m68k-ericsson + os=-ose + ;; + fx2800) + basic_machine=i860-alliant + ;; + genix) + basic_machine=ns32k-ns + ;; + gmicro) + basic_machine=tron-gmicro + os=-sysv + ;; + h3050r* | hiux*) + basic_machine=hppa1.1-hitachi + os=-hiuxwe2 + ;; + h8300hms) + basic_machine=h8300-hitachi + os=-hms + ;; + h8300xray) + basic_machine=h8300-hitachi + os=-xray + ;; + h8500hms) + basic_machine=h8500-hitachi + os=-hms + ;; + harris) + basic_machine=m88k-harris + os=-sysv3 + ;; + hp300-*) + basic_machine=m68k-hp + ;; + hp300bsd) + basic_machine=m68k-hp + os=-bsd + ;; + hp300hpux) + basic_machine=m68k-hp + os=-hpux + ;; + hp3k9[0-9][0-9] | hp9[0-9][0-9]) + basic_machine=hppa1.0-hp + ;; + hp9k2[0-9][0-9] | hp9k31[0-9]) + basic_machine=m68000-hp + ;; + hp9k3[2-9][0-9]) + basic_machine=m68k-hp + ;; + hp9k6[0-9][0-9] | hp6[0-9][0-9]) + basic_machine=hppa1.0-hp + ;; + hp9k7[0-79][0-9] | hp7[0-79][0-9]) + basic_machine=hppa1.1-hp + ;; + hp9k78[0-9] | hp78[0-9]) + # FIXME: really hppa2.0-hp + basic_machine=hppa1.1-hp + ;; + hp9k8[67]1 | hp8[67]1 | hp9k80[24] | hp80[24] | hp9k8[78]9 | hp8[78]9 | hp9k893 | hp893) + # FIXME: really hppa2.0-hp + basic_machine=hppa1.1-hp + ;; + hp9k8[0-9][13679] | hp8[0-9][13679]) + basic_machine=hppa1.1-hp + ;; + hp9k8[0-9][0-9] | hp8[0-9][0-9]) + basic_machine=hppa1.0-hp + ;; + hppa-next) + os=-nextstep3 + ;; + hppaosf) + basic_machine=hppa1.1-hp + os=-osf + ;; + hppro) + basic_machine=hppa1.1-hp + os=-proelf + ;; + i370-ibm* | ibm*) + basic_machine=i370-ibm + os=-mvs + ;; +# I'm not sure what "Sysv32" means. Should this be sysv3.2? + i[34567]86v32) + basic_machine=`echo $1 | sed -e 's/86.*/86-pc/'` + os=-sysv32 + ;; + i[34567]86v4*) + basic_machine=`echo $1 | sed -e 's/86.*/86-pc/'` + os=-sysv4 + ;; + i[34567]86v) + basic_machine=`echo $1 | sed -e 's/86.*/86-pc/'` + os=-sysv + ;; + i[34567]86sol2) + basic_machine=`echo $1 | sed -e 's/86.*/86-pc/'` + os=-solaris2 + ;; + i386mach) + basic_machine=i386-mach + os=-mach + ;; + i386-vsta | vsta) + basic_machine=i386-unknown + os=-vsta + ;; + i386-go32 | go32) + basic_machine=i386-unknown + os=-go32 + ;; + i386-mingw32 | mingw32) + basic_machine=i386-unknown + os=-mingw32 + ;; + iris | iris4d) + basic_machine=mips-sgi + case $os in + -irix*) + ;; + *) + os=-irix4 + ;; + esac + ;; + isi68 | isi) + basic_machine=m68k-isi + os=-sysv + ;; + m88k-omron*) + basic_machine=m88k-omron + ;; + magnum | m3230) + basic_machine=mips-mips + os=-sysv + ;; + merlin) + basic_machine=ns32k-utek + os=-sysv + ;; + miniframe) + basic_machine=m68000-convergent + ;; + *mint | -mint[0-9]* | *MiNT | *MiNT[0-9]*) + basic_machine=m68k-atari + os=-mint + ;; + mipsel*-linux*) + basic_machine=mipsel-unknown + os=-linux-gnu + ;; + mips*-linux*) + basic_machine=mips-unknown + os=-linux-gnu + ;; + mips3*-*) + basic_machine=`echo $basic_machine | sed -e 's/mips3/mips64/'` + ;; + mips3*) + basic_machine=`echo $basic_machine | sed -e 's/mips3/mips64/'`-unknown + ;; + monitor) + basic_machine=m68k-rom68k + os=-coff + ;; + msdos) + basic_machine=i386-unknown + os=-msdos + ;; + ncr3000) + basic_machine=i486-ncr + os=-sysv4 + ;; + netbsd386) + basic_machine=i386-unknown + os=-netbsd + ;; + netwinder) + basic_machine=armv4l-corel + os=-linux + ;; + news | news700 | news800 | news900) + basic_machine=m68k-sony + os=-newsos + ;; + news1000) + basic_machine=m68030-sony + os=-newsos + ;; + news-3600 | risc-news) + basic_machine=mips-sony + os=-newsos + ;; + necv70) + basic_machine=v70-nec + os=-sysv + ;; + next | m*-next ) + basic_machine=m68k-next + case $os in + -nextstep* ) + ;; + -ns2*) + os=-nextstep2 + ;; + *) + os=-nextstep3 + ;; + esac + ;; + nh3000) + basic_machine=m68k-harris + os=-cxux + ;; + nh[45]000) + basic_machine=m88k-harris + os=-cxux + ;; + nindy960) + basic_machine=i960-intel + os=-nindy + ;; + mon960) + basic_machine=i960-intel + os=-mon960 + ;; + np1) + basic_machine=np1-gould + ;; + op50n-* | op60c-*) + basic_machine=hppa1.1-oki + os=-proelf + ;; + OSE68000 | ose68000) + basic_machine=m68000-ericsson + os=-ose + ;; + os68k) + basic_machine=m68k-none + os=-os68k + ;; + pa-hitachi) + basic_machine=hppa1.1-hitachi + os=-hiuxwe2 + ;; + paragon) + basic_machine=i860-intel + os=-osf + ;; + pbd) + basic_machine=sparc-tti + ;; + pbb) + basic_machine=m68k-tti + ;; + pc532 | pc532-*) + basic_machine=ns32k-pc532 + ;; + pentium | p5 | k5 | k6 | nexen) + basic_machine=i586-pc + ;; + pentiumpro | p6 | 6x86) + basic_machine=i686-pc + ;; + pentiumii | pentium2) + basic_machine=i786-pc + ;; + pentium-* | p5-* | k5-* | k6-* | nexen-*) + basic_machine=i586-`echo $basic_machine | sed 's/^[^-]*-//'` + ;; + pentiumpro-* | p6-* | 6x86-*) + basic_machine=i686-`echo $basic_machine | sed 's/^[^-]*-//'` + ;; + pentiumii-* | pentium2-*) + basic_machine=i786-`echo $basic_machine | sed 's/^[^-]*-//'` + ;; + pn) + basic_machine=pn-gould + ;; + power) basic_machine=rs6000-ibm + ;; + ppc) basic_machine=powerpc-unknown + ;; + ppc-*) basic_machine=powerpc-`echo $basic_machine | sed 's/^[^-]*-//'` + ;; + ppcle | powerpclittle | ppc-le | powerpc-little) + basic_machine=powerpcle-unknown + ;; + ppcle-* | powerpclittle-*) + basic_machine=powerpcle-`echo $basic_machine | sed 's/^[^-]*-//'` + ;; + ps2) + basic_machine=i386-ibm + ;; + rom68k) + basic_machine=m68k-rom68k + os=-coff + ;; + rm[46]00) + basic_machine=mips-siemens + ;; + rtpc | rtpc-*) + basic_machine=romp-ibm + ;; + sa29200) + basic_machine=a29k-amd + os=-udi + ;; + sequent) + basic_machine=i386-sequent + ;; + sh) + basic_machine=sh-hitachi + os=-hms + ;; + sparclite-wrs) + basic_machine=sparclite-wrs + os=-vxworks + ;; + sps7) + basic_machine=m68k-bull + os=-sysv2 + ;; + spur) + basic_machine=spur-unknown + ;; + st2000) + basic_machine=m68k-tandem + ;; + stratus) + basic_machine=i860-stratus + os=-sysv4 + ;; + sun2) + basic_machine=m68000-sun + ;; + sun2os3) + basic_machine=m68000-sun + os=-sunos3 + ;; + sun2os4) + basic_machine=m68000-sun + os=-sunos4 + ;; + sun3os3) + basic_machine=m68k-sun + os=-sunos3 + ;; + sun3os4) + basic_machine=m68k-sun + os=-sunos4 + ;; + sun4os3) + basic_machine=sparc-sun + os=-sunos3 + ;; + sun4os4) + basic_machine=sparc-sun + os=-sunos4 + ;; + sun4sol2) + basic_machine=sparc-sun + os=-solaris2 + ;; + sun3 | sun3-*) + basic_machine=m68k-sun + ;; + sun4) + basic_machine=sparc-sun + ;; + sun386 | sun386i | roadrunner) + basic_machine=i386-sun + ;; + symmetry) + basic_machine=i386-sequent + os=-dynix + ;; + t3e) + basic_machine=t3e-cray + os=-unicos + ;; + tx39) + basic_machine=mipstx39-unknown + ;; + tx39el) + basic_machine=mipstx39el-unknown + ;; + tower | tower-32) + basic_machine=m68k-ncr + ;; + udi29k) + basic_machine=a29k-amd + os=-udi + ;; + ultra3) + basic_machine=a29k-nyu + os=-sym1 + ;; + v810 | necv810) + basic_machine=v810-nec + os=-none + ;; + vaxv) + basic_machine=vax-dec + os=-sysv + ;; + vms) + basic_machine=vax-dec + os=-vms + ;; + vpp*|vx|vx-*) + basic_machine=f301-fujitsu + ;; + vxworks960) + basic_machine=i960-wrs + os=-vxworks + ;; + vxworks68) + basic_machine=m68k-wrs + os=-vxworks + ;; + vxworks29k) + basic_machine=a29k-wrs + os=-vxworks + ;; + w65*) + basic_machine=w65-wdc + os=-none + ;; + w89k-*) + basic_machine=hppa1.1-winbond + os=-proelf + ;; + xmp) + basic_machine=xmp-cray + os=-unicos + ;; + xps | xps100) + basic_machine=xps100-honeywell + ;; + z8k-*-coff) + basic_machine=z8k-unknown + os=-sim + ;; + none) + basic_machine=none-none + os=-none + ;; + +# Here we handle the default manufacturer of certain CPU types. It is in +# some cases the only manufacturer, in others, it is the most popular. + w89k) + basic_machine=hppa1.1-winbond + ;; + op50n) + basic_machine=hppa1.1-oki + ;; + op60c) + basic_machine=hppa1.1-oki + ;; + mips) + if [ x$os = x-linux-gnu ]; then + basic_machine=mips-unknown + else + basic_machine=mips-mips + fi + ;; + romp) + basic_machine=romp-ibm + ;; + rs6000) + basic_machine=rs6000-ibm + ;; + vax) + basic_machine=vax-dec + ;; + pdp11) + basic_machine=pdp11-dec + ;; + we32k) + basic_machine=we32k-att + ;; + sparc | sparcv9) + basic_machine=sparc-sun + ;; + cydra) + basic_machine=cydra-cydrome + ;; + orion) + basic_machine=orion-highlevel + ;; + orion105) + basic_machine=clipper-highlevel + ;; + mac | mpw | mac-mpw) + basic_machine=m68k-apple + ;; + pmac | pmac-mpw) + basic_machine=powerpc-apple + ;; + c4x*) + basic_machine=c4x-none + os=-coff + ;; + *) + echo Invalid configuration \`$1\': machine \`$basic_machine\' not recognized 1>&2 + exit 1 + ;; +esac + +# Here we canonicalize certain aliases for manufacturers. +case $basic_machine in + *-digital*) + basic_machine=`echo $basic_machine | sed 's/digital.*/dec/'` + ;; + *-commodore*) + basic_machine=`echo $basic_machine | sed 's/commodore.*/cbm/'` + ;; + *) + ;; +esac + +# Decode manufacturer-specific aliases for certain operating systems. + +if [ x"$os" != x"" ] +then +case $os in + # First match some system type aliases + # that might get confused with valid system types. + # -solaris* is a basic system type, with this one exception. + -solaris1 | -solaris1.*) + os=`echo $os | sed -e 's|solaris1|sunos4|'` + ;; + -solaris) + os=-solaris2 + ;; + -svr4*) + os=-sysv4 + ;; + -unixware*) + os=-sysv4.2uw + ;; + -gnu/linux*) + os=`echo $os | sed -e 's|gnu/linux|linux-gnu|'` + ;; + # First accept the basic system types. + # The portable systems comes first. + # Each alternative MUST END IN A *, to match a version number. + # -sysv* is not here because it comes later, after sysvr4. + -gnu* | -bsd* | -mach* | -minix* | -genix* | -ultrix* | -irix* \ + | -*vms* | -sco* | -esix* | -isc* | -aix* | -sunos | -sunos[34]*\ + | -hpux* | -unos* | -osf* | -luna* | -dgux* | -solaris* | -sym* \ + | -amigaos* | -amigados* | -msdos* | -newsos* | -unicos* | -aof* \ + | -aos* \ + | -nindy* | -vxsim* | -vxworks* | -ebmon* | -hms* | -mvs* \ + | -clix* | -riscos* | -uniplus* | -iris* | -rtu* | -xenix* \ + | -hiux* | -386bsd* | -netbsd* | -openbsd* | -freebsd* | -riscix* \ + | -lynxos* | -bosx* | -nextstep* | -cxux* | -aout* | -elf* | -oabi* \ + | -ptx* | -coff* | -ecoff* | -winnt* | -domain* | -vsta* \ + | -udi* | -eabi* | -lites* | -ieee* | -go32* | -aux* \ + | -cygwin* | -pe* | -psos* | -moss* | -proelf* | -rtems* \ + | -mingw32* | -linux-gnu* | -uxpv* | -beos* | -mpeix* | -udk* \ + | -interix* | -uwin* | -rhapsody* | -openstep* | -oskit*) + # Remember, each alternative MUST END IN *, to match a version number. + ;; + -sim | -es1800* | -hms* | -xray | -os68k* | -none* | -v88r* \ + | -windows* | -osx | -abug | -netware* | -os9* | -beos* \ + | -macos* | -mpw* | -magic* | -mon960* | -lnews*) + ;; + -mac*) + os=`echo $os | sed -e 's|mac|macos|'` + ;; + -linux*) + os=`echo $os | sed -e 's|linux|linux-gnu|'` + ;; + -sunos5*) + os=`echo $os | sed -e 's|sunos5|solaris2|'` + ;; + -sunos6*) + os=`echo $os | sed -e 's|sunos6|solaris3|'` + ;; + -osfrose*) + os=-osfrose + ;; + -osf*) + os=-osf + ;; + -utek*) + os=-bsd + ;; + -dynix*) + os=-bsd + ;; + -acis*) + os=-aos + ;; + -386bsd) + os=-bsd + ;; + -ctix* | -uts*) + os=-sysv + ;; + -ns2 ) + os=-nextstep2 + ;; + # Preserve the version number of sinix5. + -sinix5.*) + os=`echo $os | sed -e 's|sinix|sysv|'` + ;; + -sinix*) + os=-sysv4 + ;; + -triton*) + os=-sysv3 + ;; + -oss*) + os=-sysv3 + ;; + -svr4) + os=-sysv4 + ;; + -svr3) + os=-sysv3 + ;; + -sysvr4) + os=-sysv4 + ;; + # This must come after -sysvr4. + -sysv*) + ;; + -ose*) + os=-ose + ;; + -es1800*) + os=-ose + ;; + -xenix) + os=-xenix + ;; + -*mint | -*MiNT) + os=-mint + ;; + -none) + ;; + *) + # Get rid of the `-' at the beginning of $os. + os=`echo $os | sed 's/[^-]*-//'` + echo Invalid configuration \`$1\': system \`$os\' not recognized 1>&2 + exit 1 + ;; +esac +else + +# Here we handle the default operating systems that come with various machines. +# The value should be what the vendor currently ships out the door with their +# machine or put another way, the most popular os provided with the machine. + +# Note that if you're going to try to match "-MANUFACTURER" here (say, +# "-sun"), then you have to tell the case statement up towards the top +# that MANUFACTURER isn't an operating system. Otherwise, code above +# will signal an error saying that MANUFACTURER isn't an operating +# system, and we'll never get to this point. + +case $basic_machine in + *-acorn) + os=-riscix1.2 + ;; + arm*-corel) + os=-linux + ;; + arm*-semi) + os=-aout + ;; + pdp11-*) + os=-none + ;; + *-dec | vax-*) + os=-ultrix4.2 + ;; + m68*-apollo) + os=-domain + ;; + i386-sun) + os=-sunos4.0.2 + ;; + m68000-sun) + os=-sunos3 + # This also exists in the configure program, but was not the + # default. + # os=-sunos4 + ;; + m68*-cisco) + os=-aout + ;; + mips*-cisco) + os=-elf + ;; + mips*-*) + os=-elf + ;; + *-tti) # must be before sparc entry or we get the wrong os. + os=-sysv3 + ;; + sparc-* | *-sun) + os=-sunos4.1.1 + ;; + *-be) + os=-beos + ;; + *-ibm) + os=-aix + ;; + *-wec) + os=-proelf + ;; + *-winbond) + os=-proelf + ;; + *-oki) + os=-proelf + ;; + *-hp) + os=-hpux + ;; + *-hitachi) + os=-hiux + ;; + i860-* | *-att | *-ncr | *-altos | *-motorola | *-convergent) + os=-sysv + ;; + *-cbm) + os=-amigaos + ;; + *-dg) + os=-dgux + ;; + *-dolphin) + os=-sysv3 + ;; + m68k-ccur) + os=-rtu + ;; + m88k-omron*) + os=-luna + ;; + *-next ) + os=-nextstep + ;; + *-sequent) + os=-ptx + ;; + *-crds) + os=-unos + ;; + *-ns) + os=-genix + ;; + i370-*) + os=-mvs + ;; + *-next) + os=-nextstep3 + ;; + *-gould) + os=-sysv + ;; + *-highlevel) + os=-bsd + ;; + *-encore) + os=-bsd + ;; + *-sgi) + os=-irix + ;; + *-siemens) + os=-sysv4 + ;; + *-masscomp) + os=-rtu + ;; + f301-fujitsu) + os=-uxpv + ;; + *-rom68k) + os=-coff + ;; + *-*bug) + os=-coff + ;; + *-apple) + os=-macos + ;; + *-atari*) + os=-mint + ;; + *) + os=-none + ;; +esac +fi + +# Here we handle the case where we know the os, and the CPU type, but not the +# manufacturer. We pick the logical manufacturer. +vendor=unknown +case $basic_machine in + *-unknown) + case $os in + -riscix*) + vendor=acorn + ;; + -sunos*) + vendor=sun + ;; + -aix*) + vendor=ibm + ;; + -beos*) + vendor=be + ;; + -hpux*) + vendor=hp + ;; + -mpeix*) + vendor=hp + ;; + -hiux*) + vendor=hitachi + ;; + -unos*) + vendor=crds + ;; + -dgux*) + vendor=dg + ;; + -luna*) + vendor=omron + ;; + -genix*) + vendor=ns + ;; + -mvs*) + vendor=ibm + ;; + -ptx*) + vendor=sequent + ;; + -vxsim* | -vxworks*) + vendor=wrs + ;; + -aux*) + vendor=apple + ;; + -hms*) + vendor=hitachi + ;; + -mpw* | -macos*) + vendor=apple + ;; + -*mint | -*MiNT) + vendor=atari + ;; + esac + basic_machine=`echo $basic_machine | sed "s/unknown/$vendor/"` + ;; +esac + +echo $basic_machine$os diff --git a/nulib2/configure b/nulib2/configure new file mode 100755 index 0000000..9959b76 --- /dev/null +++ b/nulib2/configure @@ -0,0 +1,2069 @@ +#! /bin/sh + +# Guess values for system-dependent variables and create Makefiles. +# Generated automatically using autoconf version 2.13 +# Copyright (C) 1992, 93, 94, 95, 96 Free Software Foundation, Inc. +# +# This configure script is free software; the Free Software Foundation +# gives unlimited permission to copy, distribute and modify it. + +# Defaults: +ac_help= +ac_default_prefix=/usr/local +# Any additions from configure.in: +ac_help="$ac_help + --enable-dmalloc: do dmalloc stuff" + +# Initialize some variables set by options. +# The variables have the same names as the options, with +# dashes changed to underlines. +build=NONE +cache_file=./config.cache +exec_prefix=NONE +host=NONE +no_create= +nonopt=NONE +no_recursion= +prefix=NONE +program_prefix=NONE +program_suffix=NONE +program_transform_name=s,x,x, +silent= +site= +srcdir= +target=NONE +verbose= +x_includes=NONE +x_libraries=NONE +bindir='${exec_prefix}/bin' +sbindir='${exec_prefix}/sbin' +libexecdir='${exec_prefix}/libexec' +datadir='${prefix}/share' +sysconfdir='${prefix}/etc' +sharedstatedir='${prefix}/com' +localstatedir='${prefix}/var' +libdir='${exec_prefix}/lib' +includedir='${prefix}/include' +oldincludedir='/usr/include' +infodir='${prefix}/info' +mandir='${prefix}/man' + +# Initialize some other variables. +subdirs= +MFLAGS= MAKEFLAGS= +SHELL=${CONFIG_SHELL-/bin/sh} +# Maximum number of lines to put in a shell here document. +ac_max_here_lines=12 + +ac_prev= +for ac_option +do + + # If the previous option needs an argument, assign it. + if test -n "$ac_prev"; then + eval "$ac_prev=\$ac_option" + ac_prev= + continue + fi + + case "$ac_option" in + -*=*) ac_optarg=`echo "$ac_option" | sed 's/[-_a-zA-Z0-9]*=//'` ;; + *) ac_optarg= ;; + esac + + # Accept the important Cygnus configure options, so we can diagnose typos. + + case "$ac_option" in + + -bindir | --bindir | --bindi | --bind | --bin | --bi) + ac_prev=bindir ;; + -bindir=* | --bindir=* | --bindi=* | --bind=* | --bin=* | --bi=*) + bindir="$ac_optarg" ;; + + -build | --build | --buil | --bui | --bu) + ac_prev=build ;; + -build=* | --build=* | --buil=* | --bui=* | --bu=*) + build="$ac_optarg" ;; + + -cache-file | --cache-file | --cache-fil | --cache-fi \ + | --cache-f | --cache- | --cache | --cach | --cac | --ca | --c) + ac_prev=cache_file ;; + -cache-file=* | --cache-file=* | --cache-fil=* | --cache-fi=* \ + | --cache-f=* | --cache-=* | --cache=* | --cach=* | --cac=* | --ca=* | --c=*) + cache_file="$ac_optarg" ;; + + -datadir | --datadir | --datadi | --datad | --data | --dat | --da) + ac_prev=datadir ;; + -datadir=* | --datadir=* | --datadi=* | --datad=* | --data=* | --dat=* \ + | --da=*) + datadir="$ac_optarg" ;; + + -disable-* | --disable-*) + ac_feature=`echo $ac_option|sed -e 's/-*disable-//'` + # Reject names that are not valid shell variable names. + if test -n "`echo $ac_feature| sed 's/[-a-zA-Z0-9_]//g'`"; then + { echo "configure: error: $ac_feature: invalid feature name" 1>&2; exit 1; } + fi + ac_feature=`echo $ac_feature| sed 's/-/_/g'` + eval "enable_${ac_feature}=no" ;; + + -enable-* | --enable-*) + ac_feature=`echo $ac_option|sed -e 's/-*enable-//' -e 's/=.*//'` + # Reject names that are not valid shell variable names. + if test -n "`echo $ac_feature| sed 's/[-_a-zA-Z0-9]//g'`"; then + { echo "configure: error: $ac_feature: invalid feature name" 1>&2; exit 1; } + fi + ac_feature=`echo $ac_feature| sed 's/-/_/g'` + case "$ac_option" in + *=*) ;; + *) ac_optarg=yes ;; + esac + eval "enable_${ac_feature}='$ac_optarg'" ;; + + -exec-prefix | --exec_prefix | --exec-prefix | --exec-prefi \ + | --exec-pref | --exec-pre | --exec-pr | --exec-p | --exec- \ + | --exec | --exe | --ex) + ac_prev=exec_prefix ;; + -exec-prefix=* | --exec_prefix=* | --exec-prefix=* | --exec-prefi=* \ + | --exec-pref=* | --exec-pre=* | --exec-pr=* | --exec-p=* | --exec-=* \ + | --exec=* | --exe=* | --ex=*) + exec_prefix="$ac_optarg" ;; + + -gas | --gas | --ga | --g) + # Obsolete; use --with-gas. + with_gas=yes ;; + + -help | --help | --hel | --he) + # Omit some internal or obsolete options to make the list less imposing. + # This message is too long to be a string in the A/UX 3.1 sh. + cat << EOF +Usage: configure [options] [host] +Options: [defaults in brackets after descriptions] +Configuration: + --cache-file=FILE cache test results in FILE + --help print this message + --no-create do not create output files + --quiet, --silent do not print \`checking...' messages + --version print the version of autoconf that created configure +Directory and file names: + --prefix=PREFIX install architecture-independent files in PREFIX + [$ac_default_prefix] + --exec-prefix=EPREFIX install architecture-dependent files in EPREFIX + [same as prefix] + --bindir=DIR user executables in DIR [EPREFIX/bin] + --sbindir=DIR system admin executables in DIR [EPREFIX/sbin] + --libexecdir=DIR program executables in DIR [EPREFIX/libexec] + --datadir=DIR read-only architecture-independent data in DIR + [PREFIX/share] + --sysconfdir=DIR read-only single-machine data in DIR [PREFIX/etc] + --sharedstatedir=DIR modifiable architecture-independent data in DIR + [PREFIX/com] + --localstatedir=DIR modifiable single-machine data in DIR [PREFIX/var] + --libdir=DIR object code libraries in DIR [EPREFIX/lib] + --includedir=DIR C header files in DIR [PREFIX/include] + --oldincludedir=DIR C header files for non-gcc in DIR [/usr/include] + --infodir=DIR info documentation in DIR [PREFIX/info] + --mandir=DIR man documentation in DIR [PREFIX/man] + --srcdir=DIR find the sources in DIR [configure dir or ..] + --program-prefix=PREFIX prepend PREFIX to installed program names + --program-suffix=SUFFIX append SUFFIX to installed program names + --program-transform-name=PROGRAM + run sed PROGRAM on installed program names +EOF + cat << EOF +Host type: + --build=BUILD configure for building on BUILD [BUILD=HOST] + --host=HOST configure for HOST [guessed] + --target=TARGET configure for TARGET [TARGET=HOST] +Features and packages: + --disable-FEATURE do not include FEATURE (same as --enable-FEATURE=no) + --enable-FEATURE[=ARG] include FEATURE [ARG=yes] + --with-PACKAGE[=ARG] use PACKAGE [ARG=yes] + --without-PACKAGE do not use PACKAGE (same as --with-PACKAGE=no) + --x-includes=DIR X include files are in DIR + --x-libraries=DIR X library files are in DIR +EOF + if test -n "$ac_help"; then + echo "--enable and --with options recognized:$ac_help" + fi + exit 0 ;; + + -host | --host | --hos | --ho) + ac_prev=host ;; + -host=* | --host=* | --hos=* | --ho=*) + host="$ac_optarg" ;; + + -includedir | --includedir | --includedi | --included | --include \ + | --includ | --inclu | --incl | --inc) + ac_prev=includedir ;; + -includedir=* | --includedir=* | --includedi=* | --included=* | --include=* \ + | --includ=* | --inclu=* | --incl=* | --inc=*) + includedir="$ac_optarg" ;; + + -infodir | --infodir | --infodi | --infod | --info | --inf) + ac_prev=infodir ;; + -infodir=* | --infodir=* | --infodi=* | --infod=* | --info=* | --inf=*) + infodir="$ac_optarg" ;; + + -libdir | --libdir | --libdi | --libd) + ac_prev=libdir ;; + -libdir=* | --libdir=* | --libdi=* | --libd=*) + libdir="$ac_optarg" ;; + + -libexecdir | --libexecdir | --libexecdi | --libexecd | --libexec \ + | --libexe | --libex | --libe) + ac_prev=libexecdir ;; + -libexecdir=* | --libexecdir=* | --libexecdi=* | --libexecd=* | --libexec=* \ + | --libexe=* | --libex=* | --libe=*) + libexecdir="$ac_optarg" ;; + + -localstatedir | --localstatedir | --localstatedi | --localstated \ + | --localstate | --localstat | --localsta | --localst \ + | --locals | --local | --loca | --loc | --lo) + ac_prev=localstatedir ;; + -localstatedir=* | --localstatedir=* | --localstatedi=* | --localstated=* \ + | --localstate=* | --localstat=* | --localsta=* | --localst=* \ + | --locals=* | --local=* | --loca=* | --loc=* | --lo=*) + localstatedir="$ac_optarg" ;; + + -mandir | --mandir | --mandi | --mand | --man | --ma | --m) + ac_prev=mandir ;; + -mandir=* | --mandir=* | --mandi=* | --mand=* | --man=* | --ma=* | --m=*) + mandir="$ac_optarg" ;; + + -nfp | --nfp | --nf) + # Obsolete; use --without-fp. + with_fp=no ;; + + -no-create | --no-create | --no-creat | --no-crea | --no-cre \ + | --no-cr | --no-c) + no_create=yes ;; + + -no-recursion | --no-recursion | --no-recursio | --no-recursi \ + | --no-recurs | --no-recur | --no-recu | --no-rec | --no-re | --no-r) + no_recursion=yes ;; + + -oldincludedir | --oldincludedir | --oldincludedi | --oldincluded \ + | --oldinclude | --oldinclud | --oldinclu | --oldincl | --oldinc \ + | --oldin | --oldi | --old | --ol | --o) + ac_prev=oldincludedir ;; + -oldincludedir=* | --oldincludedir=* | --oldincludedi=* | --oldincluded=* \ + | --oldinclude=* | --oldinclud=* | --oldinclu=* | --oldincl=* | --oldinc=* \ + | --oldin=* | --oldi=* | --old=* | --ol=* | --o=*) + oldincludedir="$ac_optarg" ;; + + -prefix | --prefix | --prefi | --pref | --pre | --pr | --p) + ac_prev=prefix ;; + -prefix=* | --prefix=* | --prefi=* | --pref=* | --pre=* | --pr=* | --p=*) + prefix="$ac_optarg" ;; + + -program-prefix | --program-prefix | --program-prefi | --program-pref \ + | --program-pre | --program-pr | --program-p) + ac_prev=program_prefix ;; + -program-prefix=* | --program-prefix=* | --program-prefi=* \ + | --program-pref=* | --program-pre=* | --program-pr=* | --program-p=*) + program_prefix="$ac_optarg" ;; + + -program-suffix | --program-suffix | --program-suffi | --program-suff \ + | --program-suf | --program-su | --program-s) + ac_prev=program_suffix ;; + -program-suffix=* | --program-suffix=* | --program-suffi=* \ + | --program-suff=* | --program-suf=* | --program-su=* | --program-s=*) + program_suffix="$ac_optarg" ;; + + -program-transform-name | --program-transform-name \ + | --program-transform-nam | --program-transform-na \ + | --program-transform-n | --program-transform- \ + | --program-transform | --program-transfor \ + | --program-transfo | --program-transf \ + | --program-trans | --program-tran \ + | --progr-tra | --program-tr | --program-t) + ac_prev=program_transform_name ;; + -program-transform-name=* | --program-transform-name=* \ + | --program-transform-nam=* | --program-transform-na=* \ + | --program-transform-n=* | --program-transform-=* \ + | --program-transform=* | --program-transfor=* \ + | --program-transfo=* | --program-transf=* \ + | --program-trans=* | --program-tran=* \ + | --progr-tra=* | --program-tr=* | --program-t=*) + program_transform_name="$ac_optarg" ;; + + -q | -quiet | --quiet | --quie | --qui | --qu | --q \ + | -silent | --silent | --silen | --sile | --sil) + silent=yes ;; + + -sbindir | --sbindir | --sbindi | --sbind | --sbin | --sbi | --sb) + ac_prev=sbindir ;; + -sbindir=* | --sbindir=* | --sbindi=* | --sbind=* | --sbin=* \ + | --sbi=* | --sb=*) + sbindir="$ac_optarg" ;; + + -sharedstatedir | --sharedstatedir | --sharedstatedi \ + | --sharedstated | --sharedstate | --sharedstat | --sharedsta \ + | --sharedst | --shareds | --shared | --share | --shar \ + | --sha | --sh) + ac_prev=sharedstatedir ;; + -sharedstatedir=* | --sharedstatedir=* | --sharedstatedi=* \ + | --sharedstated=* | --sharedstate=* | --sharedstat=* | --sharedsta=* \ + | --sharedst=* | --shareds=* | --shared=* | --share=* | --shar=* \ + | --sha=* | --sh=*) + sharedstatedir="$ac_optarg" ;; + + -site | --site | --sit) + ac_prev=site ;; + -site=* | --site=* | --sit=*) + site="$ac_optarg" ;; + + -srcdir | --srcdir | --srcdi | --srcd | --src | --sr) + ac_prev=srcdir ;; + -srcdir=* | --srcdir=* | --srcdi=* | --srcd=* | --src=* | --sr=*) + srcdir="$ac_optarg" ;; + + -sysconfdir | --sysconfdir | --sysconfdi | --sysconfd | --sysconf \ + | --syscon | --sysco | --sysc | --sys | --sy) + ac_prev=sysconfdir ;; + -sysconfdir=* | --sysconfdir=* | --sysconfdi=* | --sysconfd=* | --sysconf=* \ + | --syscon=* | --sysco=* | --sysc=* | --sys=* | --sy=*) + sysconfdir="$ac_optarg" ;; + + -target | --target | --targe | --targ | --tar | --ta | --t) + ac_prev=target ;; + -target=* | --target=* | --targe=* | --targ=* | --tar=* | --ta=* | --t=*) + target="$ac_optarg" ;; + + -v | -verbose | --verbose | --verbos | --verbo | --verb) + verbose=yes ;; + + -version | --version | --versio | --versi | --vers) + echo "configure generated by autoconf version 2.13" + exit 0 ;; + + -with-* | --with-*) + ac_package=`echo $ac_option|sed -e 's/-*with-//' -e 's/=.*//'` + # Reject names that are not valid shell variable names. + if test -n "`echo $ac_package| sed 's/[-_a-zA-Z0-9]//g'`"; then + { echo "configure: error: $ac_package: invalid package name" 1>&2; exit 1; } + fi + ac_package=`echo $ac_package| sed 's/-/_/g'` + case "$ac_option" in + *=*) ;; + *) ac_optarg=yes ;; + esac + eval "with_${ac_package}='$ac_optarg'" ;; + + -without-* | --without-*) + ac_package=`echo $ac_option|sed -e 's/-*without-//'` + # Reject names that are not valid shell variable names. + if test -n "`echo $ac_package| sed 's/[-a-zA-Z0-9_]//g'`"; then + { echo "configure: error: $ac_package: invalid package name" 1>&2; exit 1; } + fi + ac_package=`echo $ac_package| sed 's/-/_/g'` + eval "with_${ac_package}=no" ;; + + --x) + # Obsolete; use --with-x. + with_x=yes ;; + + -x-includes | --x-includes | --x-include | --x-includ | --x-inclu \ + | --x-incl | --x-inc | --x-in | --x-i) + ac_prev=x_includes ;; + -x-includes=* | --x-includes=* | --x-include=* | --x-includ=* | --x-inclu=* \ + | --x-incl=* | --x-inc=* | --x-in=* | --x-i=*) + x_includes="$ac_optarg" ;; + + -x-libraries | --x-libraries | --x-librarie | --x-librari \ + | --x-librar | --x-libra | --x-libr | --x-lib | --x-li | --x-l) + ac_prev=x_libraries ;; + -x-libraries=* | --x-libraries=* | --x-librarie=* | --x-librari=* \ + | --x-librar=* | --x-libra=* | --x-libr=* | --x-lib=* | --x-li=* | --x-l=*) + x_libraries="$ac_optarg" ;; + + -*) { echo "configure: error: $ac_option: invalid option; use --help to show usage" 1>&2; exit 1; } + ;; + + *) + if test -n "`echo $ac_option| sed 's/[-a-z0-9.]//g'`"; then + echo "configure: warning: $ac_option: invalid host type" 1>&2 + fi + if test "x$nonopt" != xNONE; then + { echo "configure: error: can only configure for one host and one target at a time" 1>&2; exit 1; } + fi + nonopt="$ac_option" + ;; + + esac +done + +if test -n "$ac_prev"; then + { echo "configure: error: missing argument to --`echo $ac_prev | sed 's/_/-/g'`" 1>&2; exit 1; } +fi + +trap 'rm -fr conftest* confdefs* core core.* *.core $ac_clean_files; exit 1' 1 2 15 + +# File descriptor usage: +# 0 standard input +# 1 file creation +# 2 errors and warnings +# 3 some systems may open it to /dev/tty +# 4 used on the Kubota Titan +# 6 checking for... messages and results +# 5 compiler messages saved in config.log +if test "$silent" = yes; then + exec 6>/dev/null +else + exec 6>&1 +fi +exec 5>./config.log + +echo "\ +This file contains any messages produced by compilers while +running configure, to aid debugging if configure makes a mistake. +" 1>&5 + +# Strip out --no-create and --no-recursion so they do not pile up. +# Also quote any args containing shell metacharacters. +ac_configure_args= +for ac_arg +do + case "$ac_arg" in + -no-create | --no-create | --no-creat | --no-crea | --no-cre \ + | --no-cr | --no-c) ;; + -no-recursion | --no-recursion | --no-recursio | --no-recursi \ + | --no-recurs | --no-recur | --no-recu | --no-rec | --no-re | --no-r) ;; + *" "*|*" "*|*[\[\]\~\#\$\^\&\*\(\)\{\}\\\|\;\<\>\?]*) + ac_configure_args="$ac_configure_args '$ac_arg'" ;; + *) ac_configure_args="$ac_configure_args $ac_arg" ;; + esac +done + +# NLS nuisances. +# Only set these to C if already set. These must not be set unconditionally +# because not all systems understand e.g. LANG=C (notably SCO). +# Fixing LC_MESSAGES prevents Solaris sh from translating var values in `set'! +# Non-C LC_CTYPE values break the ctype check. +if test "${LANG+set}" = set; then LANG=C; export LANG; fi +if test "${LC_ALL+set}" = set; then LC_ALL=C; export LC_ALL; fi +if test "${LC_MESSAGES+set}" = set; then LC_MESSAGES=C; export LC_MESSAGES; fi +if test "${LC_CTYPE+set}" = set; then LC_CTYPE=C; export LC_CTYPE; fi + +# confdefs.h avoids OS command line length limits that DEFS can exceed. +rm -rf conftest* confdefs.h +# AIX cpp loses on an empty file, so make sure it contains at least a newline. +echo > confdefs.h + +# A filename unique to this package, relative to the directory that +# configure is in, which we can look for to find out if srcdir is correct. +ac_unique_file=Main.c + +# Find the source files, if location was not specified. +if test -z "$srcdir"; then + ac_srcdir_defaulted=yes + # Try the directory containing this script, then its parent. + ac_prog=$0 + ac_confdir=`echo $ac_prog|sed 's%/[^/][^/]*$%%'` + test "x$ac_confdir" = "x$ac_prog" && ac_confdir=. + srcdir=$ac_confdir + if test ! -r $srcdir/$ac_unique_file; then + srcdir=.. + fi +else + ac_srcdir_defaulted=no +fi +if test ! -r $srcdir/$ac_unique_file; then + if test "$ac_srcdir_defaulted" = yes; then + { echo "configure: error: can not find sources in $ac_confdir or .." 1>&2; exit 1; } + else + { echo "configure: error: can not find sources in $srcdir" 1>&2; exit 1; } + fi +fi +srcdir=`echo "${srcdir}" | sed 's%\([^/]\)/*$%\1%'` + +# Prefer explicitly selected file to automatically selected ones. +if test -z "$CONFIG_SITE"; then + if test "x$prefix" != xNONE; then + CONFIG_SITE="$prefix/share/config.site $prefix/etc/config.site" + else + CONFIG_SITE="$ac_default_prefix/share/config.site $ac_default_prefix/etc/config.site" + fi +fi +for ac_site_file in $CONFIG_SITE; do + if test -r "$ac_site_file"; then + echo "loading site script $ac_site_file" + . "$ac_site_file" + fi +done + +if test -r "$cache_file"; then + echo "loading cache $cache_file" + . $cache_file +else + echo "creating cache $cache_file" + > $cache_file +fi + +ac_ext=c +# CFLAGS is not in ac_cpp because -g, -O, etc. are not valid cpp options. +ac_cpp='$CPP $CPPFLAGS' +ac_compile='${CC-cc} -c $CFLAGS $CPPFLAGS conftest.$ac_ext 1>&5' +ac_link='${CC-cc} -o conftest${ac_exeext} $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS 1>&5' +cross_compiling=$ac_cv_prog_cc_cross + +ac_exeext= +ac_objext=o +if (echo "testing\c"; echo 1,2,3) | grep c >/dev/null; then + # Stardent Vistra SVR4 grep lacks -e, says ghazi@caip.rutgers.edu. + if (echo -n testing; echo 1,2,3) | sed s/-n/xn/ | grep xn >/dev/null; then + ac_n= ac_c=' +' ac_t=' ' + else + ac_n=-n ac_c= ac_t= + fi +else + ac_n= ac_c='\c' ac_t= +fi + + + + +ac_aux_dir= +for ac_dir in $srcdir $srcdir/.. $srcdir/../..; do + if test -f $ac_dir/install-sh; then + ac_aux_dir=$ac_dir + ac_install_sh="$ac_aux_dir/install-sh -c" + break + elif test -f $ac_dir/install.sh; then + ac_aux_dir=$ac_dir + ac_install_sh="$ac_aux_dir/install.sh -c" + break + fi +done +if test -z "$ac_aux_dir"; then + { echo "configure: error: can not find install-sh or install.sh in $srcdir $srcdir/.. $srcdir/../.." 1>&2; exit 1; } +fi +ac_config_guess=$ac_aux_dir/config.guess +ac_config_sub=$ac_aux_dir/config.sub +ac_configure=$ac_aux_dir/configure # This should be Cygnus configure. + + +# Make sure we can run config.sub. +if ${CONFIG_SHELL-/bin/sh} $ac_config_sub sun4 >/dev/null 2>&1; then : +else { echo "configure: error: can not run $ac_config_sub" 1>&2; exit 1; } +fi + +echo $ac_n "checking host system type""... $ac_c" 1>&6 +echo "configure:555: checking host system type" >&5 + +host_alias=$host +case "$host_alias" in +NONE) + case $nonopt in + NONE) + if host_alias=`${CONFIG_SHELL-/bin/sh} $ac_config_guess`; then : + else { echo "configure: error: can not guess host type; you must specify one" 1>&2; exit 1; } + fi ;; + *) host_alias=$nonopt ;; + esac ;; +esac + +host=`${CONFIG_SHELL-/bin/sh} $ac_config_sub $host_alias` +host_cpu=`echo $host | sed 's/^\([^-]*\)-\([^-]*\)-\(.*\)$/\1/'` +host_vendor=`echo $host | sed 's/^\([^-]*\)-\([^-]*\)-\(.*\)$/\2/'` +host_os=`echo $host | sed 's/^\([^-]*\)-\([^-]*\)-\(.*\)$/\3/'` +echo "$ac_t""$host" 1>&6 + +# Extract the first word of "gcc", so it can be a program name with args. +set dummy gcc; ac_word=$2 +echo $ac_n "checking for $ac_word""... $ac_c" 1>&6 +echo "configure:578: checking for $ac_word" >&5 +if eval "test \"`echo '$''{'ac_cv_prog_CC'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + if test -n "$CC"; then + ac_cv_prog_CC="$CC" # Let the user override the test. +else + IFS="${IFS= }"; ac_save_ifs="$IFS"; IFS=":" + ac_dummy="$PATH" + for ac_dir in $ac_dummy; do + test -z "$ac_dir" && ac_dir=. + if test -f $ac_dir/$ac_word; then + ac_cv_prog_CC="gcc" + break + fi + done + IFS="$ac_save_ifs" +fi +fi +CC="$ac_cv_prog_CC" +if test -n "$CC"; then + echo "$ac_t""$CC" 1>&6 +else + echo "$ac_t""no" 1>&6 +fi + +if test -z "$CC"; then + # Extract the first word of "cc", so it can be a program name with args. +set dummy cc; ac_word=$2 +echo $ac_n "checking for $ac_word""... $ac_c" 1>&6 +echo "configure:608: checking for $ac_word" >&5 +if eval "test \"`echo '$''{'ac_cv_prog_CC'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + if test -n "$CC"; then + ac_cv_prog_CC="$CC" # Let the user override the test. +else + IFS="${IFS= }"; ac_save_ifs="$IFS"; IFS=":" + ac_prog_rejected=no + ac_dummy="$PATH" + for ac_dir in $ac_dummy; do + test -z "$ac_dir" && ac_dir=. + if test -f $ac_dir/$ac_word; then + if test "$ac_dir/$ac_word" = "/usr/ucb/cc"; then + ac_prog_rejected=yes + continue + fi + ac_cv_prog_CC="cc" + break + fi + done + IFS="$ac_save_ifs" +if test $ac_prog_rejected = yes; then + # We found a bogon in the path, so make sure we never use it. + set dummy $ac_cv_prog_CC + shift + if test $# -gt 0; then + # We chose a different compiler from the bogus one. + # However, it has the same basename, so the bogon will be chosen + # first if we set CC to just the basename; use the full file name. + shift + set dummy "$ac_dir/$ac_word" "$@" + shift + ac_cv_prog_CC="$@" + fi +fi +fi +fi +CC="$ac_cv_prog_CC" +if test -n "$CC"; then + echo "$ac_t""$CC" 1>&6 +else + echo "$ac_t""no" 1>&6 +fi + + if test -z "$CC"; then + case "`uname -s`" in + *win32* | *WIN32*) + # Extract the first word of "cl", so it can be a program name with args. +set dummy cl; ac_word=$2 +echo $ac_n "checking for $ac_word""... $ac_c" 1>&6 +echo "configure:659: checking for $ac_word" >&5 +if eval "test \"`echo '$''{'ac_cv_prog_CC'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + if test -n "$CC"; then + ac_cv_prog_CC="$CC" # Let the user override the test. +else + IFS="${IFS= }"; ac_save_ifs="$IFS"; IFS=":" + ac_dummy="$PATH" + for ac_dir in $ac_dummy; do + test -z "$ac_dir" && ac_dir=. + if test -f $ac_dir/$ac_word; then + ac_cv_prog_CC="cl" + break + fi + done + IFS="$ac_save_ifs" +fi +fi +CC="$ac_cv_prog_CC" +if test -n "$CC"; then + echo "$ac_t""$CC" 1>&6 +else + echo "$ac_t""no" 1>&6 +fi + ;; + esac + fi + test -z "$CC" && { echo "configure: error: no acceptable cc found in \$PATH" 1>&2; exit 1; } +fi + +echo $ac_n "checking whether the C compiler ($CC $CFLAGS $LDFLAGS) works""... $ac_c" 1>&6 +echo "configure:691: checking whether the C compiler ($CC $CFLAGS $LDFLAGS) works" >&5 + +ac_ext=c +# CFLAGS is not in ac_cpp because -g, -O, etc. are not valid cpp options. +ac_cpp='$CPP $CPPFLAGS' +ac_compile='${CC-cc} -c $CFLAGS $CPPFLAGS conftest.$ac_ext 1>&5' +ac_link='${CC-cc} -o conftest${ac_exeext} $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS 1>&5' +cross_compiling=$ac_cv_prog_cc_cross + +cat > conftest.$ac_ext << EOF + +#line 702 "configure" +#include "confdefs.h" + +main(){return(0);} +EOF +if { (eval echo configure:707: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then + ac_cv_prog_cc_works=yes + # If we can't run a trivial program, we are probably using a cross compiler. + if (./conftest; exit) 2>/dev/null; then + ac_cv_prog_cc_cross=no + else + ac_cv_prog_cc_cross=yes + fi +else + echo "configure: failed program was:" >&5 + cat conftest.$ac_ext >&5 + ac_cv_prog_cc_works=no +fi +rm -fr conftest* +ac_ext=c +# CFLAGS is not in ac_cpp because -g, -O, etc. are not valid cpp options. +ac_cpp='$CPP $CPPFLAGS' +ac_compile='${CC-cc} -c $CFLAGS $CPPFLAGS conftest.$ac_ext 1>&5' +ac_link='${CC-cc} -o conftest${ac_exeext} $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS 1>&5' +cross_compiling=$ac_cv_prog_cc_cross + +echo "$ac_t""$ac_cv_prog_cc_works" 1>&6 +if test $ac_cv_prog_cc_works = no; then + { echo "configure: error: installation or configuration problem: C compiler cannot create executables." 1>&2; exit 1; } +fi +echo $ac_n "checking whether the C compiler ($CC $CFLAGS $LDFLAGS) is a cross-compiler""... $ac_c" 1>&6 +echo "configure:733: checking whether the C compiler ($CC $CFLAGS $LDFLAGS) is a cross-compiler" >&5 +echo "$ac_t""$ac_cv_prog_cc_cross" 1>&6 +cross_compiling=$ac_cv_prog_cc_cross + +echo $ac_n "checking whether we are using GNU C""... $ac_c" 1>&6 +echo "configure:738: checking whether we are using GNU C" >&5 +if eval "test \"`echo '$''{'ac_cv_prog_gcc'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + cat > conftest.c <&5; (eval $ac_try) 2>&5; }; } | egrep yes >/dev/null 2>&1; then + ac_cv_prog_gcc=yes +else + ac_cv_prog_gcc=no +fi +fi + +echo "$ac_t""$ac_cv_prog_gcc" 1>&6 + +if test $ac_cv_prog_gcc = yes; then + GCC=yes +else + GCC= +fi + +ac_test_CFLAGS="${CFLAGS+set}" +ac_save_CFLAGS="$CFLAGS" +CFLAGS= +echo $ac_n "checking whether ${CC-cc} accepts -g""... $ac_c" 1>&6 +echo "configure:766: checking whether ${CC-cc} accepts -g" >&5 +if eval "test \"`echo '$''{'ac_cv_prog_cc_g'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + echo 'void f(){}' > conftest.c +if test -z "`${CC-cc} -g -c conftest.c 2>&1`"; then + ac_cv_prog_cc_g=yes +else + ac_cv_prog_cc_g=no +fi +rm -f conftest* + +fi + +echo "$ac_t""$ac_cv_prog_cc_g" 1>&6 +if test "$ac_test_CFLAGS" = set; then + CFLAGS="$ac_save_CFLAGS" +elif test $ac_cv_prog_cc_g = yes; then + if test "$GCC" = yes; then + CFLAGS="-g -O2" + else + CFLAGS="-g" + fi +else + if test "$GCC" = yes; then + CFLAGS="-O2" + else + CFLAGS= + fi +fi + +# Find a good install program. We prefer a C program (faster), +# so one script is as good as another. But avoid the broken or +# incompatible versions: +# SysV /etc/install, /usr/sbin/install +# SunOS /usr/etc/install +# IRIX /sbin/install +# AIX /bin/install +# AIX 4 /usr/bin/installbsd, which doesn't work without a -g flag +# AFS /usr/afsws/bin/install, which mishandles nonexistent args +# SVR4 /usr/ucb/install, which tries to use the nonexistent group "staff" +# ./install, which can be erroneously created by make from ./install.sh. +echo $ac_n "checking for a BSD compatible install""... $ac_c" 1>&6 +echo "configure:809: checking for a BSD compatible install" >&5 +if test -z "$INSTALL"; then +if eval "test \"`echo '$''{'ac_cv_path_install'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + IFS="${IFS= }"; ac_save_IFS="$IFS"; IFS=":" + for ac_dir in $PATH; do + # Account for people who put trailing slashes in PATH elements. + case "$ac_dir/" in + /|./|.//|/etc/*|/usr/sbin/*|/usr/etc/*|/sbin/*|/usr/afsws/bin/*|/usr/ucb/*) ;; + *) + # OSF1 and SCO ODT 3.0 have their own names for install. + # Don't use installbsd from OSF since it installs stuff as root + # by default. + for ac_prog in ginstall scoinst install; do + if test -f $ac_dir/$ac_prog; then + if test $ac_prog = install && + grep dspmsg $ac_dir/$ac_prog >/dev/null 2>&1; then + # AIX install. It has an incompatible calling convention. + : + else + ac_cv_path_install="$ac_dir/$ac_prog -c" + break 2 + fi + fi + done + ;; + esac + done + IFS="$ac_save_IFS" + +fi + if test "${ac_cv_path_install+set}" = set; then + INSTALL="$ac_cv_path_install" + else + # As a last resort, use the slow shell script. We don't cache a + # path for INSTALL within a source directory, because that will + # break other packages using the cache if that directory is + # removed, or if the path is relative. + INSTALL="$ac_install_sh" + fi +fi +echo "$ac_t""$INSTALL" 1>&6 + +# Use test -z because SunOS4 sh mishandles braces in ${var-val}. +# It thinks the first close brace ends the variable substitution. +test -z "$INSTALL_PROGRAM" && INSTALL_PROGRAM='${INSTALL}' + +test -z "$INSTALL_SCRIPT" && INSTALL_SCRIPT='${INSTALL_PROGRAM}' + +test -z "$INSTALL_DATA" && INSTALL_DATA='${INSTALL} -m 644' + + + +ac_header_dirent=no +for ac_hdr in dirent.h sys/ndir.h sys/dir.h ndir.h +do +ac_safe=`echo "$ac_hdr" | sed 'y%./+-%__p_%'` +echo $ac_n "checking for $ac_hdr that defines DIR""... $ac_c" 1>&6 +echo "configure:868: checking for $ac_hdr that defines DIR" >&5 +if eval "test \"`echo '$''{'ac_cv_header_dirent_$ac_safe'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + cat > conftest.$ac_ext < +#include <$ac_hdr> +int main() { +DIR *dirp = 0; +; return 0; } +EOF +if { (eval echo configure:881: \"$ac_compile\") 1>&5; (eval $ac_compile) 2>&5; }; then + rm -rf conftest* + eval "ac_cv_header_dirent_$ac_safe=yes" +else + echo "configure: failed program was:" >&5 + cat conftest.$ac_ext >&5 + rm -rf conftest* + eval "ac_cv_header_dirent_$ac_safe=no" +fi +rm -f conftest* +fi +if eval "test \"`echo '$ac_cv_header_dirent_'$ac_safe`\" = yes"; then + echo "$ac_t""yes" 1>&6 + ac_tr_hdr=HAVE_`echo $ac_hdr | sed 'y%abcdefghijklmnopqrstuvwxyz./-%ABCDEFGHIJKLMNOPQRSTUVWXYZ___%'` + cat >> confdefs.h <&6 +fi +done +# Two versions of opendir et al. are in -ldir and -lx on SCO Xenix. +if test $ac_header_dirent = dirent.h; then +echo $ac_n "checking for opendir in -ldir""... $ac_c" 1>&6 +echo "configure:906: checking for opendir in -ldir" >&5 +ac_lib_var=`echo dir'_'opendir | sed 'y%./+-%__p_%'` +if eval "test \"`echo '$''{'ac_cv_lib_$ac_lib_var'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + ac_save_LIBS="$LIBS" +LIBS="-ldir $LIBS" +cat > conftest.$ac_ext <&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then + rm -rf conftest* + eval "ac_cv_lib_$ac_lib_var=yes" +else + echo "configure: failed program was:" >&5 + cat conftest.$ac_ext >&5 + rm -rf conftest* + eval "ac_cv_lib_$ac_lib_var=no" +fi +rm -f conftest* +LIBS="$ac_save_LIBS" + +fi +if eval "test \"`echo '$ac_cv_lib_'$ac_lib_var`\" = yes"; then + echo "$ac_t""yes" 1>&6 + LIBS="$LIBS -ldir" +else + echo "$ac_t""no" 1>&6 +fi + +else +echo $ac_n "checking for opendir in -lx""... $ac_c" 1>&6 +echo "configure:947: checking for opendir in -lx" >&5 +ac_lib_var=`echo x'_'opendir | sed 'y%./+-%__p_%'` +if eval "test \"`echo '$''{'ac_cv_lib_$ac_lib_var'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + ac_save_LIBS="$LIBS" +LIBS="-lx $LIBS" +cat > conftest.$ac_ext <&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then + rm -rf conftest* + eval "ac_cv_lib_$ac_lib_var=yes" +else + echo "configure: failed program was:" >&5 + cat conftest.$ac_ext >&5 + rm -rf conftest* + eval "ac_cv_lib_$ac_lib_var=no" +fi +rm -f conftest* +LIBS="$ac_save_LIBS" + +fi +if eval "test \"`echo '$ac_cv_lib_'$ac_lib_var`\" = yes"; then + echo "$ac_t""yes" 1>&6 + LIBS="$LIBS -lx" +else + echo "$ac_t""no" 1>&6 +fi + +fi + +echo $ac_n "checking how to run the C preprocessor""... $ac_c" 1>&6 +echo "configure:989: checking how to run the C preprocessor" >&5 +# On Suns, sometimes $CPP names a directory. +if test -n "$CPP" && test -d "$CPP"; then + CPP= +fi +if test -z "$CPP"; then +if eval "test \"`echo '$''{'ac_cv_prog_CPP'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + # This must be in double quotes, not single quotes, because CPP may get + # substituted into the Makefile and "${CC-cc}" will confuse make. + CPP="${CC-cc} -E" + # On the NeXT, cc -E runs the code through the compiler's parser, + # not just through cpp. + cat > conftest.$ac_ext < +Syntax Error +EOF +ac_try="$ac_cpp conftest.$ac_ext >/dev/null 2>conftest.out" +{ (eval echo configure:1010: \"$ac_try\") 1>&5; (eval $ac_try) 2>&5; } +ac_err=`grep -v '^ *+' conftest.out | grep -v "^conftest.${ac_ext}\$"` +if test -z "$ac_err"; then + : +else + echo "$ac_err" >&5 + echo "configure: failed program was:" >&5 + cat conftest.$ac_ext >&5 + rm -rf conftest* + CPP="${CC-cc} -E -traditional-cpp" + cat > conftest.$ac_ext < +Syntax Error +EOF +ac_try="$ac_cpp conftest.$ac_ext >/dev/null 2>conftest.out" +{ (eval echo configure:1027: \"$ac_try\") 1>&5; (eval $ac_try) 2>&5; } +ac_err=`grep -v '^ *+' conftest.out | grep -v "^conftest.${ac_ext}\$"` +if test -z "$ac_err"; then + : +else + echo "$ac_err" >&5 + echo "configure: failed program was:" >&5 + cat conftest.$ac_ext >&5 + rm -rf conftest* + CPP="${CC-cc} -nologo -E" + cat > conftest.$ac_ext < +Syntax Error +EOF +ac_try="$ac_cpp conftest.$ac_ext >/dev/null 2>conftest.out" +{ (eval echo configure:1044: \"$ac_try\") 1>&5; (eval $ac_try) 2>&5; } +ac_err=`grep -v '^ *+' conftest.out | grep -v "^conftest.${ac_ext}\$"` +if test -z "$ac_err"; then + : +else + echo "$ac_err" >&5 + echo "configure: failed program was:" >&5 + cat conftest.$ac_ext >&5 + rm -rf conftest* + CPP=/lib/cpp +fi +rm -f conftest* +fi +rm -f conftest* +fi +rm -f conftest* + ac_cv_prog_CPP="$CPP" +fi + CPP="$ac_cv_prog_CPP" +else + ac_cv_prog_CPP="$CPP" +fi +echo "$ac_t""$CPP" 1>&6 + +echo $ac_n "checking for ANSI C header files""... $ac_c" 1>&6 +echo "configure:1069: checking for ANSI C header files" >&5 +if eval "test \"`echo '$''{'ac_cv_header_stdc'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + cat > conftest.$ac_ext < +#include +#include +#include +EOF +ac_try="$ac_cpp conftest.$ac_ext >/dev/null 2>conftest.out" +{ (eval echo configure:1082: \"$ac_try\") 1>&5; (eval $ac_try) 2>&5; } +ac_err=`grep -v '^ *+' conftest.out | grep -v "^conftest.${ac_ext}\$"` +if test -z "$ac_err"; then + rm -rf conftest* + ac_cv_header_stdc=yes +else + echo "$ac_err" >&5 + echo "configure: failed program was:" >&5 + cat conftest.$ac_ext >&5 + rm -rf conftest* + ac_cv_header_stdc=no +fi +rm -f conftest* + +if test $ac_cv_header_stdc = yes; then + # SunOS 4.x string.h does not declare mem*, contrary to ANSI. +cat > conftest.$ac_ext < +EOF +if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | + egrep "memchr" >/dev/null 2>&1; then + : +else + rm -rf conftest* + ac_cv_header_stdc=no +fi +rm -f conftest* + +fi + +if test $ac_cv_header_stdc = yes; then + # ISC 2.0.2 stdlib.h does not declare free, contrary to ANSI. +cat > conftest.$ac_ext < +EOF +if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | + egrep "free" >/dev/null 2>&1; then + : +else + rm -rf conftest* + ac_cv_header_stdc=no +fi +rm -f conftest* + +fi + +if test $ac_cv_header_stdc = yes; then + # /bin/cc in Irix-4.0.5 gets non-ANSI ctype macros unless using -ansi. +if test "$cross_compiling" = yes; then + : +else + cat > conftest.$ac_ext < +#define ISLOWER(c) ('a' <= (c) && (c) <= 'z') +#define TOUPPER(c) (ISLOWER(c) ? 'A' + ((c) - 'a') : (c)) +#define XOR(e, f) (((e) && !(f)) || (!(e) && (f))) +int main () { int i; for (i = 0; i < 256; i++) +if (XOR (islower (i), ISLOWER (i)) || toupper (i) != TOUPPER (i)) exit(2); +exit (0); } + +EOF +if { (eval echo configure:1149: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext} && (./conftest; exit) 2>/dev/null +then + : +else + echo "configure: failed program was:" >&5 + cat conftest.$ac_ext >&5 + rm -fr conftest* + ac_cv_header_stdc=no +fi +rm -fr conftest* +fi + +fi +fi + +echo "$ac_t""$ac_cv_header_stdc" 1>&6 +if test $ac_cv_header_stdc = yes; then + cat >> confdefs.h <<\EOF +#define STDC_HEADERS 1 +EOF + +fi + +for ac_hdr in fcntl.h limits.h malloc.h stdlib.h strings.h sys/stat.h \ + sys/time.h sys/types.h unistd.h +do +ac_safe=`echo "$ac_hdr" | sed 'y%./+-%__p_%'` +echo $ac_n "checking for $ac_hdr""... $ac_c" 1>&6 +echo "configure:1177: checking for $ac_hdr" >&5 +if eval "test \"`echo '$''{'ac_cv_header_$ac_safe'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + cat > conftest.$ac_ext < +EOF +ac_try="$ac_cpp conftest.$ac_ext >/dev/null 2>conftest.out" +{ (eval echo configure:1187: \"$ac_try\") 1>&5; (eval $ac_try) 2>&5; } +ac_err=`grep -v '^ *+' conftest.out | grep -v "^conftest.${ac_ext}\$"` +if test -z "$ac_err"; then + rm -rf conftest* + eval "ac_cv_header_$ac_safe=yes" +else + echo "$ac_err" >&5 + echo "configure: failed program was:" >&5 + cat conftest.$ac_ext >&5 + rm -rf conftest* + eval "ac_cv_header_$ac_safe=no" +fi +rm -f conftest* +fi +if eval "test \"`echo '$ac_cv_header_'$ac_safe`\" = yes"; then + echo "$ac_t""yes" 1>&6 + ac_tr_hdr=HAVE_`echo $ac_hdr | sed 'y%abcdefghijklmnopqrstuvwxyz./-%ABCDEFGHIJKLMNOPQRSTUVWXYZ___%'` + cat >> confdefs.h <&6 +fi +done + + +echo $ac_n "checking for working const""... $ac_c" 1>&6 +echo "configure:1215: checking for working const" >&5 +if eval "test \"`echo '$''{'ac_cv_c_const'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + cat > conftest.$ac_ext <j = 5; +} +{ /* ULTRIX-32 V3.1 (Rev 9) vcc rejects this */ + const int foo = 10; +} + +; return 0; } +EOF +if { (eval echo configure:1269: \"$ac_compile\") 1>&5; (eval $ac_compile) 2>&5; }; then + rm -rf conftest* + ac_cv_c_const=yes +else + echo "configure: failed program was:" >&5 + cat conftest.$ac_ext >&5 + rm -rf conftest* + ac_cv_c_const=no +fi +rm -f conftest* +fi + +echo "$ac_t""$ac_cv_c_const" 1>&6 +if test $ac_cv_c_const = no; then + cat >> confdefs.h <<\EOF +#define const +EOF + +fi + +echo $ac_n "checking for mode_t""... $ac_c" 1>&6 +echo "configure:1290: checking for mode_t" >&5 +if eval "test \"`echo '$''{'ac_cv_type_mode_t'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + cat > conftest.$ac_ext < +#if STDC_HEADERS +#include +#include +#endif +EOF +if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | + egrep "(^|[^a-zA-Z_0-9])mode_t[^a-zA-Z_0-9]" >/dev/null 2>&1; then + rm -rf conftest* + ac_cv_type_mode_t=yes +else + rm -rf conftest* + ac_cv_type_mode_t=no +fi +rm -f conftest* + +fi +echo "$ac_t""$ac_cv_type_mode_t" 1>&6 +if test $ac_cv_type_mode_t = no; then + cat >> confdefs.h <<\EOF +#define mode_t int +EOF + +fi + +echo $ac_n "checking for off_t""... $ac_c" 1>&6 +echo "configure:1323: checking for off_t" >&5 +if eval "test \"`echo '$''{'ac_cv_type_off_t'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + cat > conftest.$ac_ext < +#if STDC_HEADERS +#include +#include +#endif +EOF +if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | + egrep "(^|[^a-zA-Z_0-9])off_t[^a-zA-Z_0-9]" >/dev/null 2>&1; then + rm -rf conftest* + ac_cv_type_off_t=yes +else + rm -rf conftest* + ac_cv_type_off_t=no +fi +rm -f conftest* + +fi +echo "$ac_t""$ac_cv_type_off_t" 1>&6 +if test $ac_cv_type_off_t = no; then + cat >> confdefs.h <<\EOF +#define off_t long +EOF + +fi + +echo $ac_n "checking for size_t""... $ac_c" 1>&6 +echo "configure:1356: checking for size_t" >&5 +if eval "test \"`echo '$''{'ac_cv_type_size_t'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + cat > conftest.$ac_ext < +#if STDC_HEADERS +#include +#include +#endif +EOF +if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | + egrep "(^|[^a-zA-Z_0-9])size_t[^a-zA-Z_0-9]" >/dev/null 2>&1; then + rm -rf conftest* + ac_cv_type_size_t=yes +else + rm -rf conftest* + ac_cv_type_size_t=no +fi +rm -f conftest* + +fi +echo "$ac_t""$ac_cv_type_size_t" 1>&6 +if test $ac_cv_type_size_t = no; then + cat >> confdefs.h <<\EOF +#define size_t unsigned +EOF + +fi + +echo $ac_n "checking whether struct tm is in sys/time.h or time.h""... $ac_c" 1>&6 +echo "configure:1389: checking whether struct tm is in sys/time.h or time.h" >&5 +if eval "test \"`echo '$''{'ac_cv_struct_tm'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + cat > conftest.$ac_ext < +#include +int main() { +struct tm *tp; tp->tm_sec; +; return 0; } +EOF +if { (eval echo configure:1402: \"$ac_compile\") 1>&5; (eval $ac_compile) 2>&5; }; then + rm -rf conftest* + ac_cv_struct_tm=time.h +else + echo "configure: failed program was:" >&5 + cat conftest.$ac_ext >&5 + rm -rf conftest* + ac_cv_struct_tm=sys/time.h +fi +rm -f conftest* +fi + +echo "$ac_t""$ac_cv_struct_tm" 1>&6 +if test $ac_cv_struct_tm = sys/time.h; then + cat >> confdefs.h <<\EOF +#define TM_IN_SYS_TIME 1 +EOF + +fi + +echo $ac_n "checking for uchar""... $ac_c" 1>&6 +echo "configure:1423: checking for uchar" >&5 +if eval "test \"`echo '$''{'ac_cv_type_uchar'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + cat > conftest.$ac_ext < +#if STDC_HEADERS +#include +#include +#endif +EOF +if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | + egrep "(^|[^a-zA-Z_0-9])uchar[^a-zA-Z_0-9]" >/dev/null 2>&1; then + rm -rf conftest* + ac_cv_type_uchar=yes +else + rm -rf conftest* + ac_cv_type_uchar=no +fi +rm -f conftest* + +fi +echo "$ac_t""$ac_cv_type_uchar" 1>&6 +if test $ac_cv_type_uchar = no; then + cat >> confdefs.h <<\EOF +#define uchar unsigned char +EOF + +fi + +echo $ac_n "checking for ushort""... $ac_c" 1>&6 +echo "configure:1456: checking for ushort" >&5 +if eval "test \"`echo '$''{'ac_cv_type_ushort'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + cat > conftest.$ac_ext < +#if STDC_HEADERS +#include +#include +#endif +EOF +if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | + egrep "(^|[^a-zA-Z_0-9])ushort[^a-zA-Z_0-9]" >/dev/null 2>&1; then + rm -rf conftest* + ac_cv_type_ushort=yes +else + rm -rf conftest* + ac_cv_type_ushort=no +fi +rm -f conftest* + +fi +echo "$ac_t""$ac_cv_type_ushort" 1>&6 +if test $ac_cv_type_ushort = no; then + cat >> confdefs.h <<\EOF +#define ushort unsigned short +EOF + +fi + +echo $ac_n "checking for uint""... $ac_c" 1>&6 +echo "configure:1489: checking for uint" >&5 +if eval "test \"`echo '$''{'ac_cv_type_uint'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + cat > conftest.$ac_ext < +#if STDC_HEADERS +#include +#include +#endif +EOF +if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | + egrep "(^|[^a-zA-Z_0-9])uint[^a-zA-Z_0-9]" >/dev/null 2>&1; then + rm -rf conftest* + ac_cv_type_uint=yes +else + rm -rf conftest* + ac_cv_type_uint=no +fi +rm -f conftest* + +fi +echo "$ac_t""$ac_cv_type_uint" 1>&6 +if test $ac_cv_type_uint = no; then + cat >> confdefs.h <<\EOF +#define uint unsigned int +EOF + +fi + +echo $ac_n "checking for ulong""... $ac_c" 1>&6 +echo "configure:1522: checking for ulong" >&5 +if eval "test \"`echo '$''{'ac_cv_type_ulong'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + cat > conftest.$ac_ext < +#if STDC_HEADERS +#include +#include +#endif +EOF +if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | + egrep "(^|[^a-zA-Z_0-9])ulong[^a-zA-Z_0-9]" >/dev/null 2>&1; then + rm -rf conftest* + ac_cv_type_ulong=yes +else + rm -rf conftest* + ac_cv_type_ulong=no +fi +rm -f conftest* + +fi +echo "$ac_t""$ac_cv_type_ulong" 1>&6 +if test $ac_cv_type_ulong = no; then + cat >> confdefs.h <<\EOF +#define ulong unsigned long +EOF + +fi + + +echo $ac_n "checking whether utime accepts a null argument""... $ac_c" 1>&6 +echo "configure:1556: checking whether utime accepts a null argument" >&5 +if eval "test \"`echo '$''{'ac_cv_func_utime_null'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + rm -f conftestdata; > conftestdata +# Sequent interprets utime(file, 0) to mean use start of epoch. Wrong. +if test "$cross_compiling" = yes; then + ac_cv_func_utime_null=no +else + cat > conftest.$ac_ext < +#include +main() { +struct stat s, t; +exit(!(stat ("conftestdata", &s) == 0 && utime("conftestdata", (long *)0) == 0 +&& stat("conftestdata", &t) == 0 && t.st_mtime >= s.st_mtime +&& t.st_mtime - s.st_mtime < 120)); +} +EOF +if { (eval echo configure:1577: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext} && (./conftest; exit) 2>/dev/null +then + ac_cv_func_utime_null=yes +else + echo "configure: failed program was:" >&5 + cat conftest.$ac_ext >&5 + rm -fr conftest* + ac_cv_func_utime_null=no +fi +rm -fr conftest* +fi + +rm -f core core.* *.core +fi + +echo "$ac_t""$ac_cv_func_utime_null" 1>&6 +if test $ac_cv_func_utime_null = yes; then + cat >> confdefs.h <<\EOF +#define HAVE_UTIME_NULL 1 +EOF + +fi + +for ac_func in memmove mkdir strtoul strcasecmp strncasecmp strerror +do +echo $ac_n "checking for $ac_func""... $ac_c" 1>&6 +echo "configure:1603: checking for $ac_func" >&5 +if eval "test \"`echo '$''{'ac_cv_func_$ac_func'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + cat > conftest.$ac_ext < +/* Override any gcc2 internal prototype to avoid an error. */ +/* We use char because int might match the return type of a gcc2 + builtin and then its argument prototype would still apply. */ +char $ac_func(); + +int main() { + +/* The GNU C library defines this for functions which it implements + to always fail with ENOSYS. Some functions are actually named + something starting with __ and the normal name is an alias. */ +#if defined (__stub_$ac_func) || defined (__stub___$ac_func) +choke me +#else +$ac_func(); +#endif + +; return 0; } +EOF +if { (eval echo configure:1631: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then + rm -rf conftest* + eval "ac_cv_func_$ac_func=yes" +else + echo "configure: failed program was:" >&5 + cat conftest.$ac_ext >&5 + rm -rf conftest* + eval "ac_cv_func_$ac_func=no" +fi +rm -f conftest* +fi + +if eval "test \"`echo '$ac_cv_func_'$ac_func`\" = yes"; then + echo "$ac_t""yes" 1>&6 + ac_tr_func=HAVE_`echo $ac_func | tr 'abcdefghijklmnopqrstuvwxyz' 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'` + cat >> confdefs.h <&6 +fi +done + + +if test "$host_os" = "beos"; then + if test "$prefix" = "NONE" -a \ + "$includedir" = '${prefix}/include' -a \ + "$libdir" = '${exec_prefix}/lib' -a \ + "$bindir" = '${exec_prefix}/bin' -a \ + "$mandir" = '${prefix}/man' + then + echo replacing install locations with BeOS values + prefix=/boot + includedir='${prefix}/develop/headers' + libdir='${exec_prefix}/home/config/lib' + bindir='${exec_prefix}/home/config/bin' + mandir='/tmp' + + + + + + fi +fi + + +if test "$host_cpu" = "powerpc" -a "$host_os" = "beos"; then + CC=cc + GCC= + CFLAGS='-proc 603 -opt full' + echo "forcing CC to \"$CC\" and CFLAGS to \"$CFLAGS\"" +fi + +if test -z "$GCC"; then + BUILD_FLAGS='$(OPT)' +else + BUILD_FLAGS='$(OPT) $(GCC_FLAGS)' +fi + + + +DMALLOC= +# Check whether --enable-dmalloc or --disable-dmalloc was given. +if test "${enable_dmalloc+set}" = set; then + enableval="$enable_dmalloc" + \ + echo "--- enabling dmalloc"; + DMALLOC="-L/usr/local/lib -ldmalloc"; cat >> confdefs.h <<\EOF +#define USE_DMALLOC 1 +EOF + +fi + + + +trap '' 1 2 15 +cat > confcache <<\EOF +# This file is a shell script that caches the results of configure +# tests run on this system so they can be shared between configure +# scripts and configure runs. It is not useful on other systems. +# If it contains results you don't want to keep, you may remove or edit it. +# +# By default, configure uses ./config.cache as the cache file, +# creating it if it does not exist already. You can give configure +# the --cache-file=FILE option to use a different cache file; that is +# what configure does when it calls configure scripts in +# subdirectories, so they share the cache. +# Giving --cache-file=/dev/null disables caching, for debugging configure. +# config.status only pays attention to the cache file if you give it the +# --recheck option to rerun configure. +# +EOF +# The following way of writing the cache mishandles newlines in values, +# but we know of no workaround that is simple, portable, and efficient. +# So, don't put newlines in cache variables' values. +# Ultrix sh set writes to stderr and can't be redirected directly, +# and sets the high bit in the cache file unless we assign to the vars. +(set) 2>&1 | + case `(ac_space=' '; set | grep ac_space) 2>&1` in + *ac_space=\ *) + # `set' does not quote correctly, so add quotes (double-quote substitution + # turns \\\\ into \\, and sed turns \\ into \). + sed -n \ + -e "s/'/'\\\\''/g" \ + -e "s/^\\([a-zA-Z0-9_]*_cv_[a-zA-Z0-9_]*\\)=\\(.*\\)/\\1=\${\\1='\\2'}/p" + ;; + *) + # `set' quotes correctly as required by POSIX, so do not add quotes. + sed -n -e 's/^\([a-zA-Z0-9_]*_cv_[a-zA-Z0-9_]*\)=\(.*\)/\1=${\1=\2}/p' + ;; + esac >> confcache +if cmp -s $cache_file confcache; then + : +else + if test -w $cache_file; then + echo "updating cache $cache_file" + cat confcache > $cache_file + else + echo "not updating unwritable cache $cache_file" + fi +fi +rm -f confcache + +trap 'rm -fr conftest* confdefs* core core.* *.core $ac_clean_files; exit 1' 1 2 15 + +test "x$prefix" = xNONE && prefix=$ac_default_prefix +# Let make expand exec_prefix. +test "x$exec_prefix" = xNONE && exec_prefix='${prefix}' + +# Any assignment to VPATH causes Sun make to only execute +# the first set of double-colon rules, so remove it if not needed. +# If there is a colon in the path, we need to keep it. +if test "x$srcdir" = x.; then + ac_vpsub='/^[ ]*VPATH[ ]*=[^:]*$/d' +fi + +trap 'rm -f $CONFIG_STATUS conftest*; exit 1' 1 2 15 + +DEFS=-DHAVE_CONFIG_H + +# Without the "./", some shells look in PATH for config.status. +: ${CONFIG_STATUS=./config.status} + +echo creating $CONFIG_STATUS +rm -f $CONFIG_STATUS +cat > $CONFIG_STATUS </dev/null | sed 1q`: +# +# $0 $ac_configure_args +# +# Compiler output produced by configure, useful for debugging +# configure, is in ./config.log if it exists. + +ac_cs_usage="Usage: $CONFIG_STATUS [--recheck] [--version] [--help]" +for ac_option +do + case "\$ac_option" in + -recheck | --recheck | --rechec | --reche | --rech | --rec | --re | --r) + echo "running \${CONFIG_SHELL-/bin/sh} $0 $ac_configure_args --no-create --no-recursion" + exec \${CONFIG_SHELL-/bin/sh} $0 $ac_configure_args --no-create --no-recursion ;; + -version | --version | --versio | --versi | --vers | --ver | --ve | --v) + echo "$CONFIG_STATUS generated by autoconf version 2.13" + exit 0 ;; + -help | --help | --hel | --he | --h) + echo "\$ac_cs_usage"; exit 0 ;; + *) echo "\$ac_cs_usage"; exit 1 ;; + esac +done + +ac_given_srcdir=$srcdir +ac_given_INSTALL="$INSTALL" + +trap 'rm -fr `echo "Makefile config.h" | sed "s/:[^ ]*//g"` conftest*; exit 1' 1 2 15 +EOF +cat >> $CONFIG_STATUS < conftest.subs <<\\CEOF +$ac_vpsub +$extrasub +s%@SHELL@%$SHELL%g +s%@CFLAGS@%$CFLAGS%g +s%@CPPFLAGS@%$CPPFLAGS%g +s%@CXXFLAGS@%$CXXFLAGS%g +s%@FFLAGS@%$FFLAGS%g +s%@DEFS@%$DEFS%g +s%@LDFLAGS@%$LDFLAGS%g +s%@LIBS@%$LIBS%g +s%@exec_prefix@%$exec_prefix%g +s%@prefix@%$prefix%g +s%@program_transform_name@%$program_transform_name%g +s%@bindir@%$bindir%g +s%@sbindir@%$sbindir%g +s%@libexecdir@%$libexecdir%g +s%@datadir@%$datadir%g +s%@sysconfdir@%$sysconfdir%g +s%@sharedstatedir@%$sharedstatedir%g +s%@localstatedir@%$localstatedir%g +s%@libdir@%$libdir%g +s%@includedir@%$includedir%g +s%@oldincludedir@%$oldincludedir%g +s%@infodir@%$infodir%g +s%@mandir@%$mandir%g +s%@host@%$host%g +s%@host_alias@%$host_alias%g +s%@host_cpu@%$host_cpu%g +s%@host_vendor@%$host_vendor%g +s%@host_os@%$host_os%g +s%@CC@%$CC%g +s%@INSTALL_PROGRAM@%$INSTALL_PROGRAM%g +s%@INSTALL_SCRIPT@%$INSTALL_SCRIPT%g +s%@INSTALL_DATA@%$INSTALL_DATA%g +s%@CPP@%$CPP%g +s%@BUILD_FLAGS@%$BUILD_FLAGS%g +s%@DMALLOC@%$DMALLOC%g + +CEOF +EOF + +cat >> $CONFIG_STATUS <<\EOF + +# Split the substitutions into bite-sized pieces for seds with +# small command number limits, like on Digital OSF/1 and HP-UX. +ac_max_sed_cmds=90 # Maximum number of lines to put in a sed script. +ac_file=1 # Number of current file. +ac_beg=1 # First line for current file. +ac_end=$ac_max_sed_cmds # Line after last line for current file. +ac_more_lines=: +ac_sed_cmds="" +while $ac_more_lines; do + if test $ac_beg -gt 1; then + sed "1,${ac_beg}d; ${ac_end}q" conftest.subs > conftest.s$ac_file + else + sed "${ac_end}q" conftest.subs > conftest.s$ac_file + fi + if test ! -s conftest.s$ac_file; then + ac_more_lines=false + rm -f conftest.s$ac_file + else + if test -z "$ac_sed_cmds"; then + ac_sed_cmds="sed -f conftest.s$ac_file" + else + ac_sed_cmds="$ac_sed_cmds | sed -f conftest.s$ac_file" + fi + ac_file=`expr $ac_file + 1` + ac_beg=$ac_end + ac_end=`expr $ac_end + $ac_max_sed_cmds` + fi +done +if test -z "$ac_sed_cmds"; then + ac_sed_cmds=cat +fi +EOF + +cat >> $CONFIG_STATUS <> $CONFIG_STATUS <<\EOF +for ac_file in .. $CONFIG_FILES; do if test "x$ac_file" != x..; then + # Support "outfile[:infile[:infile...]]", defaulting infile="outfile.in". + case "$ac_file" in + *:*) ac_file_in=`echo "$ac_file"|sed 's%[^:]*:%%'` + ac_file=`echo "$ac_file"|sed 's%:.*%%'` ;; + *) ac_file_in="${ac_file}.in" ;; + esac + + # Adjust a relative srcdir, top_srcdir, and INSTALL for subdirectories. + + # Remove last slash and all that follows it. Not all systems have dirname. + ac_dir=`echo $ac_file|sed 's%/[^/][^/]*$%%'` + if test "$ac_dir" != "$ac_file" && test "$ac_dir" != .; then + # The file is in a subdirectory. + test ! -d "$ac_dir" && mkdir "$ac_dir" + ac_dir_suffix="/`echo $ac_dir|sed 's%^\./%%'`" + # A "../" for each directory in $ac_dir_suffix. + ac_dots=`echo $ac_dir_suffix|sed 's%/[^/]*%../%g'` + else + ac_dir_suffix= ac_dots= + fi + + case "$ac_given_srcdir" in + .) srcdir=. + if test -z "$ac_dots"; then top_srcdir=. + else top_srcdir=`echo $ac_dots|sed 's%/$%%'`; fi ;; + /*) srcdir="$ac_given_srcdir$ac_dir_suffix"; top_srcdir="$ac_given_srcdir" ;; + *) # Relative path. + srcdir="$ac_dots$ac_given_srcdir$ac_dir_suffix" + top_srcdir="$ac_dots$ac_given_srcdir" ;; + esac + + case "$ac_given_INSTALL" in + [/$]*) INSTALL="$ac_given_INSTALL" ;; + *) INSTALL="$ac_dots$ac_given_INSTALL" ;; + esac + + echo creating "$ac_file" + rm -f "$ac_file" + configure_input="Generated automatically from `echo $ac_file_in|sed 's%.*/%%'` by configure." + case "$ac_file" in + *Makefile*) ac_comsub="1i\\ +# $configure_input" ;; + *) ac_comsub= ;; + esac + + ac_file_inputs=`echo $ac_file_in|sed -e "s%^%$ac_given_srcdir/%" -e "s%:% $ac_given_srcdir/%g"` + sed -e "$ac_comsub +s%@configure_input@%$configure_input%g +s%@srcdir@%$srcdir%g +s%@top_srcdir@%$top_srcdir%g +s%@INSTALL@%$INSTALL%g +" $ac_file_inputs | (eval "$ac_sed_cmds") > $ac_file +fi; done +rm -f conftest.s* + +# These sed commands are passed to sed as "A NAME B NAME C VALUE D", where +# NAME is the cpp macro being defined and VALUE is the value it is being given. +# +# ac_d sets the value in "#define NAME VALUE" lines. +ac_dA='s%^\([ ]*\)#\([ ]*define[ ][ ]*\)' +ac_dB='\([ ][ ]*\)[^ ]*%\1#\2' +ac_dC='\3' +ac_dD='%g' +# ac_u turns "#undef NAME" with trailing blanks into "#define NAME VALUE". +ac_uA='s%^\([ ]*\)#\([ ]*\)undef\([ ][ ]*\)' +ac_uB='\([ ]\)%\1#\2define\3' +ac_uC=' ' +ac_uD='\4%g' +# ac_e turns "#undef NAME" without trailing blanks into "#define NAME VALUE". +ac_eA='s%^\([ ]*\)#\([ ]*\)undef\([ ][ ]*\)' +ac_eB='$%\1#\2define\3' +ac_eC=' ' +ac_eD='%g' + +if test "${CONFIG_HEADERS+set}" != set; then +EOF +cat >> $CONFIG_STATUS <> $CONFIG_STATUS <<\EOF +fi +for ac_file in .. $CONFIG_HEADERS; do if test "x$ac_file" != x..; then + # Support "outfile[:infile[:infile...]]", defaulting infile="outfile.in". + case "$ac_file" in + *:*) ac_file_in=`echo "$ac_file"|sed 's%[^:]*:%%'` + ac_file=`echo "$ac_file"|sed 's%:.*%%'` ;; + *) ac_file_in="${ac_file}.in" ;; + esac + + echo creating $ac_file + + rm -f conftest.frag conftest.in conftest.out + ac_file_inputs=`echo $ac_file_in|sed -e "s%^%$ac_given_srcdir/%" -e "s%:% $ac_given_srcdir/%g"` + cat $ac_file_inputs > conftest.in + +EOF + +# Transform confdefs.h into a sed script conftest.vals that substitutes +# the proper values into config.h.in to produce config.h. And first: +# Protect against being on the right side of a sed subst in config.status. +# Protect against being in an unquoted here document in config.status. +rm -f conftest.vals +cat > conftest.hdr <<\EOF +s/[\\&%]/\\&/g +s%[\\$`]%\\&%g +s%#define \([A-Za-z_][A-Za-z0-9_]*\) *\(.*\)%${ac_dA}\1${ac_dB}\1${ac_dC}\2${ac_dD}%gp +s%ac_d%ac_u%gp +s%ac_u%ac_e%gp +EOF +sed -n -f conftest.hdr confdefs.h > conftest.vals +rm -f conftest.hdr + +# This sed command replaces #undef with comments. This is necessary, for +# example, in the case of _POSIX_SOURCE, which is predefined and required +# on some systems where configure will not decide to define it. +cat >> conftest.vals <<\EOF +s%^[ ]*#[ ]*undef[ ][ ]*[a-zA-Z_][a-zA-Z_0-9]*%/* & */% +EOF + +# Break up conftest.vals because some shells have a limit on +# the size of here documents, and old seds have small limits too. + +rm -f conftest.tail +while : +do + ac_lines=`grep -c . conftest.vals` + # grep -c gives empty output for an empty file on some AIX systems. + if test -z "$ac_lines" || test "$ac_lines" -eq 0; then break; fi + # Write a limited-size here document to conftest.frag. + echo ' cat > conftest.frag <> $CONFIG_STATUS + sed ${ac_max_here_lines}q conftest.vals >> $CONFIG_STATUS + echo 'CEOF + sed -f conftest.frag conftest.in > conftest.out + rm -f conftest.in + mv conftest.out conftest.in +' >> $CONFIG_STATUS + sed 1,${ac_max_here_lines}d conftest.vals > conftest.tail + rm -f conftest.vals + mv conftest.tail conftest.vals +done +rm -f conftest.vals + +cat >> $CONFIG_STATUS <<\EOF + rm -f conftest.frag conftest.h + echo "/* $ac_file. Generated automatically by configure. */" > conftest.h + cat conftest.in >> conftest.h + rm -f conftest.in + if cmp -s $ac_file conftest.h 2>/dev/null; then + echo "$ac_file is unchanged" + rm -f conftest.h + else + # Remove last slash and all that follows it. Not all systems have dirname. + ac_dir=`echo $ac_file|sed 's%/[^/][^/]*$%%'` + if test "$ac_dir" != "$ac_file" && test "$ac_dir" != .; then + # The file is in a subdirectory. + test ! -d "$ac_dir" && mkdir "$ac_dir" + fi + rm -f $ac_file + mv conftest.h $ac_file + fi +fi; done + +EOF +cat >> $CONFIG_STATUS <> $CONFIG_STATUS <<\EOF + +exit 0 +EOF +chmod +x $CONFIG_STATUS +rm -fr confdefs* $ac_clean_files +test "$no_create" = yes || ${CONFIG_SHELL-/bin/sh} $CONFIG_STATUS || exit 1 + diff --git a/nulib2/configure.in b/nulib2/configure.in new file mode 100644 index 0000000..0c077fb --- /dev/null +++ b/nulib2/configure.in @@ -0,0 +1,86 @@ +dnl Process this file with autoconf to produce a configure script. +AC_INIT(Main.c) +AC_CONFIG_HEADER(config.h) + +dnl Checks for programs. +AC_CANONICAL_HOST +AC_PROG_CC +AC_PROG_INSTALL + +dnl Checks for libraries. +dnl Replace `main' with a function in -lr: +dnl AC_CHECK_LIB(r, main) + +dnl Checks for header files. +AC_HEADER_DIRENT +AC_HEADER_STDC +AC_CHECK_HEADERS(fcntl.h limits.h malloc.h stdlib.h strings.h sys/stat.h \ + sys/time.h sys/types.h unistd.h) + +dnl Checks for typedefs, structures, and compiler characteristics. +AC_C_CONST +AC_TYPE_MODE_T +AC_TYPE_OFF_T +AC_TYPE_SIZE_T +AC_STRUCT_TM +AC_CHECK_TYPE(uchar, unsigned char) +AC_CHECK_TYPE(ushort, unsigned short) +AC_CHECK_TYPE(uint, unsigned int) +AC_CHECK_TYPE(ulong, unsigned long) + +dnl Checks for library functions. +dnl AC_FUNC_SETVBUF_REVERSED +AC_FUNC_UTIME_NULL +AC_CHECK_FUNCS(memmove mkdir strtoul strcasecmp strncasecmp strerror) + +dnl BeOS doesn't like /usr/local/include, and gets feisty about it. If libdir +dnl and includedir are set to defaults, replace them with BeOS values. This +dnl might be going a little too far... +if test "$host_os" = "beos"; then + if test "$prefix" = "NONE" -a \ + "$includedir" = '${prefix}/include' -a \ + "$libdir" = '${exec_prefix}/lib' -a \ + "$bindir" = '${exec_prefix}/bin' -a \ + "$mandir" = '${prefix}/man' + then + echo replacing install locations with BeOS values + prefix=/boot + includedir='${prefix}/develop/headers' + libdir='${exec_prefix}/home/config/lib' + bindir='${exec_prefix}/home/config/bin' + mandir='/tmp' + AC_SUBST(prefix) + AC_SUBST(includedir) + AC_SUBST(libdir) + AC_SUBST(bindir) + AC_SUBST(mandir) + fi +fi + + +dnl Figure out what the build and link flags should be +if test "$host_cpu" = "powerpc" -a "$host_os" = "beos"; then + dnl BeOS/PPC with Metrowerks compiler + CC=cc + GCC= + CFLAGS='-proc 603 -opt full' + echo "forcing CC to \"$CC\" and CFLAGS to \"$CFLAGS\"" +fi + +dnl if we're using gcc, include gcc-specific warning flags +dnl ( +if test -z "$GCC"; then + BUILD_FLAGS='$(OPT)' +else + BUILD_FLAGS='$(OPT) $(GCC_FLAGS)' +fi + +AC_SUBST(BUILD_FLAGS) + +DMALLOC= +AC_ARG_ENABLE(dmalloc, [ --enable-dmalloc: do dmalloc stuff], \ + [ echo "--- enabling dmalloc"; + DMALLOC="-L/usr/local/lib -ldmalloc"; AC_DEFINE(USE_DMALLOC) ]) +AC_SUBST(DMALLOC) + +AC_OUTPUT(Makefile) diff --git a/nulib2/install-sh b/nulib2/install-sh new file mode 100755 index 0000000..e9de238 --- /dev/null +++ b/nulib2/install-sh @@ -0,0 +1,251 @@ +#!/bin/sh +# +# install - install a program, script, or datafile +# This comes from X11R5 (mit/util/scripts/install.sh). +# +# Copyright 1991 by the Massachusetts Institute of Technology +# +# Permission to use, copy, modify, distribute, and sell this software and its +# documentation for any purpose is hereby granted without fee, provided that +# the above copyright notice appear in all copies and that both that +# copyright notice and this permission notice appear in supporting +# documentation, and that the name of M.I.T. not be used in advertising or +# publicity pertaining to distribution of the software without specific, +# written prior permission. M.I.T. makes no representations about the +# suitability of this software for any purpose. It is provided "as is" +# without express or implied warranty. +# +# Calling this script install-sh is preferred over install.sh, to prevent +# `make' implicit rules from creating a file called install from it +# when there is no Makefile. +# +# This script is compatible with the BSD install script, but was written +# from scratch. It can only install one file at a time, a restriction +# shared with many OS's install programs. + + +# set DOITPROG to echo to test this script + +# Don't use :- since 4.3BSD and earlier shells don't like it. +doit="${DOITPROG-}" + + +# put in absolute paths if you don't have them in your path; or use env. vars. + +mvprog="${MVPROG-mv}" +cpprog="${CPPROG-cp}" +chmodprog="${CHMODPROG-chmod}" +chownprog="${CHOWNPROG-chown}" +chgrpprog="${CHGRPPROG-chgrp}" +stripprog="${STRIPPROG-strip}" +rmprog="${RMPROG-rm}" +mkdirprog="${MKDIRPROG-mkdir}" + +transformbasename="" +transform_arg="" +instcmd="$mvprog" +chmodcmd="$chmodprog 0755" +chowncmd="" +chgrpcmd="" +stripcmd="" +rmcmd="$rmprog -f" +mvcmd="$mvprog" +src="" +dst="" +dir_arg="" + +while [ x"$1" != x ]; do + case $1 in + -c) instcmd="$cpprog" + shift + continue;; + + -d) dir_arg=true + shift + continue;; + + -m) chmodcmd="$chmodprog $2" + shift + shift + continue;; + + -o) chowncmd="$chownprog $2" + shift + shift + continue;; + + -g) chgrpcmd="$chgrpprog $2" + shift + shift + continue;; + + -s) stripcmd="$stripprog" + shift + continue;; + + -t=*) transformarg=`echo $1 | sed 's/-t=//'` + shift + continue;; + + -b=*) transformbasename=`echo $1 | sed 's/-b=//'` + shift + continue;; + + *) if [ x"$src" = x ] + then + src=$1 + else + # this colon is to work around a 386BSD /bin/sh bug + : + dst=$1 + fi + shift + continue;; + esac +done + +if [ x"$src" = x ] +then + echo "install: no input file specified" + exit 1 +else + true +fi + +if [ x"$dir_arg" != x ]; then + dst=$src + src="" + + if [ -d $dst ]; then + instcmd=: + chmodcmd="" + else + instcmd=mkdir + fi +else + +# Waiting for this to be detected by the "$instcmd $src $dsttmp" command +# might cause directories to be created, which would be especially bad +# if $src (and thus $dsttmp) contains '*'. + + if [ -f $src -o -d $src ] + then + true + else + echo "install: $src does not exist" + exit 1 + fi + + if [ x"$dst" = x ] + then + echo "install: no destination specified" + exit 1 + else + true + fi + +# If destination is a directory, append the input filename; if your system +# does not like double slashes in filenames, you may need to add some logic + + if [ -d $dst ] + then + dst="$dst"/`basename $src` + else + true + fi +fi + +## this sed command emulates the dirname command +dstdir=`echo $dst | sed -e 's,[^/]*$,,;s,/$,,;s,^$,.,'` + +# Make sure that the destination directory exists. +# this part is taken from Noah Friedman's mkinstalldirs script + +# Skip lots of stat calls in the usual case. +if [ ! -d "$dstdir" ]; then +defaultIFS=' +' +IFS="${IFS-${defaultIFS}}" + +oIFS="${IFS}" +# Some sh's can't handle IFS=/ for some reason. +IFS='%' +set - `echo ${dstdir} | sed -e 's@/@%@g' -e 's@^%@/@'` +IFS="${oIFS}" + +pathcomp='' + +while [ $# -ne 0 ] ; do + pathcomp="${pathcomp}${1}" + shift + + if [ ! -d "${pathcomp}" ] ; + then + $mkdirprog "${pathcomp}" + else + true + fi + + pathcomp="${pathcomp}/" +done +fi + +if [ x"$dir_arg" != x ] +then + $doit $instcmd $dst && + + if [ x"$chowncmd" != x ]; then $doit $chowncmd $dst; else true ; fi && + if [ x"$chgrpcmd" != x ]; then $doit $chgrpcmd $dst; else true ; fi && + if [ x"$stripcmd" != x ]; then $doit $stripcmd $dst; else true ; fi && + if [ x"$chmodcmd" != x ]; then $doit $chmodcmd $dst; else true ; fi +else + +# If we're going to rename the final executable, determine the name now. + + if [ x"$transformarg" = x ] + then + dstfile=`basename $dst` + else + dstfile=`basename $dst $transformbasename | + sed $transformarg`$transformbasename + fi + +# don't allow the sed command to completely eliminate the filename + + if [ x"$dstfile" = x ] + then + dstfile=`basename $dst` + else + true + fi + +# Make a temp file name in the proper directory. + + dsttmp=$dstdir/#inst.$$# + +# Move or copy the file name to the temp name + + $doit $instcmd $src $dsttmp && + + trap "rm -f ${dsttmp}" 0 && + +# and set any options; do chmod last to preserve setuid bits + +# If any of these fail, we abort the whole thing. If we want to +# ignore errors from any of these, just make sure not to ignore +# errors from the above "$doit $instcmd $src $dsttmp" command. + + if [ x"$chowncmd" != x ]; then $doit $chowncmd $dsttmp; else true;fi && + if [ x"$chgrpcmd" != x ]; then $doit $chgrpcmd $dsttmp; else true;fi && + if [ x"$stripcmd" != x ]; then $doit $stripcmd $dsttmp; else true;fi && + if [ x"$chmodcmd" != x ]; then $doit $chmodcmd $dsttmp; else true;fi && + +# Now rename the file to the real destination. + + $doit $rmcmd -f $dstdir/$dstfile && + $doit $mvcmd $dsttmp $dstdir/$dstfile + +fi && + + +exit 0 diff --git a/nulib2/mkinstalldirs b/nulib2/mkinstalldirs new file mode 100755 index 0000000..936cf34 --- /dev/null +++ b/nulib2/mkinstalldirs @@ -0,0 +1,34 @@ +#! /bin/sh +# mkinstalldirs --- make directory hierarchy +# Author: Noah Friedman +# Created: 1993-05-16 +# Last modified: 1995-03-05 +# Public domain + +errstatus=0 + +for file in ${1+"$@"} ; do + set fnord `echo ":$file" | sed -ne 's/^:\//#/;s/^://;s/\// /g;s/^#/\//;p'` + shift + + pathcomp= + for d in ${1+"$@"} ; do + pathcomp="$pathcomp$d" + case "$pathcomp" in + -* ) pathcomp=./$pathcomp ;; + esac + + if test ! -d "$pathcomp"; then + echo "mkdir $pathcomp" 1>&2 + mkdir "$pathcomp" > /dev/null 2>&1 || lasterr=$? + fi + + if test ! -d "$pathcomp"; then + errstatus=$lasterr + fi + + pathcomp="$pathcomp/" + done +done + +exit $errstatus diff --git a/nulib2/nulib2.1 b/nulib2/nulib2.1 new file mode 100644 index 0000000..393dc0f --- /dev/null +++ b/nulib2/nulib2.1 @@ -0,0 +1,178 @@ +.\" nulib2.1 +.\" 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. +.\" +.\" The general structure of this man page was borrowed from "zip.1" in +.\" the Red Hat Linux 6.0 distribution. +.\" +.TH NULIB2 1L "17 Jan 2000" +.SH NAME +nulib2 \- package and compress (archive) files +.SH SYNOPSIS +.B nulib2 +.RB \-command[modifiers] +.I archive +.I [ filenames ] +.SH DESCRIPTION +.I nulib2 +is a disk and file archiver for NuFX (ShrinkIt) files. It can add files +to and extract files from +.IR .SHK , +.IR .BXY , +.IR .SEA +(as created by GS/ShrinkIt), and +.I .BSE +files. +.LP +When extracting, testing, or listing the contents of an archive, you can +specify "-" for the archive name. The archive will be read from stdin. +.LP +Filenames are considered case-sensitive. +.\" .LP +.\" The +.\" .I filenames +.\" will be compared in a case-sensitive fashion. While this would be +.\" inappropriate for most UNIX systems, it makes sense for Apple II archives, +.\" because most Apple II filesystems are case-insensitive. +.LP +This man page contains a summary of available options. For full +documentation and the latest versions, visit http://www.nulib.com/. +.SH "OPTIONS" +.TP +.B \-a +Add files to an archive. If the archive does not exist, a new one +will be created. The list of files to add must be given. +.TP +.B \-d +Delete files from an archive. The set of files to delete must be provided. +.TP +.B \-i +Integrity test. If no files are listed, all files in the archive are +tested. +.TP +.B \-p +Pipe extraction. All extracted files are written to stdout instead of +a file on disk. Normal archive progress messages are suppressed. +.TP +.B \-t +Table of contents. Provides a simple list of files in the archive, one +per line. +.TP +.B \-v +Verbose table of contents. Output similar to what ShrinkIt displays is +shown. +.TP +.B \-x +Extract files from an archive. If no files are listed, all files in +the archive are extracted. +.\" There's also a '-z' command that does a verbose archive dump, but it's +.\" only available if NufxLib was built with debugging enabled. +.SH "MODIFIERS" +.TP +.B \-0 +Don't use compression. Files added will be stored without compression. +.TP +.B \-c +Comments. When extracting, comments will be displayed. When adding, +you will be prompted to enter a one-line comment for every file. +.TP +.B \-e +Preserve ProDOS file types. See the ProDOS File Type Preservation document +on http://www.nulib.com/ for details on how this works. +.TP +.B \-ee +Preserve file types, using extended names. A file extension is appended +to extracted files. Useful on operating systems like Windows, where +filename extensions are important. When adding files, +.I nulib2 +will try to guess at correct file types by examining the filename extension. +.TP +.B \-f +Freshen files. When adding, files in the archive that are older than files +on disk are "freshened", meaning that no new files are added, and files +that are the same age or newer aren't touched. Works similarly when +extracting. +.TP +.B \-j +Junk directory names. Only the filename is kept; the rest of the pathname +is thrown away. Empty directories aren't stored. Works when adding or +extracting. +.TP +.B \-k +Store files as disk images. Files that are a multiple of 512 bytes will +be added as disk images rather than normal files. This does not override +the "-e" flag. +.TP +.B \-l +Auto-convert text files. A reasonably smart algorithm is used to identify +which files are text and which aren't during extraction. It then converts +whatever EOL +indicator is being used by the text file into something appropriate for +the current system. +.TP +.B \-ll +Auto-convert all files. All files being extracted are considered text, +and will be converted. Don't use this unless you're sure you need it. +.TP +.B \-r +Recurse into subdirectories. When adding, this causes +.I nulib2 +to descend into subdirectories and store all of the files found. When +extracting, testing, or deleting, this causes the files listed to match +against all records whose prefix matches, allowing you to extract, test, +or delete entire subdirectories from the archive. +.TP +.B \-u +Update files. When adding, files in the archive that are older than files +on disk are updated. Files in the archive that are the same age or newer +aren't touched. New files will be added. Works similarly when extracting. +.SH "EXAMPLES" +A simple example: +.IP +\fCnulib2 a foo.shk *\fP +.LP +creates the archive +.I foo.shk +(assuming it doesn't exist) and stores all of the files in the current +directory in it, in compressed form. +.LP +If you wanted to add all the files in the current directory, as well as +all files in all subdirectories, you could use: +.IP +\fCnulib2 ar foo.shk *\fP +.LP +to recursively descend into the directory tree. +.LP +Using the command: +.IP +\fCnulib2 xe foo.shk\fP +.LP +would extract all files from +.I foo.shk, +preserving ProDOS file types. If you then used the command: +.IP +\fCnulib2 aer foo.shk *\fP +.LP +you would add the files, +preserving the file types of anything that was extracted with the +"-e" flag set. +.LP +A handy way to look at text documents is to use: +.IP +\fCnulib2 xeel foo.shk\fP +.LP +to convert end-of-line terminators (e.g. CRLF to LF) as the files are +being extracted. The "-ee" flag adds ".TXT" to all files with a ProDOS +file type of TXT ($04). +.SH "SEE ALSO" +compress(1), +tar(1), +zip(1L), +unzip(1L), +nulib(1L) +.SH BUGS +Probably. +.SH AUTHOR +Copyright (C) 2000 by Andy McFadden. All Rights Reserved. +.\" end of file