/* * 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; }
/* * Compress "srcLen" bytes from "pStraw" to "fp". */ NuError Nu_CompressDeflate(NuArchive* pArchive, NuStraw* pStraw, FILE* fp, ulong srcLen, ulong* pDstLen, ushort* pCrc) { NuError err = kNuErrNone; z_stream zstream; int zerr; Bytef* outbuf = nil; Assert(pArchive != nil); Assert(pStraw != nil); Assert(fp != nil); Assert(srcLen > 0); Assert(pDstLen != nil); Assert(pCrc != nil); err = Nu_AllocCompressionBufferIFN(pArchive); if (err != kNuErrNone) return err; /* allocate a similarly-sized buffer for the output */ outbuf = Nu_Malloc(pArchive, kNuGenCompBufSize); BailAlloc(outbuf); /* * Initialize the zlib stream. */ zstream.zalloc = Nu_zalloc; zstream.zfree = Nu_zfree; zstream.opaque = pArchive; zstream.next_in = nil; zstream.avail_in = 0; zstream.next_out = outbuf; zstream.avail_out = kNuGenCompBufSize; zstream.data_type = Z_UNKNOWN; zerr = deflateInit(&zstream, kNuDeflateLevel); if (zerr != Z_OK) { err = kNuErrInternal; if (zerr == Z_VERSION_ERROR) { Nu_ReportError(NU_BLOB, err, "installed zlib is not compatible with linked version (%s)", ZLIB_VERSION); } else { Nu_ReportError(NU_BLOB, err, "call to deflateInit failed (zerr=%d)", zerr); } goto bail; } /* * Loop while we have data. */ do { ulong getSize; int flush; /* should be able to read a full buffer every time */ if (zstream.avail_in == 0 && srcLen) { getSize = (srcLen > kNuGenCompBufSize) ? kNuGenCompBufSize : srcLen; DBUG(("+++ reading %ld bytes\n", getSize)); err = Nu_StrawRead(pArchive, pStraw, pArchive->compBuf, getSize); if (err != kNuErrNone) { Nu_ReportError(NU_BLOB, err, "deflate read failed"); goto z_bail; } srcLen -= getSize; *pCrc = Nu_CalcCRC16(*pCrc, pArchive->compBuf, getSize); zstream.next_in = pArchive->compBuf; zstream.avail_in = getSize; } if (srcLen == 0) flush = Z_FINISH; /* tell zlib that we're done */ else flush = Z_NO_FLUSH; /* more to come! */ zerr = deflate(&zstream, flush); if (zerr != Z_OK && zerr != Z_STREAM_END) { err = kNuErrInternal; Nu_ReportError(NU_BLOB, err, "zlib deflate call failed (zerr=%d)", zerr); goto z_bail; } /* write when we're full or when we're done */ if (zstream.avail_out == 0 || (zerr == Z_STREAM_END && zstream.avail_out != kNuGenCompBufSize)) { DBUG(("+++ writing %d bytes\n", zstream.next_out - outbuf)); err = Nu_FWrite(fp, outbuf, zstream.next_out - outbuf); if (err != kNuErrNone) { Nu_ReportError(NU_BLOB, err, "fwrite failed in deflate"); goto z_bail; } zstream.next_out = outbuf; zstream.avail_out = kNuGenCompBufSize; } } while (zerr == Z_OK); Assert(zerr == Z_STREAM_END); /* other errors should've been caught */ *pDstLen = zstream.total_out; z_bail: deflateEnd(&zstream); /* free up any allocated structures */ bail: if (outbuf != nil) free(outbuf); return err; }
/* * Expand from "infp" to "pFunnel". */ NuError Nu_ExpandDeflate(NuArchive* pArchive, const NuRecord* pRecord, const NuThread* pThread, FILE* infp, NuFunnel* pFunnel, ushort* pCrc) { NuError err = kNuErrNone; z_stream zstream; int zerr; ulong compRemaining; Bytef* outbuf; Assert(pArchive != nil); Assert(pThread != nil); Assert(infp != nil); Assert(pFunnel != nil); err = Nu_AllocCompressionBufferIFN(pArchive); if (err != kNuErrNone) return err; /* allocate a similarly-sized buffer for the output */ outbuf = Nu_Malloc(pArchive, kNuGenCompBufSize); BailAlloc(outbuf); compRemaining = pThread->thCompThreadEOF; /* * Initialize the zlib stream. */ zstream.zalloc = Nu_zalloc; zstream.zfree = Nu_zfree; zstream.opaque = pArchive; zstream.next_in = nil; zstream.avail_in = 0; zstream.next_out = outbuf; zstream.avail_out = kNuGenCompBufSize; zstream.data_type = Z_UNKNOWN; zerr = inflateInit(&zstream); if (zerr != Z_OK) { err = kNuErrInternal; if (zerr == Z_VERSION_ERROR) { Nu_ReportError(NU_BLOB, err, "installed zlib is not compatible with linked version (%s)", ZLIB_VERSION); } else { Nu_ReportError(NU_BLOB, err, "call to inflateInit failed (zerr=%d)", zerr); } goto bail; } /* * Loop while we have data. */ do { ulong getSize; /* read as much as we can */ if (zstream.avail_in == 0) { getSize = (compRemaining > kNuGenCompBufSize) ? kNuGenCompBufSize : compRemaining; DBUG(("+++ reading %ld bytes (%ld left)\n", getSize, compRemaining)); err = Nu_FRead(infp, pArchive->compBuf, getSize); if (err != kNuErrNone) { Nu_ReportError(NU_BLOB, err, "inflate read failed"); goto z_bail; } compRemaining -= getSize; zstream.next_in = pArchive->compBuf; zstream.avail_in = getSize; } /* uncompress the data */ zerr = inflate(&zstream, Z_NO_FLUSH); if (zerr != Z_OK && zerr != Z_STREAM_END) { err = kNuErrInternal; Nu_ReportError(NU_BLOB, err, "zlib inflate call failed (zerr=%d)", zerr); goto z_bail; } /* write every time there's anything (buffer will usually be full) */ if (zstream.avail_out != kNuGenCompBufSize) { DBUG(("+++ writing %d bytes\n", zstream.next_out - outbuf)); err = Nu_FunnelWrite(pArchive, pFunnel, outbuf, zstream.next_out - outbuf); if (err != kNuErrNone) { Nu_ReportError(NU_BLOB, err, "write failed in inflate"); goto z_bail; } if (pCrc != nil) *pCrc = Nu_CalcCRC16(*pCrc, outbuf, zstream.next_out - outbuf); zstream.next_out = outbuf; zstream.avail_out = kNuGenCompBufSize; } } while (zerr == Z_OK); Assert(zerr == Z_STREAM_END); /* other errors should've been caught */ if (zstream.total_out != pThread->actualThreadEOF) { err = kNuErrBadData; Nu_ReportError(NU_BLOB, err, "size mismatch on inflated file (%ld vs %ld)", zstream.total_out, pThread->actualThreadEOF); goto z_bail; } z_bail: inflateEnd(&zstream); /* free up any allocated structures */ bail: if (outbuf != nil) free(outbuf); return err; }
/* * 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; }
/* * 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; }