Fix handling of entries with missing threads

When GSHK adds files to an archive, it doesn't create threads for
zero-length data and resource forks.  NufxLib had a workaround
for this, but it wasn't handling all possible cases.  We now
fully handle "Miranda threads" (if you cannot afford a thread,
one will be provided for you).

This broke test-basic, because a callback gets called one extra
time now due to the additional thread.  It also broke test-twirl,
which uses "mask dataless" and is sensitive to the order in which
threads appear.  (test-twirl actually works just fine, but the
CRC check is too simple-minded, and is arguably incorrect.)

Since this can apparently break things, I'm making this a minor
version bump, to 3.1.0-a1.

I also tweaked the NuLib2 file listing to test for the extended
file storage type, rather than simply scanning for data threads.
Forked files are now listed as such, even when they're missing
the actual resource fork data thread.
This commit is contained in:
Andy McFadden 2015-12-26 11:44:47 -08:00
parent bc96aa420b
commit f37b387cc6
10 changed files with 133 additions and 72 deletions

View File

@ -1,4 +1,7 @@
2005/01/09 ***** v3.0.0 shipped *****
2015/12/26 fadden
- Fix handling of entries with missing threads.
2015/01/09 ***** v3.0.0 shipped *****
2015/01/03 fadden
- Mac OS X: replace Carbon FinderInfo calls with BSD xattr.

View File

@ -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)
{

View File

@ -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)

View File

@ -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:

View File

@ -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;

View File

@ -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;

View File

@ -1,4 +1,7 @@
2005/01/09 ***** v3.0.0 shipped *****
2015/12/26 fadden
- Fix handling of entries with missing threads.
2015/01/09 ***** v3.0.0 shipped *****
2015/01/03 fadden
- Mac OS X: get file/aux type from FinderInfo when adding files.

View File

@ -135,10 +135,14 @@ bail:
* a disk image thread, but it's a fair bet ShrinkIt would ignore one
* or the other.
*
* NOTE: we don't currently work around the GSHK zero-length file bug.
* Such records, which have a filename thread but no data threads at all,
* will be categorized as "unknown". We could detect the situation and
* correct it, but we might as well flag it in a user-visible way.
* NOTE: GSHK likes to omit the data threads for zero-length data and
* resource forks. That screws up analysis by scanning for threads. We
* can work around missing resource forks by simply checking the record's
* storage type. We could be clever and detect records that have no
* data-class threads at all, and no additional threads other than a
* comment and filename, but this is just for display. ShrinkIt doesn't
* handle these records correctly in all cases, so flagging them in a
* user-visible way seems reasonable.
*/
static NuError AnalyzeRecord(const NuRecord* pRecord,
enum RecordKind* pRecordKind, uint16_t* pFormat, uint32_t* pTotalLen,
@ -177,6 +181,16 @@ static NuError AnalyzeRecord(const NuRecord* pRecord,
}
}
/*
* Fix up the case where we have a forked file, but GSHK decided not
* to include a resource fork in the record.
*/
if (pRecord->recStorageType == kNuStorageExtended &&
*pRecordKind != kRecordKindForkedFile)
{
*pRecordKind = kRecordKindForkedFile;
}
return kNuErrNone;
}

View File

@ -143,7 +143,7 @@ static void Usage(const NulibState* pState)
printf("\nNuLib2 v%s, linked with NufxLib v%d.%d.%d [%s]\n",
NState_GetProgramVersion(pState),
majorVersion, minorVersion, bugVersion, nufxLibFlags);
printf("Copyright (C) 2000-2014, Andy McFadden. All Rights Reserved.\n");
printf("Copyright (C) 2000-2015, Andy McFadden. All Rights Reserved.\n");
printf("This software is distributed under terms of the BSD License.\n");
printf("Visit http://www.nulib.com/ for source code and documentation.\n\n");
printf("Usage: %s -command[modifiers] archive [filename-list]\n\n",

View File

@ -9,7 +9,7 @@
#include "NuLib2.h"
static const char* gProgramVersion = "3.0.0";
static const char* gProgramVersion = "3.1.0-a1";
/*