mirror of
https://github.com/fadden/ciderpress.git
synced 2024-12-22 05:29:43 +00:00
84706d7ea4
This integrates the latest NufxLib sources, and updates CiderPress to work with the API changes.
1369 lines
44 KiB
C
1369 lines
44 KiB
C
/*
|
|
* NuFX archive manipulation library
|
|
* Copyright (C) 2000-2007 by Andy McFadden, All Rights Reserved.
|
|
* This is free software; you can redistribute it and/or modify it under the
|
|
* terms of the BSD License, see the file COPYING-LIB.
|
|
*
|
|
* Thread-level operations.
|
|
*/
|
|
#include "NufxLibPriv.h"
|
|
|
|
|
|
/*
|
|
* ===========================================================================
|
|
* Utils
|
|
* ===========================================================================
|
|
*/
|
|
|
|
/*
|
|
* Returns thread N, or NULL if the index is invalid.
|
|
*/
|
|
NuThread* Nu_GetThread(const NuRecord* pRecord, int idx)
|
|
{
|
|
if (idx >= (int)pRecord->recTotalThreads)
|
|
return NULL;
|
|
else
|
|
return &pRecord->pThreads[idx];
|
|
}
|
|
|
|
/*
|
|
* 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)
|
|
{
|
|
uint8_t* cp;
|
|
|
|
for (cp = (uint8_t*)str; *cp != '\0'; cp++)
|
|
if (!(*cp & 0x80))
|
|
return;
|
|
|
|
for (cp = (uint8_t*)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(uint16_t 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 != NULL);
|
|
|
|
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 != NULL);
|
|
|
|
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 != NULL);
|
|
Assert(pSrcThread != NULL);
|
|
|
|
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,
|
|
uint16_t* pCrc)
|
|
{
|
|
FILE* fp;
|
|
|
|
Assert(pArchive != NULL);
|
|
Assert(pThread != NULL);
|
|
Assert(pCrc != NULL);
|
|
|
|
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 */
|
|
pThread->used = 0xcfcf; /* init to invalid value */
|
|
|
|
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,
|
|
uint16_t* pCrc)
|
|
{
|
|
NuError err = kNuErrNone;
|
|
NuThread* pThread;
|
|
long count;
|
|
Boolean hasData = false;
|
|
|
|
Assert(pArchive != NULL);
|
|
Assert(pRecord != NULL);
|
|
Assert(pCrc != NULL);
|
|
|
|
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);
|
|
|
|
if (pThread->thThreadClass == kNuThreadClassData)
|
|
hasData = true;
|
|
|
|
/*
|
|
* 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 if (pRecord->recStorageType == 256 &&
|
|
pRecord->recExtraType == 280 &&
|
|
pRecord->recFileSysID == kNuFileSysDOS33)
|
|
{
|
|
/*
|
|
* Fix for less-common ShrinkIt problem: looks like an old
|
|
* version of GS/ShrinkIt used 256 as the block size when
|
|
* compressing DOS 3.3 images from 5.25" disks. If that
|
|
* appears to be the case here, crank up the block size.
|
|
*/
|
|
DBUG(("--- no such thing as a 70K disk image!\n"));
|
|
pThread->actualThreadEOF = pRecord->recExtraType * 512;
|
|
|
|
} else {
|
|
pThread->actualThreadEOF =
|
|
pRecord->recExtraType * pRecord->recStorageType;
|
|
}
|
|
} else {
|
|
pThread->actualThreadEOF = pThread->thThreadEOF;
|
|
}
|
|
|
|
pThread->used = false;
|
|
pThread++;
|
|
}
|
|
|
|
/*
|
|
* 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);
|
|
int firstNewThread = pRecord->recTotalThreads;
|
|
|
|
pRecord->recTotalThreads++;
|
|
pRecord->fakeThreads++;
|
|
if (needRsrc) {
|
|
pRecord->recTotalThreads++;
|
|
pRecord->fakeThreads++;
|
|
}
|
|
|
|
pRecord->pThreads = Nu_Realloc(pArchive, pRecord->pThreads,
|
|
pRecord->recTotalThreads * sizeof(NuThread));
|
|
BailAlloc(pRecord->pThreads);
|
|
|
|
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++;
|
|
pThread->thThreadClass = kNuThreadClassData;
|
|
pThread->thThreadFormat = kNuThreadFormatUncompressed;
|
|
pThread->thThreadKind = 0x0002; /* rsrc fork */
|
|
pThread->thThreadCRC = kNuInitialThreadCRC;
|
|
pThread->thThreadEOF = 0;
|
|
pThread->thCompThreadEOF = 0;
|
|
pThread->threadIdx = Nu_GetNextThreadIdx(pArchive);
|
|
pThread->actualThreadEOF = 0;
|
|
pThread->fileOffset = -99999999;
|
|
pThread->used = false;
|
|
}
|
|
}
|
|
|
|
bail:
|
|
return err;
|
|
}
|
|
|
|
|
|
/*
|
|
* Write a single thread header to the archive.
|
|
*/
|
|
static NuError Nu_WriteThreadHeader(NuArchive* pArchive,
|
|
const NuThread* pThread, FILE* fp, uint16_t* pCrc)
|
|
{
|
|
Assert(pArchive != NULL);
|
|
Assert(pThread != NULL);
|
|
Assert(fp != NULL);
|
|
Assert(pCrc != NULL);
|
|
|
|
Nu_WriteTwoC(pArchive, fp, pThread->thThreadClass, pCrc);
|
|
Nu_WriteTwoC(pArchive, fp, (uint16_t)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.
|
|
*
|
|
* Note this doesn't care whether a thread was "fake" or not. In
|
|
* effect, we promote all threads to "real" status. We update the
|
|
* "fake" count in pRecord accordingly.
|
|
*/
|
|
NuError Nu_WriteThreadHeaders(NuArchive* pArchive, NuRecord* pRecord, FILE* fp,
|
|
uint16_t* pCrc)
|
|
{
|
|
NuError err = kNuErrNone;
|
|
NuThread* pThread;
|
|
int idx;
|
|
|
|
for (idx = 0; idx < (int)pRecord->recTotalThreads; idx++) {
|
|
pThread = Nu_GetThread(pRecord, idx);
|
|
Assert(pThread != NULL);
|
|
|
|
err = Nu_WriteThreadHeader(pArchive, pThread, fp, pCrc);
|
|
BailError(err);
|
|
}
|
|
|
|
if (pRecord->fakeThreads != 0) {
|
|
DBUG(("+++ promoting %ld fake threads to real\n",pRecord->fakeThreads));
|
|
pRecord->fakeThreads = 0;
|
|
}
|
|
|
|
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 != NULL);
|
|
Assert(pRecord != NULL);
|
|
|
|
/*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 != NULL);
|
|
Assert(pRecord != NULL);
|
|
|
|
fp = pArchive->archiveFp;
|
|
|
|
Assert(numThreads <= (long)pRecord->recTotalThreads);
|
|
|
|
pThread = pRecord->pThreads;
|
|
while (numThreads--) {
|
|
if (pRecord->threadFilenameMOR == NULL &&
|
|
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 (%u)",
|
|
pThread->thCompThreadEOF);
|
|
goto bail;
|
|
}
|
|
pRecord->threadFilenameMOR = Nu_Malloc(pArchive,
|
|
pThread->thCompThreadEOF +1);
|
|
BailAlloc(pRecord->threadFilenameMOR);
|
|
|
|
/* note there is no CRC on a filename thread */
|
|
(void) Nu_ReadBytes(pArchive, fp, pRecord->threadFilenameMOR,
|
|
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->threadFilenameMOR[pThread->thThreadEOF] = '\0';
|
|
|
|
Nu_StripHiIfAllSet(pRecord->threadFilenameMOR);
|
|
|
|
/* prefer this one over the record one, but only one should exist */
|
|
if (pRecord->filenameMOR != NULL) {
|
|
DBUG(("--- HEY: got record filename and thread filename\n"));
|
|
}
|
|
pRecord->filenameMOR = pRecord->threadFilenameMOR;
|
|
|
|
} 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->filenameMOR == NULL) {
|
|
DBUG(("+++ no filename found, using default record name\n"));
|
|
pRecord->filenameMOR = 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 = NULL;
|
|
|
|
/* 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;
|
|
UNICHAR* newPathStorageUNI = NULL;
|
|
UNICHAR* recFilenameStorageUNI = NULL;
|
|
const UNICHAR* newPathnameUNI;
|
|
NuResult result;
|
|
uint8_t newFssep;
|
|
Boolean doFreeSink = false;
|
|
|
|
Assert(pRecord != NULL);
|
|
Assert(pThread != NULL);
|
|
Assert(pDataSink != NULL);
|
|
|
|
memset(&progressData, 0, sizeof(progressData));
|
|
pProgressData = NULL;
|
|
|
|
/*
|
|
* 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 != NULL) {
|
|
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;
|
|
}
|
|
}
|
|
|
|
newPathnameUNI = NULL;
|
|
newFssep = 0;
|
|
|
|
recFilenameStorageUNI = Nu_CopyMORToUNI(pRecord->filenameMOR);
|
|
|
|
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.
|
|
*/
|
|
newPathnameUNI = 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 != NULL) {
|
|
pathProposal.pathnameUNI = recFilenameStorageUNI;
|
|
pathProposal.filenameSeparator =
|
|
NuGetSepFromSysInfo(pRecord->recFileSysInfo);
|
|
pathProposal.pRecord = pRecord;
|
|
pathProposal.pThread = pThread;
|
|
pathProposal.newPathnameUNI = NULL;
|
|
pathProposal.newFilenameSeparator = '\0';
|
|
/*pathProposal.newStorage = (NuThreadID)-1;*/
|
|
pathProposal.newDataSink = NULL;
|
|
|
|
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.newPathnameUNI != NULL) {
|
|
Nu_Free(pArchive, newPathStorageUNI);
|
|
newPathStorageUNI = strdup(pathProposal.newPathnameUNI);
|
|
newPathnameUNI = newPathStorageUNI;
|
|
} else {
|
|
newPathnameUNI = NULL;
|
|
}
|
|
if (pathProposal.newFilenameSeparator != '\0')
|
|
newFssep = pathProposal.newFilenameSeparator;
|
|
|
|
/* if they want to send this somewhere else, let them */
|
|
if (pathProposal.newDataSink != NULL)
|
|
pDataSink = pathProposal.newDataSink;
|
|
}
|
|
|
|
/* at least one of these must be set */
|
|
Assert(!(newPathnameUNI == NULL && pathProposal.newDataSink == NULL));
|
|
}
|
|
|
|
/*
|
|
* Prepare the progress data if this is a data thread.
|
|
*/
|
|
if (newPathnameUNI == NULL) {
|
|
/* using a data sink; get the pathname out of the record */
|
|
newPathnameUNI = recFilenameStorageUNI;
|
|
newFssep = NuGetSepFromSysInfo(pRecord->recFileSysInfo);
|
|
}
|
|
if (pThread->thThreadClass == kNuThreadClassData) {
|
|
pProgressData = &progressData;
|
|
err = Nu_ProgressDataInit_Expand(pArchive, pProgressData, pRecord,
|
|
newPathnameUNI, newFssep, recFilenameStorageUNI,
|
|
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 = NULL;
|
|
|
|
err = Nu_OpenOutputFile(pArchive, pRecord, pThread, newPathnameUNI,
|
|
newFssep, &fileFp);
|
|
if (err == kNuErrRename) {
|
|
/* they want to rename; the OutputPathname callback handles this */
|
|
Nu_Free(pArchive, newPathStorageUNI);
|
|
newPathStorageUNI = NULL;
|
|
/* reset these just to be careful */
|
|
newPathnameUNI = NULL;
|
|
fileFp = NULL;
|
|
goto retry_name;
|
|
} else if (err != kNuErrNone) {
|
|
goto bail;
|
|
}
|
|
|
|
Assert(fileFp != NULL);
|
|
(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), newPathnameUNI);
|
|
Nu_DataSinkFile_SetFP(pDataSink, NULL);
|
|
BailError(err);
|
|
}
|
|
|
|
bail:
|
|
if (err != kNuErrNone && pProgressData != NULL) {
|
|
/* send a final progress message, indicating failure */
|
|
if (err == kNuErrSkipped)
|
|
pProgressData->state = kNuProgressSkipped;
|
|
else if (err == kNuErrAborted)
|
|
pProgressData->state = kNuProgressAborted;
|
|
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, newPathStorageUNI);
|
|
Nu_Free(pArchive, recFilenameStorageUNI);
|
|
|
|
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 = NULL;
|
|
UNICHAR* recFilenameStorageUNI = NULL;
|
|
NuValue eolConv;
|
|
|
|
/*
|
|
* 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.)
|
|
*
|
|
* One exception: we turn EOL conversion off for disk image threads.
|
|
* It's *very* unlikely this would be desirable, and could be a problem
|
|
* if the user is extracting a collection of disks and files.
|
|
*/
|
|
eolConv = pArchive->valConvertExtractedEOL;
|
|
if (NuGetThreadID(pThread) == kNuThreadIDDiskImage)
|
|
eolConv = kNuConvertOff;
|
|
recFilenameStorageUNI = Nu_CopyMORToUNI(pRecord->filenameMOR);
|
|
err = Nu_DataSinkFile_New(true, eolConv, recFilenameStorageUNI,
|
|
NuGetSepFromSysInfo(pRecord->recFileSysInfo), &pDataSink);
|
|
BailError(err);
|
|
|
|
err = Nu_ExtractThreadCommon(pArchive, pRecord, pThread, pDataSink);
|
|
BailError(err);
|
|
|
|
bail:
|
|
if (pDataSink != NULL) {
|
|
NuError err2 = Nu_DataSinkFree(pDataSink);
|
|
if (err == kNuErrNone)
|
|
err = err2;
|
|
}
|
|
Nu_Free(pArchive, recFilenameStorageUNI);
|
|
|
|
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 == NULL)
|
|
return kNuErrInvalidArg;
|
|
err = Nu_GetTOCIfNeeded(pArchive);
|
|
BailError(err);
|
|
|
|
/* find the correct record and thread by index */
|
|
err = Nu_RecordSet_FindByThreadIdx(&pArchive->origRecordSet, threadIdx,
|
|
&pRecord, &pThread);
|
|
BailError(err);
|
|
Assert(pRecord != NULL);
|
|
|
|
/* 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 != NULL);
|
|
|
|
if (NuGetThreadID(pThread) == threadID) {
|
|
/* found a match, see if it has been deleted */
|
|
pThreadMod = Nu_ThreadMod_FindByThreadIdx(pRecord,
|
|
pThread->threadIdx);
|
|
if (pThreadMod != NULL &&
|
|
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 != NULL) {
|
|
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 != NULL);
|
|
|
|
if (pThread->thThreadClass == threadClass) {
|
|
/* found a match, see if it has been deleted */
|
|
pThreadMod = Nu_ThreadMod_FindByThreadIdx(pRecord,
|
|
pThread->threadIdx);
|
|
if (pThreadMod != NULL &&
|
|
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 != NULL) {
|
|
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 = NULL; /* 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 == NULL) {
|
|
err = Nu_RecordSet_Clone(pArchive, &pArchive->copyRecordSet,
|
|
&pArchive->origRecordSet);
|
|
BailError(err);
|
|
err = Nu_RecordSet_FindByThreadIdx(&pArchive->copyRecordSet, threadIdx,
|
|
ppFoundRecord, ppFoundThread);
|
|
Assert(err == kNuErrNone && *ppFoundThread != NULL); /* 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", and "pDataSource" will be owned by NufxLib.
|
|
*/
|
|
NuError Nu_AddThread(NuArchive* pArchive, NuRecordIdx recIdx,
|
|
NuThreadID threadID, NuDataSource* pDataSource, NuThreadIdx* pThreadIdx)
|
|
{
|
|
NuError err;
|
|
NuRecord* pRecord;
|
|
NuThreadMod* pThreadMod = NULL;
|
|
NuThreadFormat threadFormat;
|
|
|
|
/* okay for pThreadIdx to be NULL */
|
|
if (recIdx == 0 || pDataSource == NULL)
|
|
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 != NULL);
|
|
|
|
/*
|
|
* 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 (which makes a copy of the data source) */
|
|
err = Nu_ThreadModAdd_New(pArchive, threadID, threadFormat, pDataSource,
|
|
&pThreadMod);
|
|
BailError(err);
|
|
Assert(pThreadMod != NULL);
|
|
|
|
/* add the thread mod to the record */
|
|
Nu_RecordAddThreadMod(pRecord, pThreadMod);
|
|
if (pThreadIdx != NULL)
|
|
*pThreadIdx = pThreadMod->entry.add.threadIdx;
|
|
pThreadMod = NULL; /* 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 != NULL)
|
|
Nu_ThreadModFree(pArchive, pThreadMod);
|
|
if (err == kNuErrNone && pDataSource != NULL) {
|
|
/* on success, we have ownership of the data source. ThreadMod
|
|
made its own copy, so get rid of this one */
|
|
Nu_DataSourceFree(pDataSource);
|
|
}
|
|
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, int32_t* pMaxLen)
|
|
{
|
|
NuError err;
|
|
NuThreadMod* pThreadMod = NULL;
|
|
NuRecord* pFoundRecord;
|
|
NuThread* pFoundThread;
|
|
|
|
if (pDataSource == NULL) {
|
|
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 != NULL)
|
|
*pMaxLen = pFoundThread->thCompThreadEOF;
|
|
|
|
/*
|
|
* Check to see if somebody is trying to delete this, or has already
|
|
* updated it.
|
|
*/
|
|
if (Nu_ThreadMod_FindByThreadIdx(pFoundRecord, threadIdx) != NULL) {
|
|
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 %u bytes into %u",
|
|
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 (%u 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 != NULL);
|
|
|
|
/* 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 = NULL;
|
|
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) != NULL) {
|
|
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 = NULL; /* successful, don't free */
|
|
|
|
bail:
|
|
Nu_ThreadModFree(pArchive, pThreadMod);
|
|
return err;
|
|
}
|
|
|