/* * Crunch deleted files out of an archive by shifting the later files down. * * Because we're not using a temp file, we do the operation inside the * current file. */ status_t ZipFile::crunchArchive(void) { status_t result = NO_ERROR; int i, count; long delCount, adjust; #if 0 printf("CONTENTS:\n"); for (i = 0; i < (int) mEntries.size(); i++) { printf(" %d: lfhOff=%ld del=%d\n", i, mEntries[i]->getLFHOffset(), mEntries[i]->getDeleted()); } printf(" END is %ld\n", (long) mEOCD.mCentralDirOffset); #endif /* * Roll through the set of files, shifting them as appropriate. We * could probably get a slight performance improvement by sliding * multiple files down at once (because we could use larger reads * when operating on batches of small files), but it's not that useful. */ count = mEntries.size(); delCount = adjust = 0; for (i = 0; i < count; i++) { ZipEntry* pEntry = mEntries[i]; long span; if (pEntry->getLFHOffset() != 0) { long nextOffset; /* Get the length of this entry by finding the offset * of the next entry. Directory entries don't have * file offsets, so we need to find the next non-directory * entry. */ nextOffset = 0; for (int ii = i+1; nextOffset == 0 && ii < count; ii++) nextOffset = mEntries[ii]->getLFHOffset(); if (nextOffset == 0) nextOffset = mEOCD.mCentralDirOffset; span = nextOffset - pEntry->getLFHOffset(); assert(span >= ZipEntry::LocalFileHeader::kLFHLen); } else { /* This is a directory entry. It doesn't have * any actual file contents, so there's no need to * move anything. */ span = 0; } //printf("+++ %d: off=%ld span=%ld del=%d [count=%d]\n", // i, pEntry->getLFHOffset(), span, pEntry->getDeleted(), count); if (pEntry->getDeleted()) { adjust += span; delCount++; delete pEntry; mEntries.removeAt(i); /* adjust loop control */ count--; i--; } else if (span != 0 && adjust > 0) { /* shuffle this entry back */ //printf("+++ Shuffling '%s' back %ld\n", // pEntry->getFileName(), adjust); result = filemove(mZipFp, pEntry->getLFHOffset() - adjust, pEntry->getLFHOffset(), span); if (result != NO_ERROR) { /* this is why you use a temp file */ LOGE("error during crunch - archive is toast\n"); return result; } pEntry->setLFHOffset(pEntry->getLFHOffset() - adjust); } } /* * Fix EOCD info. We have to wait until the end to do some of this * because we use mCentralDirOffset to determine "span" for the * last entry. */ mEOCD.mCentralDirOffset -= adjust; mEOCD.mNumEntries -= delCount; mEOCD.mTotalNumEntries -= delCount; mEOCD.mCentralDirSize = 0; // mark invalid; set by flush() assert(mEOCD.mNumEntries == mEOCD.mTotalNumEntries); assert(mEOCD.mNumEntries == count); return result; }
/* * Add a new file to the archive. * * This requires creating and populating a ZipEntry structure, and copying * the data into the file at the appropriate position. The "appropriate * position" is the current location of the central directory, which we * casually overwrite (we can put it back later). * * If we were concerned about safety, we would want to make all changes * in a temp file and then overwrite the original after everything was * safely written. Not really a concern for us. */ status_t ZipFile::addCommon(const char* fileName, const void* data, size_t size, const char* storageName, int sourceType, int compressionMethod, ZipEntry** ppEntry) { ZipEntry* pEntry = NULL; status_t result = NO_ERROR; long lfhPosn, startPosn, endPosn, uncompressedLen; FILE* inputFp = NULL; unsigned long crc; time_t modWhen; if (mReadOnly) return INVALID_OPERATION; assert(compressionMethod == ZipEntry::kCompressDeflated || compressionMethod == ZipEntry::kCompressStored); /* make sure we're in a reasonable state */ assert(mZipFp != NULL); assert(mEntries.size() == mEOCD.mTotalNumEntries); /* make sure it doesn't already exist */ if (getEntryByName(storageName) != NULL) return ALREADY_EXISTS; if (!data) { inputFp = fopen(fileName, FILE_OPEN_RO); if (inputFp == NULL) return errnoToStatus(errno); } if (fseek(mZipFp, mEOCD.mCentralDirOffset, SEEK_SET) != 0) { result = UNKNOWN_ERROR; goto bail; } pEntry = new ZipEntry; pEntry->initNew(storageName, NULL); /* * From here on out, failures are more interesting. */ mNeedCDRewrite = true; /* * Write the LFH, even though it's still mostly blank. We need it * as a place-holder. In theory the LFH isn't necessary, but in * practice some utilities demand it. */ lfhPosn = ftell(mZipFp); pEntry->mLFH.write(mZipFp); startPosn = ftell(mZipFp); /* * Copy the data in, possibly compressing it as we go. */ if (sourceType == ZipEntry::kCompressStored) { if (compressionMethod == ZipEntry::kCompressDeflated) { bool failed = false; result = compressFpToFp(mZipFp, inputFp, data, size, &crc); if (result != NO_ERROR) { LOGD("compression failed, storing\n"); failed = true; } else { /* * Make sure it has compressed "enough". This probably ought * to be set through an API call, but I don't expect our * criteria to change over time. */ long src = inputFp ? ftell(inputFp) : size; long dst = ftell(mZipFp) - startPosn; if (dst + (dst / 10) > src) { LOGD("insufficient compression (src=%ld dst=%ld), storing\n", src, dst); failed = true; } } if (failed) { compressionMethod = ZipEntry::kCompressStored; if (inputFp) rewind(inputFp); fseek(mZipFp, startPosn, SEEK_SET); /* fall through to kCompressStored case */ } } /* handle "no compression" request, or failed compression from above */ if (compressionMethod == ZipEntry::kCompressStored) { if (inputFp) { result = copyFpToFp(mZipFp, inputFp, &crc); } else { result = copyDataToFp(mZipFp, data, size, &crc); } if (result != NO_ERROR) { // don't need to truncate; happens in CDE rewrite LOGD("failed copying data in\n"); goto bail; } } // currently seeked to end of file uncompressedLen = inputFp ? ftell(inputFp) : size; } else if (sourceType == ZipEntry::kCompressDeflated) { /* we should support uncompressed-from-compressed, but it's not * important right now */ assert(compressionMethod == ZipEntry::kCompressDeflated); bool scanResult; int method; long compressedLen; scanResult = ZipUtils::examineGzip(inputFp, &method, &uncompressedLen, &compressedLen, &crc); if (!scanResult || method != ZipEntry::kCompressDeflated) { LOGD("this isn't a deflated gzip file?"); result = UNKNOWN_ERROR; goto bail; } result = copyPartialFpToFp(mZipFp, inputFp, compressedLen, NULL); if (result != NO_ERROR) { LOGD("failed copying gzip data in\n"); goto bail; } } else { assert(false); result = UNKNOWN_ERROR; goto bail; } /* * We could write the "Data Descriptor", but there doesn't seem to * be any point since we're going to go back and write the LFH. * * Update file offsets. */ endPosn = ftell(mZipFp); // seeked to end of compressed data /* * Success! Fill out new values. */ pEntry->setDataInfo(uncompressedLen, endPosn - startPosn, crc, compressionMethod); modWhen = getModTime(inputFp ? fileno(inputFp) : fileno(mZipFp)); pEntry->setModWhen(modWhen); pEntry->setLFHOffset(lfhPosn); mEOCD.mNumEntries++; mEOCD.mTotalNumEntries++; mEOCD.mCentralDirSize = 0; // mark invalid; set by flush() mEOCD.mCentralDirOffset = endPosn; /* * Go back and write the LFH. */ if (fseek(mZipFp, lfhPosn, SEEK_SET) != 0) { result = UNKNOWN_ERROR; goto bail; } pEntry->mLFH.write(mZipFp); /* * Add pEntry to the list. */ mEntries.add(pEntry); if (ppEntry != NULL) *ppEntry = pEntry; pEntry = NULL; bail: if (inputFp != NULL) fclose(inputFp); delete pEntry; return result; }
/* * Add an entry by copying it from another zip file. If "padding" is * nonzero, the specified number of bytes will be added to the "extra" * field in the header. * * If "ppEntry" is non-NULL, a pointer to the new entry will be returned. */ status_t ZipFile::add(const ZipFile* pSourceZip, const ZipEntry* pSourceEntry, int padding, ZipEntry** ppEntry) { ZipEntry* pEntry = NULL; status_t result; long lfhPosn, endPosn; if (mReadOnly) return INVALID_OPERATION; /* make sure we're in a reasonable state */ assert(mZipFp != NULL); assert(mEntries.size() == mEOCD.mTotalNumEntries); if (fseek(mZipFp, mEOCD.mCentralDirOffset, SEEK_SET) != 0) { result = UNKNOWN_ERROR; goto bail; } pEntry = new ZipEntry; if (pEntry == NULL) { result = NO_MEMORY; goto bail; } result = pEntry->initFromExternal(pSourceZip, pSourceEntry); if (result != NO_ERROR) goto bail; if (padding != 0) { result = pEntry->addPadding(padding); if (result != NO_ERROR) goto bail; } /* * From here on out, failures are more interesting. */ mNeedCDRewrite = true; /* * Write the LFH. Since we're not recompressing the data, we already * have all of the fields filled out. */ lfhPosn = ftell(mZipFp); pEntry->mLFH.write(mZipFp); /* * Copy the data over. * * If the "has data descriptor" flag is set, we want to copy the DD * fields as well. This is a fixed-size area immediately following * the data. */ if (fseek(pSourceZip->mZipFp, pSourceEntry->getFileOffset(), SEEK_SET) != 0) { result = UNKNOWN_ERROR; goto bail; } off_t copyLen; copyLen = pSourceEntry->getCompressedLen(); if ((pSourceEntry->mLFH.mGPBitFlag & ZipEntry::kUsesDataDescr) != 0) copyLen += ZipEntry::kDataDescriptorLen; if (copyPartialFpToFp(mZipFp, pSourceZip->mZipFp, copyLen, NULL) != NO_ERROR) { LOGW("copy of '%s' failed\n", pEntry->mCDE.mFileName); result = UNKNOWN_ERROR; goto bail; } /* * Update file offsets. */ endPosn = ftell(mZipFp); /* * Success! Fill out new values. */ pEntry->setLFHOffset(lfhPosn); // sets mCDE.mLocalHeaderRelOffset mEOCD.mNumEntries++; mEOCD.mTotalNumEntries++; mEOCD.mCentralDirSize = 0; // mark invalid; set by flush() mEOCD.mCentralDirOffset = endPosn; /* * Add pEntry to the list. */ mEntries.add(pEntry); if (ppEntry != NULL) *ppEntry = pEntry; pEntry = NULL; result = NO_ERROR; bail: delete pEntry; return result; }
/* * Add an entry by copying it from another zip file, recompressing with * Zopfli if already compressed. * * If "ppEntry" is non-NULL, a pointer to the new entry will be returned. */ status_t ZipFile::addRecompress(const ZipFile* pSourceZip, const ZipEntry* pSourceEntry, ZipEntry** ppEntry) { ZipEntry* pEntry = NULL; status_t result; long lfhPosn, startPosn, endPosn, uncompressedLen; if (mReadOnly) return INVALID_OPERATION; /* make sure we're in a reasonable state */ assert(mZipFp != NULL); assert(mEntries.size() == mEOCD.mTotalNumEntries); if (fseek(mZipFp, mEOCD.mCentralDirOffset, SEEK_SET) != 0) { result = UNKNOWN_ERROR; goto bail; } pEntry = new ZipEntry; if (pEntry == NULL) { result = NO_MEMORY; goto bail; } result = pEntry->initFromExternal(pSourceEntry); if (result != NO_ERROR) goto bail; /* * From here on out, failures are more interesting. */ mNeedCDRewrite = true; /* * Write the LFH, even though it's still mostly blank. We need it * as a place-holder. In theory the LFH isn't necessary, but in * practice some utilities demand it. */ lfhPosn = ftell(mZipFp); pEntry->mLFH.write(mZipFp); startPosn = ftell(mZipFp); /* * Copy the data over. * * If the "has data descriptor" flag is set, we want to copy the DD * fields as well. This is a fixed-size area immediately following * the data. */ if (fseek(pSourceZip->mZipFp, pSourceEntry->getFileOffset(), SEEK_SET) != 0) { result = UNKNOWN_ERROR; goto bail; } uncompressedLen = pSourceEntry->getUncompressedLen(); if (pSourceEntry->isCompressed()) { void *buf = pSourceZip->uncompress(pSourceEntry); if (buf == NULL) { result = NO_MEMORY; goto bail; } long startPosn = ftell(mZipFp); uint32_t crc; if (compressFpToFp(mZipFp, NULL, buf, uncompressedLen, &crc) != NO_ERROR) { ALOGW("recompress of '%s' failed\n", pEntry->mCDE.mFileName); result = UNKNOWN_ERROR; free(buf); goto bail; } long endPosn = ftell(mZipFp); pEntry->setDataInfo(uncompressedLen, endPosn - startPosn, pSourceEntry->getCRC32(), ZipEntry::kCompressDeflated); free(buf); } else { off_t copyLen; copyLen = pSourceEntry->getCompressedLen(); if ((pSourceEntry->mLFH.mGPBitFlag & ZipEntry::kUsesDataDescr) != 0) copyLen += ZipEntry::kDataDescriptorLen; if (copyPartialFpToFp(mZipFp, pSourceZip->mZipFp, copyLen, NULL) != NO_ERROR) { ALOGW("copy of '%s' failed\n", pEntry->mCDE.mFileName); result = UNKNOWN_ERROR; goto bail; } } /* * Update file offsets. */ endPosn = ftell(mZipFp); /* * Success! Fill out new values. */ pEntry->setLFHOffset(lfhPosn); mEOCD.mNumEntries++; mEOCD.mTotalNumEntries++; mEOCD.mCentralDirSize = 0; // mark invalid; set by flush() mEOCD.mCentralDirOffset = endPosn; /* * Go back and write the LFH. */ if (fseek(mZipFp, lfhPosn, SEEK_SET) != 0) { result = UNKNOWN_ERROR; goto bail; } pEntry->mLFH.write(mZipFp); /* * Add pEntry to the list. */ mEntries.add(pEntry); if (ppEntry != NULL) *ppEntry = pEntry; pEntry = NULL; result = NO_ERROR; bail: delete pEntry; return result; }