From 6bcf388c3eb12838358867aba975c1211258d763 Mon Sep 17 00:00:00 2001 From: Andy McFadden Date: Sat, 26 Dec 2015 16:50:42 -0800 Subject: [PATCH] Update NufxLib with recent 3.1.0 changes --- nufxlib/ChangeLog.txt | 6 ++- nufxlib/FileIO.c | 57 ++++++++++++++++++------ nufxlib/NufxLib.h | 11 +++-- nufxlib/Record.c | 87 ++++++++++++++++++++++--------------- nufxlib/Thread.c | 54 +++++++++++++---------- nufxlib/samples/Exerciser.c | 29 +++++++++++-- nufxlib/samples/ImgConv.c | 3 +- nufxlib/samples/TestBasic.c | 10 +++-- nufxlib/samples/TestTwirl.c | 26 ++++++++++- 9 files changed, 198 insertions(+), 85 deletions(-) diff --git a/nufxlib/ChangeLog.txt b/nufxlib/ChangeLog.txt index 6ec7872..e84eaed 100644 --- a/nufxlib/ChangeLog.txt +++ b/nufxlib/ChangeLog.txt @@ -1,4 +1,8 @@ -2005/01/09 ***** v3.0.0 shipped ***** +2015/12/26 fadden + - Fix handling of entries with missing threads. + - Improve handling of Mac OS X file type attributes. + +2015/01/09 ***** v3.0.0 shipped ***** 2015/01/03 fadden - Mac OS X: replace Carbon FinderInfo calls with BSD xattr. diff --git a/nufxlib/FileIO.c b/nufxlib/FileIO.c index 9d20366..7e87685 100644 --- a/nufxlib/FileIO.c +++ b/nufxlib/FileIO.c @@ -228,6 +228,13 @@ typedef struct NuFileInfo { /* * Determine whether the record has both data and resource forks. + * + * TODO: if we're not using "mask dataless", scanning threads may not + * get the right answer, because GSHK omits theads for zero-length forks. + * We could check pRecord->recStorageType, though we have to be careful + * because that's overloaded for disk images. In any event, the result + * from this method isn't relevant unless we're trying to use forked + * files on the native filesystem. */ static Boolean Nu_IsForkedFile(NuArchive* pArchive, const NuRecord* pRecord) { @@ -320,20 +327,44 @@ static NuError Nu_SetFinderInfo(NuArchive* pArchive, const NuRecord* pRecord, return kNuErrFile; } - /* build type and creator from 8-bit type and 16-bit aux type */ - uint32_t fileType, creator; - fileType = ('p' << 24) | ((pRecord->recFileType & 0xff) << 16) | - (pRecord->recExtraType & 0xffff); - creator = 'pdos'; + uint8_t proType = (uint8_t) pRecord->recFileType; + uint16_t proAux = (uint16_t) pRecord->recExtraType; - fiBuf[0] = fileType >> 24; - fiBuf[1] = fileType >> 16; - fiBuf[2] = fileType >> 8; - fiBuf[3] = fileType; - fiBuf[4] = creator >> 24; - fiBuf[5] = creator >> 16; - fiBuf[6] = creator >> 8; - fiBuf[7] = creator; + /* + * Attempt to use one of the convenience types. If nothing matches, + * use the generic pdos/pXYZ approach. Note that PSYS/PS16 will + * lose the file's aux type. + * + * I'm told this is from page 336 of _Programmer's Reference for + * System 6.0_. + */ + uint8_t* fileTypeBuf = fiBuf; + uint8_t* creatorBuf = fiBuf + 4; + + memcpy(creatorBuf, "pdos", 4); + if (proType == 0x00 && proAux == 0x0000) { + memcpy(fileTypeBuf, "BINA", 4); + } else if (proType == 0x04 && proAux == 0x0000) { + memcpy(fileTypeBuf, "TEXT", 4); + } else if (proType == 0xff) { + memcpy(fileTypeBuf, "PSYS", 4); + } else if (proType == 0xb3 && (proAux & 0xff00) != 0xdb00) { + memcpy(fileTypeBuf, "PS16", 4); + } else if (proType == 0xd7 && proAux == 0x0000) { + memcpy(fileTypeBuf, "MIDI", 4); + } else if (proType == 0xd8 && proAux == 0x0000) { + memcpy(fileTypeBuf, "AIFF", 4); + } else if (proType == 0xd8 && proAux == 0x0001) { + memcpy(fileTypeBuf, "AIFC", 4); + } else if (proType == 0xe0 && proAux == 0x0005) { + memcpy(creatorBuf, "dCpy", 4); + memcpy(fileTypeBuf, "dImg", 4); + } else { + fileTypeBuf[0] = 'p'; + fileTypeBuf[1] = proType; + fileTypeBuf[2] = (uint8_t) (proAux >> 8); + fileTypeBuf[3] = (uint8_t) proAux; + } if (setxattr(pathnameUNI, XATTR_FINDERINFO_NAME, fiBuf, sizeof(fiBuf), 0, 0) != 0) diff --git a/nufxlib/NufxLib.h b/nufxlib/NufxLib.h index 1ec1ada..349917c 100644 --- a/nufxlib/NufxLib.h +++ b/nufxlib/NufxLib.h @@ -33,7 +33,7 @@ extern "C" { * fixes. Unless, of course, your code depends upon that fix. */ #define kNuVersionMajor 3 -#define kNuVersionMinor 0 +#define kNuVersionMinor 1 #define kNuVersionBug 0 @@ -201,13 +201,16 @@ typedef uint32_t NuThreadID; #define kNuThreadClassControl 0x0001 #define kNuThreadClassData 0x0002 #define kNuThreadClassFilename 0x0003 +#define kNuThreadKindDataFork 0x0000 /* when class=data */ +#define kNuThreadKindDiskImage 0x0001 /* when class=data */ +#define kNuThreadKindRsrcFork 0x0002 /* when class=data */ #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 kNuThreadIDDataFork NuMakeThreadID(kNuThreadClassData, kNuThreadKindDataFork) +#define kNuThreadIDDiskImage NuMakeThreadID(kNuThreadClassData, kNuThreadKindDiskImage) +#define kNuThreadIDRsrcFork NuMakeThreadID(kNuThreadClassData, kNuThreadKindRsrcFork) #define kNuThreadIDFilename NuMakeThreadID(kNuThreadClassFilename, 0x0000) #define kNuThreadIDWildcard NuMakeThreadID(0xffff, 0xffff) diff --git a/nufxlib/Record.c b/nufxlib/Record.c index d77899c..858cf4d 100644 --- a/nufxlib/Record.c +++ b/nufxlib/Record.c @@ -1524,7 +1524,7 @@ NuError Nu_StreamExtract(NuArchive* pArchive) { NuError err = kNuErrNone; NuRecord tmpRecord; - Boolean hasInterestingThread; + Boolean needFakeData, needFakeRsrc; uint32_t count; long idx; @@ -1573,7 +1573,8 @@ NuError Nu_StreamExtract(NuArchive* pArchive) /*Nu_DebugDumpRecord(&tmpRecord); printf("\n");*/ - hasInterestingThread = false; + needFakeData = true; + needFakeRsrc = (tmpRecord.recStorageType == kNuStorageExtended); /* extract all relevant (remaining) threads */ pArchive->lastFileCreatedUNI = NULL; @@ -1581,7 +1582,11 @@ NuError Nu_StreamExtract(NuArchive* pArchive) const NuThread* pThread = Nu_GetThread(&tmpRecord, idx); if (pThread->thThreadClass == kNuThreadClassData) { - hasInterestingThread = true; + if (pThread->thThreadKind == kNuThreadKindDataFork) { + needFakeData = false; + } else if (pThread->thThreadKind == kNuThreadKindRsrcFork) { + needFakeRsrc = false; + } err = Nu_ExtractThreadBulk(pArchive, &tmpRecord, pThread); if (err == kNuErrSkipped) { err = Nu_SkipThread(pArchive, &tmpRecord, pThread); @@ -1595,7 +1600,8 @@ NuError Nu_StreamExtract(NuArchive* pArchive) if (NuGetThreadID(pThread) != kNuThreadIDComment && NuGetThreadID(pThread) != kNuThreadIDFilename) { - hasInterestingThread = true; + /* unknown stuff in record, skip thread fakery */ + needFakeData = needFakeRsrc = false; } err = Nu_SkipThread(pArchive, &tmpRecord, pThread); BailError(err); @@ -1603,19 +1609,19 @@ NuError Nu_StreamExtract(NuArchive* pArchive) } /* - * 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 (and possibly a resource fork). - * - * See notes in previous instance, above. + * As in Nu_ExtractRecordByPtr, we need to synthesize empty forks for + * cases where GSHK omitted the data thread entirely. */ - if (/*pArchive->valMaskDataless &&*/ !hasInterestingThread) { - err = Nu_FakeZeroExtract(pArchive, &tmpRecord, 0x0000); + Assert(!pArchive->valMaskDataless || (!needFakeData && !needFakeRsrc)); + if (needFakeData) { + err = Nu_FakeZeroExtract(pArchive, &tmpRecord, + kNuThreadKindDataFork); + BailError(err); + } + if (needFakeRsrc) { + err = Nu_FakeZeroExtract(pArchive, &tmpRecord, + kNuThreadKindRsrcFork); BailError(err); - if (tmpRecord.recStorageType == kNuStorageExtended) { - err = Nu_FakeZeroExtract(pArchive, &tmpRecord, 0x0002); - BailError(err); - } } /* dispose of the entry */ @@ -1698,20 +1704,26 @@ bail: static NuError Nu_ExtractRecordByPtr(NuArchive* pArchive, NuRecord* pRecord) { NuError err = kNuErrNone; - Boolean hasInterestingThread; + Boolean needFakeData, needFakeRsrc; uint32_t idx; + needFakeData = true; + needFakeRsrc = (pRecord->recStorageType == kNuStorageExtended); + Assert(!Nu_IsStreaming(pArchive)); /* we don't skip things we don't read */ Assert(pRecord != NULL); /* extract all relevant threads */ - hasInterestingThread = false; pArchive->lastFileCreatedUNI = NULL; for (idx = 0; idx < pRecord->recTotalThreads; idx++) { const NuThread* pThread = Nu_GetThread(pRecord, idx); if (pThread->thThreadClass == kNuThreadClassData) { - hasInterestingThread = true; + if (pThread->thThreadKind == kNuThreadKindDataFork) { + needFakeData = false; + } else if (pThread->thThreadKind == kNuThreadKindRsrcFork) { + needFakeRsrc = false; + } err = Nu_ExtractThreadBulk(pArchive, pRecord, pThread); if (err == kNuErrSkipped) { err = Nu_SkipThread(pArchive, pRecord, pThread); @@ -1722,7 +1734,13 @@ static NuError Nu_ExtractRecordByPtr(NuArchive* pArchive, NuRecord* pRecord) if (NuGetThreadID(pThread) != kNuThreadIDComment && NuGetThreadID(pThread) != kNuThreadIDFilename) { - hasInterestingThread = true; + /* + * This record has a thread we don't recognize. Disable + * the thread fakery to avoid doing anything weird -- we + * should only need to create zero-length files for + * simple file records. + */ + needFakeData = needFakeRsrc = false; } DBUG(("IGNORING 0x%08lx from '%s'\n", NuMakeThreadID(pThread->thThreadClass, pThread->thThreadKind), @@ -1731,28 +1749,27 @@ static NuError Nu_ExtractRecordByPtr(NuArchive* pArchive, NuRecord* pRecord) } /* - * 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 file. + * GSHK creates empty threads for zero-length forks. It doesn't always + * handle them correctly when extracting, so it appears this behavior + * may not be intentional. * - * (GSHK handles empty data and resource forks by not storing a - * thread at all. It doesn't correctly deal with them when extracting - * though, so it appears this behavior wasn't entirely expected.) + * We need to create an empty file for whichever forks are missing. + * Could be the data fork, resource fork, or both. The only way to + * know what's expected is to examine the file's storage type. * - * If it's a forked file, we also need to create an empty rsrc file. - * - * If valMaskDataless is enabled, this won't fire, because we "forge" - * appropriate threads. + * If valMaskDataless is enabled, this won't fire, because we will have + * "forged" the appropriate threads. * * Note there's another one of these below, in Nu_StreamExtract. */ - if (/*pArchive->valMaskDataless &&*/ !hasInterestingThread) { - err = Nu_FakeZeroExtract(pArchive, pRecord, 0x0000 /*data*/); + Assert(!pArchive->valMaskDataless || (!needFakeData && !needFakeRsrc)); + if (needFakeData) { + err = Nu_FakeZeroExtract(pArchive, pRecord, kNuThreadKindDataFork); + BailError(err); + } + if (needFakeRsrc) { + err = Nu_FakeZeroExtract(pArchive, pRecord, kNuThreadKindRsrcFork); BailError(err); - if (pRecord->recStorageType == kNuStorageExtended) { - err = Nu_FakeZeroExtract(pArchive, pRecord, 0x0002 /*rsrc*/); - BailError(err); - } } bail: diff --git a/nufxlib/Thread.c b/nufxlib/Thread.c index d8e2683..0ad4ba0 100644 --- a/nufxlib/Thread.c +++ b/nufxlib/Thread.c @@ -193,7 +193,10 @@ NuError Nu_ReadThreadHeaders(NuArchive* pArchive, NuRecord* pRecord, NuError err = kNuErrNone; NuThread* pThread; long count; - Boolean hasData = false; + Boolean needFakeData, needFakeRsrc; + + needFakeData = true; + needFakeRsrc = (pRecord->recStorageType == kNuStorageExtended); Assert(pArchive != NULL); Assert(pRecord != NULL); @@ -215,8 +218,13 @@ NuError Nu_ReadThreadHeaders(NuArchive* pArchive, NuRecord* pRecord, err = Nu_ReadThreadHeader(pArchive, pThread, pCrc); BailError(err); - if (pThread->thThreadClass == kNuThreadClassData) - hasData = true; + if (pThread->thThreadClass == kNuThreadClassData) { + if (pThread->thThreadKind == kNuThreadKindDataFork) { + needFakeData = false; + } else if (pThread->thThreadKind == kNuThreadKindRsrcFork) { + needFakeRsrc = false; + } + } /* * Some versions of ShrinkIt write an invalid thThreadEOF for disks, @@ -258,13 +266,14 @@ NuError Nu_ReadThreadHeaders(NuArchive* pArchive, NuRecord* pRecord, * If "mask threadless" is set, create "fake" threads with empty * data and resource forks as needed. */ - if (!hasData && pArchive->valMaskDataless) { - Boolean needRsrc = (pRecord->recStorageType == kNuStorageExtended); + if ((needFakeData || needFakeRsrc) && pArchive->valMaskDataless) { int firstNewThread = pRecord->recTotalThreads; - pRecord->recTotalThreads++; - pRecord->fakeThreads++; - if (needRsrc) { + if (needFakeData) { + pRecord->recTotalThreads++; + pRecord->fakeThreads++; + } + if (needFakeRsrc) { pRecord->recTotalThreads++; pRecord->fakeThreads++; } @@ -275,22 +284,23 @@ NuError Nu_ReadThreadHeaders(NuArchive* pArchive, NuRecord* pRecord, pThread = pRecord->pThreads + firstNewThread; - pThread->thThreadClass = kNuThreadClassData; - pThread->thThreadFormat = kNuThreadFormatUncompressed; - pThread->thThreadKind = 0x0000; /* data fork */ - pThread->thThreadCRC = kNuInitialThreadCRC; - pThread->thThreadEOF = 0; - pThread->thCompThreadEOF = 0; - pThread->threadIdx = Nu_GetNextThreadIdx(pArchive); - pThread->actualThreadEOF = 0; - pThread->fileOffset = -99999999; - pThread->used = false; - - if (needRsrc) { - pThread++; + if (needFakeData) { pThread->thThreadClass = kNuThreadClassData; pThread->thThreadFormat = kNuThreadFormatUncompressed; - pThread->thThreadKind = 0x0002; /* rsrc fork */ + pThread->thThreadKind = kNuThreadKindDataFork; + pThread->thThreadCRC = kNuInitialThreadCRC; + pThread->thThreadEOF = 0; + pThread->thCompThreadEOF = 0; + pThread->threadIdx = Nu_GetNextThreadIdx(pArchive); + pThread->actualThreadEOF = 0; + pThread->fileOffset = -99999999; + pThread->used = false; + pThread++; + } + if (needFakeRsrc) { + pThread->thThreadClass = kNuThreadClassData; + pThread->thThreadFormat = kNuThreadFormatUncompressed; + pThread->thThreadKind = kNuThreadKindRsrcFork; pThread->thThreadCRC = kNuInitialThreadCRC; pThread->thThreadEOF = 0; pThread->thCompThreadEOF = 0; diff --git a/nufxlib/samples/Exerciser.c b/nufxlib/samples/Exerciser.c index 18b72dc..15b1f50 100644 --- a/nufxlib/samples/Exerciser.c +++ b/nufxlib/samples/Exerciser.c @@ -24,6 +24,15 @@ static const char kFssep = PATH_SEP; #define kTempFile "exer-temp" +#ifndef HAVE_STRCASECMP +static int strcasecmp(const char *str1, const char *str2) +{ + while (*str1 && *str2 && toupper(*str1) == toupper(*str2)) + str1++, str2++; + return (toupper(*str1) - toupper(*str2)); +} +#endif + /* * =========================================================================== @@ -364,13 +373,25 @@ static NuError AbortFunc(ExerciserState* pState, int argc, char** argv) static NuError AddFileFunc(ExerciserState* pState, int argc, char** argv) { NuFileDetails nuFileDetails; + int fromRsrc = false; (void) pState, (void) argc, (void) argv; /* shut up, gcc */ assert(ExerciserState_GetNuArchive(pState) != NULL); - assert(argc == 2); + assert(argc == 3); + + if (strcasecmp(argv[2], "true") == 0) { + fromRsrc = true; + } else if (strcasecmp(argv[2], "false") != 0) { + fprintf(stderr, "WARNING: fromRsrc should be 'true' or 'false'\n"); + /* ignore */ + } memset(&nuFileDetails, 0, sizeof(nuFileDetails)); - nuFileDetails.threadID = kNuThreadIDDataFork; + if (fromRsrc) { + nuFileDetails.threadID = kNuThreadIDRsrcFork; + } else { + nuFileDetails.threadID = kNuThreadIDDataFork; + } nuFileDetails.storageNameMOR = argv[1]; nuFileDetails.fileSysID = kNuFileSysUnknown; nuFileDetails.fileSysInfo = (short) kFssep; @@ -378,7 +399,7 @@ static NuError AddFileFunc(ExerciserState* pState, int argc, char** argv) /* fileType, extraType, storageType, dates */ return NuAddFile(ExerciserState_GetNuArchive(pState), argv[1], - &nuFileDetails, false, NULL); + &nuFileDetails, fromRsrc, NULL); } /* @@ -1039,7 +1060,7 @@ static const struct { { "ab", AbortFunc, 0, "", kFlagArchiveReq, "Abort current changes" }, - { "af", AddFileFunc, 1, "filename", kFlagArchiveReq, + { "af", AddFileFunc, 2, "filename fromRsrc", kFlagArchiveReq, "Add file" }, { "ar", AddRecordFunc, 1, "storageName", kFlagArchiveReq, "Add record" }, diff --git a/nufxlib/samples/ImgConv.c b/nufxlib/samples/ImgConv.c index 9eae3a7..d9624b2 100644 --- a/nufxlib/samples/ImgConv.c +++ b/nufxlib/samples/ImgConv.c @@ -16,8 +16,7 @@ #include "Common.h" #ifndef HAVE_STRCASECMP -static int -strcasecmp(const char *str1, const char *str2) +static int strcasecmp(const char *str1, const char *str2) { while (*str1 && *str2 && toupper(*str1) == toupper(*str2)) str1++, str2++; diff --git a/nufxlib/samples/TestBasic.c b/nufxlib/samples/TestBasic.c index 2559608..19fea41 100644 --- a/nufxlib/samples/TestBasic.c +++ b/nufxlib/samples/TestBasic.c @@ -298,7 +298,8 @@ int Test_AddStuff(NuArchive* pArchive) /* - * Create an empty file with a rather non-empty name. + * Create an empty file with a rather non-empty name. Add it as + * a resource fork. */ printf("... add 'long' record\n"); err = NuCreateDataSourceForBuffer(kNuThreadFormatUncompressed, @@ -454,7 +455,8 @@ failed: /* - * Selection callback filter for "test". This gets called once per record. + * Selection callback filter for "test". This gets called once per record, + * twice per record for forked files. */ NuResult VerifySelectionCallback(NuArchive* pArchive, void* vpProposal) { @@ -523,7 +525,9 @@ int Test_Verify(NuArchive* pArchive) goto failed; } - if (count != kNumEntries) { + /* the count will be one higher than the number of records because + the last entry is forked, and each fork is tested separately */ + if (count != kNumEntries + 1) { fprintf(stderr, "ERROR: verified %ld when expecting %d\n", count, kNumEntries); goto failed; diff --git a/nufxlib/samples/TestTwirl.c b/nufxlib/samples/TestTwirl.c index 8e8c34e..1712172 100644 --- a/nufxlib/samples/TestTwirl.c +++ b/nufxlib/samples/TestTwirl.c @@ -184,6 +184,7 @@ CRCList* GatherCRCs(NuArchive* pArchive) goto bail; } + int rsrcCrcIdx = -1; for (i = 0; i < (int)NuRecordGetNumThreads(pRecord); i++) { pThread = NuGetThread(pRecord, i); if (pThread->thThreadClass == kNuThreadClassData) { @@ -194,7 +195,30 @@ CRCList* GatherCRCs(NuArchive* pArchive) goto bail; } - pEntries[crcIdx++] = pThread->thThreadCRC; + /* + * Ensure that the data fork CRC comes first. Otherwise + * we can fail if it gets rearranged. This is only a + * problem for GSHK-created archives that don't have + * threads for every fork, so "mask dataless" is create + * fake entries. + * + * The correct way to do this is to store a tuple + * { thread-kind, crc }, but that's more work. + */ + if (pThread->thThreadKind == kNuThreadKindRsrcFork) { + rsrcCrcIdx = crcIdx; + } + + if (pThread->thThreadKind == kNuThreadKindDataFork && + rsrcCrcIdx != -1) + { + /* this is the data fork, we've already seen the + resource fork; swap entries */ + pEntries[crcIdx++] = pEntries[rsrcCrcIdx]; + pEntries[rsrcCrcIdx] = pThread->thThreadCRC; + } else { + pEntries[crcIdx++] = pThread->thThreadCRC; + } } } }