/* * 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; }
/* * 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; }
/* * 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; }
/* * 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; }
/* * 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; }
/* * 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; }
/* * 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 NulibState'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; int newBufLen; Assert(pState != NULL); Assert(pPathProposal != NULL); Assert(pPathProposal->pathnameUNI != NULL); 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. */ newBufLen = strlen(pPathProposal->pathnameUNI)*3 + kMaxPathGrowth +1; NState_SetTempPathnameLen(pState, newBufLen); pathBuf = NState_GetTempPathnameBuf(pState); Assert(pathBuf != NULL); if (pathBuf == NULL) return NULL; startp = pPathProposal->pathnameUNI; 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 != NULL) { endp = strchr(startp, pPathProposal->filenameSeparator); if (endp != NULL) { /* 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) { #ifndef HAS_RESOURCE_FORKS /* add this in lieu of the preservation extension */ strcat(pathBuf, kResourceStr); #endif } startp = NULL; /* we're done */ } } pPathProposal->newPathnameUNI = pathBuf; pPathProposal->newFilenameSeparator = localFssep; /* check for overflow */ Assert(dstp - pathBuf <= newBufLen); /* * If "junk paths" is set, drop everything but the last component. */ if (NState_GetModJunkPaths(pState)) { char* lastFssep; lastFssep = strrchr(pathBuf, localFssep); if (lastFssep != NULL) { Assert(*(lastFssep+1) != '\0'); /* should already have been caught*/ memmove(pathBuf, lastFssep+1, strlen(lastFssep+1)+1); } } bail: if (err != kNuErrNone) return NULL; return pathBuf; }
/* * 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 %u\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; (uint32_t)threadIdx < pRecord->recTotalThreads; threadIdx++) { pThread = NuGetThread(pRecord, threadIdx); Assert(pThread != NULL); if (NuGetThreadID(pThread) == kNuThreadIDComment && pThread->actualThreadEOF > 0) { UNICHAR* filenameUNI = CopyMORToUNI(pRecord->filenameMOR); printf("----- '%s':\n", filenameUNI); free(filenameUNI); 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; }