Home | History | Annotate | Download | only in zipalign
      1 /*
      2  * Copyright (C) 2006 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 //
     18 // Access to Zip archives.
     19 //
     20 
     21 #define LOG_TAG "zip"
     22 
     23 #include <androidfw/ZipUtils.h>
     24 #include <utils/Log.h>
     25 
     26 #include "ZipFile.h"
     27 
     28 #include <zlib.h>
     29 #define DEF_MEM_LEVEL 8                // normally in zutil.h?
     30 
     31 #include "zopfli/deflate.h"
     32 
     33 #include <memory.h>
     34 #include <sys/stat.h>
     35 #include <errno.h>
     36 #include <assert.h>
     37 
     38 using namespace android;
     39 
     40 /*
     41  * Some environments require the "b", some choke on it.
     42  */
     43 #define FILE_OPEN_RO        "rb"
     44 #define FILE_OPEN_RW        "r+b"
     45 #define FILE_OPEN_RW_CREATE "w+b"
     46 
     47 /* should live somewhere else? */
     48 static status_t errnoToStatus(int err)
     49 {
     50     if (err == ENOENT)
     51         return NAME_NOT_FOUND;
     52     else if (err == EACCES)
     53         return PERMISSION_DENIED;
     54     else
     55         return UNKNOWN_ERROR;
     56 }
     57 
     58 /*
     59  * Open a file and parse its guts.
     60  */
     61 status_t ZipFile::open(const char* zipFileName, int flags)
     62 {
     63     bool newArchive = false;
     64 
     65     assert(mZipFp == NULL);     // no reopen
     66 
     67     if ((flags & kOpenTruncate))
     68         flags |= kOpenCreate;           // trunc implies create
     69 
     70     if ((flags & kOpenReadOnly) && (flags & kOpenReadWrite))
     71         return INVALID_OPERATION;       // not both
     72     if (!((flags & kOpenReadOnly) || (flags & kOpenReadWrite)))
     73         return INVALID_OPERATION;       // not neither
     74     if ((flags & kOpenCreate) && !(flags & kOpenReadWrite))
     75         return INVALID_OPERATION;       // create requires write
     76 
     77     if (flags & kOpenTruncate) {
     78         newArchive = true;
     79     } else {
     80         newArchive = (access(zipFileName, F_OK) != 0);
     81         if (!(flags & kOpenCreate) && newArchive) {
     82             /* not creating, must already exist */
     83             ALOGD("File %s does not exist", zipFileName);
     84             return NAME_NOT_FOUND;
     85         }
     86     }
     87 
     88     /* open the file */
     89     const char* openflags;
     90     if (flags & kOpenReadWrite) {
     91         if (newArchive)
     92             openflags = FILE_OPEN_RW_CREATE;
     93         else
     94             openflags = FILE_OPEN_RW;
     95     } else {
     96         openflags = FILE_OPEN_RO;
     97     }
     98     mZipFp = fopen(zipFileName, openflags);
     99     if (mZipFp == NULL) {
    100         int err = errno;
    101         ALOGD("fopen failed: %d\n", err);
    102         return errnoToStatus(err);
    103     }
    104 
    105     status_t result;
    106     if (!newArchive) {
    107         /*
    108          * Load the central directory.  If that fails, then this probably
    109          * isn't a Zip archive.
    110          */
    111         result = readCentralDir();
    112     } else {
    113         /*
    114          * Newly-created.  The EndOfCentralDir constructor actually
    115          * sets everything to be the way we want it (all zeroes).  We
    116          * set mNeedCDRewrite so that we create *something* if the
    117          * caller doesn't add any files.  (We could also just unlink
    118          * the file if it's brand new and nothing was added, but that's
    119          * probably doing more than we really should -- the user might
    120          * have a need for empty zip files.)
    121          */
    122         mNeedCDRewrite = true;
    123         result = NO_ERROR;
    124     }
    125 
    126     if (flags & kOpenReadOnly)
    127         mReadOnly = true;
    128     else
    129         assert(!mReadOnly);
    130 
    131     return result;
    132 }
    133 
    134 /*
    135  * Return the Nth entry in the archive.
    136  */
    137 ZipEntry* ZipFile::getEntryByIndex(int idx) const
    138 {
    139     if (idx < 0 || idx >= (int) mEntries.size())
    140         return NULL;
    141 
    142     return mEntries[idx];
    143 }
    144 
    145 /*
    146  * Find an entry by name.
    147  */
    148 ZipEntry* ZipFile::getEntryByName(const char* fileName) const
    149 {
    150     /*
    151      * Do a stupid linear string-compare search.
    152      *
    153      * There are various ways to speed this up, especially since it's rare
    154      * to intermingle changes to the archive with "get by name" calls.  We
    155      * don't want to sort the mEntries vector itself, however, because
    156      * it's used to recreate the Central Directory.
    157      *
    158      * (Hash table works, parallel list of pointers in sorted order is good.)
    159      */
    160     int idx;
    161 
    162     for (idx = mEntries.size()-1; idx >= 0; idx--) {
    163         ZipEntry* pEntry = mEntries[idx];
    164         if (!pEntry->getDeleted() &&
    165             strcmp(fileName, pEntry->getFileName()) == 0)
    166         {
    167             return pEntry;
    168         }
    169     }
    170 
    171     return NULL;
    172 }
    173 
    174 /*
    175  * Empty the mEntries vector.
    176  */
    177 void ZipFile::discardEntries(void)
    178 {
    179     int count = mEntries.size();
    180 
    181     while (--count >= 0)
    182         delete mEntries[count];
    183 
    184     mEntries.clear();
    185 }
    186 
    187 
    188 /*
    189  * Find the central directory and read the contents.
    190  *
    191  * The fun thing about ZIP archives is that they may or may not be
    192  * readable from start to end.  In some cases, notably for archives
    193  * that were written to stdout, the only length information is in the
    194  * central directory at the end of the file.
    195  *
    196  * Of course, the central directory can be followed by a variable-length
    197  * comment field, so we have to scan through it backwards.  The comment
    198  * is at most 64K, plus we have 18 bytes for the end-of-central-dir stuff
    199  * itself, plus apparently sometimes people throw random junk on the end
    200  * just for the fun of it.
    201  *
    202  * This is all a little wobbly.  If the wrong value ends up in the EOCD
    203  * area, we're hosed.  This appears to be the way that everbody handles
    204  * it though, so we're in pretty good company if this fails.
    205  */
    206 status_t ZipFile::readCentralDir(void)
    207 {
    208     status_t result = NO_ERROR;
    209     unsigned char* buf = NULL;
    210     off_t fileLength, seekStart;
    211     long readAmount;
    212     int i;
    213 
    214     fseek(mZipFp, 0, SEEK_END);
    215     fileLength = ftell(mZipFp);
    216     rewind(mZipFp);
    217 
    218     /* too small to be a ZIP archive? */
    219     if (fileLength < EndOfCentralDir::kEOCDLen) {
    220         ALOGD("Length is %ld -- too small\n", (long)fileLength);
    221         result = INVALID_OPERATION;
    222         goto bail;
    223     }
    224 
    225     buf = new unsigned char[EndOfCentralDir::kMaxEOCDSearch];
    226     if (buf == NULL) {
    227         ALOGD("Failure allocating %d bytes for EOCD search",
    228              EndOfCentralDir::kMaxEOCDSearch);
    229         result = NO_MEMORY;
    230         goto bail;
    231     }
    232 
    233     if (fileLength > EndOfCentralDir::kMaxEOCDSearch) {
    234         seekStart = fileLength - EndOfCentralDir::kMaxEOCDSearch;
    235         readAmount = EndOfCentralDir::kMaxEOCDSearch;
    236     } else {
    237         seekStart = 0;
    238         readAmount = (long) fileLength;
    239     }
    240     if (fseek(mZipFp, seekStart, SEEK_SET) != 0) {
    241         ALOGD("Failure seeking to end of zip at %ld", (long) seekStart);
    242         result = UNKNOWN_ERROR;
    243         goto bail;
    244     }
    245 
    246     /* read the last part of the file into the buffer */
    247     if (fread(buf, 1, readAmount, mZipFp) != (size_t) readAmount) {
    248         ALOGD("short file? wanted %ld\n", readAmount);
    249         result = UNKNOWN_ERROR;
    250         goto bail;
    251     }
    252 
    253     /* find the end-of-central-dir magic */
    254     for (i = readAmount - 4; i >= 0; i--) {
    255         if (buf[i] == 0x50 &&
    256             ZipEntry::getLongLE(&buf[i]) == EndOfCentralDir::kSignature)
    257         {
    258             ALOGV("+++ Found EOCD at buf+%d\n", i);
    259             break;
    260         }
    261     }
    262     if (i < 0) {
    263         ALOGD("EOCD not found, not Zip\n");
    264         result = INVALID_OPERATION;
    265         goto bail;
    266     }
    267 
    268     /* extract eocd values */
    269     result = mEOCD.readBuf(buf + i, readAmount - i);
    270     if (result != NO_ERROR) {
    271         ALOGD("Failure reading %ld bytes of EOCD values", readAmount - i);
    272         goto bail;
    273     }
    274     //mEOCD.dump();
    275 
    276     if (mEOCD.mDiskNumber != 0 || mEOCD.mDiskWithCentralDir != 0 ||
    277         mEOCD.mNumEntries != mEOCD.mTotalNumEntries)
    278     {
    279         ALOGD("Archive spanning not supported\n");
    280         result = INVALID_OPERATION;
    281         goto bail;
    282     }
    283 
    284     /*
    285      * So far so good.  "mCentralDirSize" is the size in bytes of the
    286      * central directory, so we can just seek back that far to find it.
    287      * We can also seek forward mCentralDirOffset bytes from the
    288      * start of the file.
    289      *
    290      * We're not guaranteed to have the rest of the central dir in the
    291      * buffer, nor are we guaranteed that the central dir will have any
    292      * sort of convenient size.  We need to skip to the start of it and
    293      * read the header, then the other goodies.
    294      *
    295      * The only thing we really need right now is the file comment, which
    296      * we're hoping to preserve.
    297      */
    298     if (fseek(mZipFp, mEOCD.mCentralDirOffset, SEEK_SET) != 0) {
    299         ALOGD("Failure seeking to central dir offset %ld\n",
    300              mEOCD.mCentralDirOffset);
    301         result = UNKNOWN_ERROR;
    302         goto bail;
    303     }
    304 
    305     /*
    306      * Loop through and read the central dir entries.
    307      */
    308     ALOGV("Scanning %d entries...\n", mEOCD.mTotalNumEntries);
    309     int entry;
    310     for (entry = 0; entry < mEOCD.mTotalNumEntries; entry++) {
    311         ZipEntry* pEntry = new ZipEntry;
    312 
    313         result = pEntry->initFromCDE(mZipFp);
    314         if (result != NO_ERROR) {
    315             ALOGD("initFromCDE failed\n");
    316             delete pEntry;
    317             goto bail;
    318         }
    319 
    320         mEntries.add(pEntry);
    321     }
    322 
    323 
    324     /*
    325      * If all went well, we should now be back at the EOCD.
    326      */
    327     {
    328         unsigned char checkBuf[4];
    329         if (fread(checkBuf, 1, 4, mZipFp) != 4) {
    330             ALOGD("EOCD check read failed\n");
    331             result = INVALID_OPERATION;
    332             goto bail;
    333         }
    334         if (ZipEntry::getLongLE(checkBuf) != EndOfCentralDir::kSignature) {
    335             ALOGD("EOCD read check failed\n");
    336             result = UNKNOWN_ERROR;
    337             goto bail;
    338         }
    339         ALOGV("+++ EOCD read check passed\n");
    340     }
    341 
    342 bail:
    343     delete[] buf;
    344     return result;
    345 }
    346 
    347 
    348 /*
    349  * Add a new file to the archive.
    350  *
    351  * This requires creating and populating a ZipEntry structure, and copying
    352  * the data into the file at the appropriate position.  The "appropriate
    353  * position" is the current location of the central directory, which we
    354  * casually overwrite (we can put it back later).
    355  *
    356  * If we were concerned about safety, we would want to make all changes
    357  * in a temp file and then overwrite the original after everything was
    358  * safely written.  Not really a concern for us.
    359  */
    360 status_t ZipFile::addCommon(const char* fileName, const void* data, size_t size,
    361     const char* storageName, int sourceType, int compressionMethod,
    362     ZipEntry** ppEntry)
    363 {
    364     ZipEntry* pEntry = NULL;
    365     status_t result = NO_ERROR;
    366     long lfhPosn, startPosn, endPosn, uncompressedLen;
    367     FILE* inputFp = NULL;
    368     unsigned long crc;
    369     time_t modWhen;
    370 
    371     if (mReadOnly)
    372         return INVALID_OPERATION;
    373 
    374     assert(compressionMethod == ZipEntry::kCompressDeflated ||
    375            compressionMethod == ZipEntry::kCompressStored);
    376 
    377     /* make sure we're in a reasonable state */
    378     assert(mZipFp != NULL);
    379     assert(mEntries.size() == mEOCD.mTotalNumEntries);
    380 
    381     /* make sure it doesn't already exist */
    382     if (getEntryByName(storageName) != NULL)
    383         return ALREADY_EXISTS;
    384 
    385     if (!data) {
    386         inputFp = fopen(fileName, FILE_OPEN_RO);
    387         if (inputFp == NULL)
    388             return errnoToStatus(errno);
    389     }
    390 
    391     if (fseek(mZipFp, mEOCD.mCentralDirOffset, SEEK_SET) != 0) {
    392         result = UNKNOWN_ERROR;
    393         goto bail;
    394     }
    395 
    396     pEntry = new ZipEntry;
    397     pEntry->initNew(storageName, NULL);
    398 
    399     /*
    400      * From here on out, failures are more interesting.
    401      */
    402     mNeedCDRewrite = true;
    403 
    404     /*
    405      * Write the LFH, even though it's still mostly blank.  We need it
    406      * as a place-holder.  In theory the LFH isn't necessary, but in
    407      * practice some utilities demand it.
    408      */
    409     lfhPosn = ftell(mZipFp);
    410     pEntry->mLFH.write(mZipFp);
    411     startPosn = ftell(mZipFp);
    412 
    413     /*
    414      * Copy the data in, possibly compressing it as we go.
    415      */
    416     if (sourceType == ZipEntry::kCompressStored) {
    417         if (compressionMethod == ZipEntry::kCompressDeflated) {
    418             bool failed = false;
    419             result = compressFpToFp(mZipFp, inputFp, data, size, &crc);
    420             if (result != NO_ERROR) {
    421                 ALOGD("compression failed, storing\n");
    422                 failed = true;
    423             } else {
    424                 /*
    425                  * Make sure it has compressed "enough".  This probably ought
    426                  * to be set through an API call, but I don't expect our
    427                  * criteria to change over time.
    428                  */
    429                 long src = inputFp ? ftell(inputFp) : size;
    430                 long dst = ftell(mZipFp) - startPosn;
    431                 if (dst + (dst / 10) > src) {
    432                     ALOGD("insufficient compression (src=%ld dst=%ld), storing\n",
    433                         src, dst);
    434                     failed = true;
    435                 }
    436             }
    437 
    438             if (failed) {
    439                 compressionMethod = ZipEntry::kCompressStored;
    440                 if (inputFp) rewind(inputFp);
    441                 fseek(mZipFp, startPosn, SEEK_SET);
    442                 /* fall through to kCompressStored case */
    443             }
    444         }
    445         /* handle "no compression" request, or failed compression from above */
    446         if (compressionMethod == ZipEntry::kCompressStored) {
    447             if (inputFp) {
    448                 result = copyFpToFp(mZipFp, inputFp, &crc);
    449             } else {
    450                 result = copyDataToFp(mZipFp, data, size, &crc);
    451             }
    452             if (result != NO_ERROR) {
    453                 // don't need to truncate; happens in CDE rewrite
    454                 ALOGD("failed copying data in\n");
    455                 goto bail;
    456             }
    457         }
    458 
    459         // currently seeked to end of file
    460         uncompressedLen = inputFp ? ftell(inputFp) : size;
    461     } else if (sourceType == ZipEntry::kCompressDeflated) {
    462         /* we should support uncompressed-from-compressed, but it's not
    463          * important right now */
    464         assert(compressionMethod == ZipEntry::kCompressDeflated);
    465 
    466         bool scanResult;
    467         int method;
    468         long compressedLen;
    469 
    470         scanResult = ZipUtils::examineGzip(inputFp, &method, &uncompressedLen,
    471                         &compressedLen, &crc);
    472         if (!scanResult || method != ZipEntry::kCompressDeflated) {
    473             ALOGD("this isn't a deflated gzip file?");
    474             result = UNKNOWN_ERROR;
    475             goto bail;
    476         }
    477 
    478         result = copyPartialFpToFp(mZipFp, inputFp, compressedLen, NULL);
    479         if (result != NO_ERROR) {
    480             ALOGD("failed copying gzip data in\n");
    481             goto bail;
    482         }
    483     } else {
    484         assert(false);
    485         result = UNKNOWN_ERROR;
    486         goto bail;
    487     }
    488 
    489     /*
    490      * We could write the "Data Descriptor", but there doesn't seem to
    491      * be any point since we're going to go back and write the LFH.
    492      *
    493      * Update file offsets.
    494      */
    495     endPosn = ftell(mZipFp);            // seeked to end of compressed data
    496 
    497     /*
    498      * Success!  Fill out new values.
    499      */
    500     pEntry->setDataInfo(uncompressedLen, endPosn - startPosn, crc,
    501         compressionMethod);
    502     modWhen = getModTime(inputFp ? fileno(inputFp) : fileno(mZipFp));
    503     pEntry->setModWhen(modWhen);
    504     pEntry->setLFHOffset(lfhPosn);
    505     mEOCD.mNumEntries++;
    506     mEOCD.mTotalNumEntries++;
    507     mEOCD.mCentralDirSize = 0;      // mark invalid; set by flush()
    508     mEOCD.mCentralDirOffset = endPosn;
    509 
    510     /*
    511      * Go back and write the LFH.
    512      */
    513     if (fseek(mZipFp, lfhPosn, SEEK_SET) != 0) {
    514         result = UNKNOWN_ERROR;
    515         goto bail;
    516     }
    517     pEntry->mLFH.write(mZipFp);
    518 
    519     /*
    520      * Add pEntry to the list.
    521      */
    522     mEntries.add(pEntry);
    523     if (ppEntry != NULL)
    524         *ppEntry = pEntry;
    525     pEntry = NULL;
    526 
    527 bail:
    528     if (inputFp != NULL)
    529         fclose(inputFp);
    530     delete pEntry;
    531     return result;
    532 }
    533 
    534 /*
    535  * Add an entry by copying it from another zip file.  If "padding" is
    536  * nonzero, the specified number of bytes will be added to the "extra"
    537  * field in the header.
    538  *
    539  * If "ppEntry" is non-NULL, a pointer to the new entry will be returned.
    540  */
    541 status_t ZipFile::add(const ZipFile* pSourceZip, const ZipEntry* pSourceEntry,
    542     int padding, ZipEntry** ppEntry)
    543 {
    544     ZipEntry* pEntry = NULL;
    545     status_t result;
    546     long lfhPosn, endPosn;
    547 
    548     if (mReadOnly)
    549         return INVALID_OPERATION;
    550 
    551     /* make sure we're in a reasonable state */
    552     assert(mZipFp != NULL);
    553     assert(mEntries.size() == mEOCD.mTotalNumEntries);
    554 
    555     if (fseek(mZipFp, mEOCD.mCentralDirOffset, SEEK_SET) != 0) {
    556         result = UNKNOWN_ERROR;
    557         goto bail;
    558     }
    559 
    560     pEntry = new ZipEntry;
    561     if (pEntry == NULL) {
    562         result = NO_MEMORY;
    563         goto bail;
    564     }
    565 
    566     result = pEntry->initFromExternal(pSourceZip, pSourceEntry);
    567     if (result != NO_ERROR)
    568         goto bail;
    569     if (padding != 0) {
    570         result = pEntry->addPadding(padding);
    571         if (result != NO_ERROR)
    572             goto bail;
    573     }
    574 
    575     /*
    576      * From here on out, failures are more interesting.
    577      */
    578     mNeedCDRewrite = true;
    579 
    580     /*
    581      * Write the LFH.  Since we're not recompressing the data, we already
    582      * have all of the fields filled out.
    583      */
    584     lfhPosn = ftell(mZipFp);
    585     pEntry->mLFH.write(mZipFp);
    586 
    587     /*
    588      * Copy the data over.
    589      *
    590      * If the "has data descriptor" flag is set, we want to copy the DD
    591      * fields as well.  This is a fixed-size area immediately following
    592      * the data.
    593      */
    594     if (fseek(pSourceZip->mZipFp, pSourceEntry->getFileOffset(), SEEK_SET) != 0)
    595     {
    596         result = UNKNOWN_ERROR;
    597         goto bail;
    598     }
    599 
    600     off_t copyLen;
    601     copyLen = pSourceEntry->getCompressedLen();
    602     if ((pSourceEntry->mLFH.mGPBitFlag & ZipEntry::kUsesDataDescr) != 0)
    603         copyLen += ZipEntry::kDataDescriptorLen;
    604 
    605     if (copyPartialFpToFp(mZipFp, pSourceZip->mZipFp, copyLen, NULL)
    606         != NO_ERROR)
    607     {
    608         ALOGW("copy of '%s' failed\n", pEntry->mCDE.mFileName);
    609         result = UNKNOWN_ERROR;
    610         goto bail;
    611     }
    612 
    613     /*
    614      * Update file offsets.
    615      */
    616     endPosn = ftell(mZipFp);
    617 
    618     /*
    619      * Success!  Fill out new values.
    620      */
    621     pEntry->setLFHOffset(lfhPosn);      // sets mCDE.mLocalHeaderRelOffset
    622     mEOCD.mNumEntries++;
    623     mEOCD.mTotalNumEntries++;
    624     mEOCD.mCentralDirSize = 0;      // mark invalid; set by flush()
    625     mEOCD.mCentralDirOffset = endPosn;
    626 
    627     /*
    628      * Add pEntry to the list.
    629      */
    630     mEntries.add(pEntry);
    631     if (ppEntry != NULL)
    632         *ppEntry = pEntry;
    633     pEntry = NULL;
    634 
    635     result = NO_ERROR;
    636 
    637 bail:
    638     delete pEntry;
    639     return result;
    640 }
    641 
    642 /*
    643  * Add an entry by copying it from another zip file, recompressing with
    644  * Zopfli if already compressed.
    645  *
    646  * If "ppEntry" is non-NULL, a pointer to the new entry will be returned.
    647  */
    648 status_t ZipFile::addRecompress(const ZipFile* pSourceZip, const ZipEntry* pSourceEntry,
    649     ZipEntry** ppEntry)
    650 {
    651     ZipEntry* pEntry = NULL;
    652     status_t result;
    653     long lfhPosn, startPosn, endPosn, uncompressedLen;
    654 
    655     if (mReadOnly)
    656         return INVALID_OPERATION;
    657 
    658     /* make sure we're in a reasonable state */
    659     assert(mZipFp != NULL);
    660     assert(mEntries.size() == mEOCD.mTotalNumEntries);
    661 
    662     if (fseek(mZipFp, mEOCD.mCentralDirOffset, SEEK_SET) != 0) {
    663         result = UNKNOWN_ERROR;
    664         goto bail;
    665     }
    666 
    667     pEntry = new ZipEntry;
    668     if (pEntry == NULL) {
    669         result = NO_MEMORY;
    670         goto bail;
    671     }
    672 
    673     result = pEntry->initFromExternal(pSourceZip, pSourceEntry);
    674     if (result != NO_ERROR)
    675         goto bail;
    676 
    677     /*
    678      * From here on out, failures are more interesting.
    679      */
    680     mNeedCDRewrite = true;
    681 
    682     /*
    683      * Write the LFH, even though it's still mostly blank.  We need it
    684      * as a place-holder.  In theory the LFH isn't necessary, but in
    685      * practice some utilities demand it.
    686      */
    687     lfhPosn = ftell(mZipFp);
    688     pEntry->mLFH.write(mZipFp);
    689     startPosn = ftell(mZipFp);
    690 
    691     /*
    692      * Copy the data over.
    693      *
    694      * If the "has data descriptor" flag is set, we want to copy the DD
    695      * fields as well.  This is a fixed-size area immediately following
    696      * the data.
    697      */
    698     if (fseek(pSourceZip->mZipFp, pSourceEntry->getFileOffset(), SEEK_SET) != 0)
    699     {
    700         result = UNKNOWN_ERROR;
    701         goto bail;
    702     }
    703 
    704     uncompressedLen = pSourceEntry->getUncompressedLen();
    705 
    706     if (pSourceEntry->isCompressed()) {
    707         void *buf = pSourceZip->uncompress(pSourceEntry);
    708         if (buf == NULL) {
    709             result = NO_MEMORY;
    710             goto bail;
    711         }
    712         long startPosn = ftell(mZipFp);
    713         unsigned long crc;
    714         if (compressFpToFp(mZipFp, NULL, buf, uncompressedLen, &crc) != NO_ERROR) {
    715             ALOGW("recompress of '%s' failed\n", pEntry->mCDE.mFileName);
    716             result = UNKNOWN_ERROR;
    717             free(buf);
    718             goto bail;
    719         }
    720         long endPosn = ftell(mZipFp);
    721         pEntry->setDataInfo(uncompressedLen, endPosn - startPosn,
    722             pSourceEntry->getCRC32(), ZipEntry::kCompressDeflated);
    723         free(buf);
    724     } else {
    725         off_t copyLen;
    726         copyLen = pSourceEntry->getCompressedLen();
    727         if ((pSourceEntry->mLFH.mGPBitFlag & ZipEntry::kUsesDataDescr) != 0)
    728             copyLen += ZipEntry::kDataDescriptorLen;
    729 
    730         if (copyPartialFpToFp(mZipFp, pSourceZip->mZipFp, copyLen, NULL)
    731             != NO_ERROR)
    732         {
    733             ALOGW("copy of '%s' failed\n", pEntry->mCDE.mFileName);
    734             result = UNKNOWN_ERROR;
    735             goto bail;
    736         }
    737     }
    738 
    739     /*
    740      * Update file offsets.
    741      */
    742     endPosn = ftell(mZipFp);
    743 
    744     /*
    745      * Success!  Fill out new values.
    746      */
    747     pEntry->setLFHOffset(lfhPosn);
    748     mEOCD.mNumEntries++;
    749     mEOCD.mTotalNumEntries++;
    750     mEOCD.mCentralDirSize = 0;      // mark invalid; set by flush()
    751     mEOCD.mCentralDirOffset = endPosn;
    752 
    753     /*
    754      * Go back and write the LFH.
    755      */
    756     if (fseek(mZipFp, lfhPosn, SEEK_SET) != 0) {
    757         result = UNKNOWN_ERROR;
    758         goto bail;
    759     }
    760     pEntry->mLFH.write(mZipFp);
    761 
    762     /*
    763      * Add pEntry to the list.
    764      */
    765     mEntries.add(pEntry);
    766     if (ppEntry != NULL)
    767         *ppEntry = pEntry;
    768     pEntry = NULL;
    769 
    770     result = NO_ERROR;
    771 
    772 bail:
    773     delete pEntry;
    774     return result;
    775 }
    776 
    777 /*
    778  * Copy all of the bytes in "src" to "dst".
    779  *
    780  * On exit, "srcFp" will be seeked to the end of the file, and "dstFp"
    781  * will be seeked immediately past the data.
    782  */
    783 status_t ZipFile::copyFpToFp(FILE* dstFp, FILE* srcFp, unsigned long* pCRC32)
    784 {
    785     unsigned char tmpBuf[32768];
    786     size_t count;
    787 
    788     *pCRC32 = crc32(0L, Z_NULL, 0);
    789 
    790     while (1) {
    791         count = fread(tmpBuf, 1, sizeof(tmpBuf), srcFp);
    792         if (ferror(srcFp) || ferror(dstFp))
    793             return errnoToStatus(errno);
    794         if (count == 0)
    795             break;
    796 
    797         *pCRC32 = crc32(*pCRC32, tmpBuf, count);
    798 
    799         if (fwrite(tmpBuf, 1, count, dstFp) != count) {
    800             ALOGD("fwrite %d bytes failed\n", (int) count);
    801             return UNKNOWN_ERROR;
    802         }
    803     }
    804 
    805     return NO_ERROR;
    806 }
    807 
    808 /*
    809  * Copy all of the bytes in "src" to "dst".
    810  *
    811  * On exit, "dstFp" will be seeked immediately past the data.
    812  */
    813 status_t ZipFile::copyDataToFp(FILE* dstFp,
    814     const void* data, size_t size, unsigned long* pCRC32)
    815 {
    816     size_t count;
    817 
    818     *pCRC32 = crc32(0L, Z_NULL, 0);
    819     if (size > 0) {
    820         *pCRC32 = crc32(*pCRC32, (const unsigned char*)data, size);
    821         if (fwrite(data, 1, size, dstFp) != size) {
    822             ALOGD("fwrite %d bytes failed\n", (int) size);
    823             return UNKNOWN_ERROR;
    824         }
    825     }
    826 
    827     return NO_ERROR;
    828 }
    829 
    830 /*
    831  * Copy some of the bytes in "src" to "dst".
    832  *
    833  * If "pCRC32" is NULL, the CRC will not be computed.
    834  *
    835  * On exit, "srcFp" will be seeked to the end of the file, and "dstFp"
    836  * will be seeked immediately past the data just written.
    837  */
    838 status_t ZipFile::copyPartialFpToFp(FILE* dstFp, FILE* srcFp, long length,
    839     unsigned long* pCRC32)
    840 {
    841     unsigned char tmpBuf[32768];
    842     size_t count;
    843 
    844     if (pCRC32 != NULL)
    845         *pCRC32 = crc32(0L, Z_NULL, 0);
    846 
    847     while (length) {
    848         long readSize;
    849 
    850         readSize = sizeof(tmpBuf);
    851         if (readSize > length)
    852             readSize = length;
    853 
    854         count = fread(tmpBuf, 1, readSize, srcFp);
    855         if ((long) count != readSize) {     // error or unexpected EOF
    856             ALOGD("fread %d bytes failed\n", (int) readSize);
    857             return UNKNOWN_ERROR;
    858         }
    859 
    860         if (pCRC32 != NULL)
    861             *pCRC32 = crc32(*pCRC32, tmpBuf, count);
    862 
    863         if (fwrite(tmpBuf, 1, count, dstFp) != count) {
    864             ALOGD("fwrite %d bytes failed\n", (int) count);
    865             return UNKNOWN_ERROR;
    866         }
    867 
    868         length -= readSize;
    869     }
    870 
    871     return NO_ERROR;
    872 }
    873 
    874 /*
    875  * Compress all of the data in "srcFp" and write it to "dstFp".
    876  *
    877  * On exit, "srcFp" will be seeked to the end of the file, and "dstFp"
    878  * will be seeked immediately past the compressed data.
    879  */
    880 status_t ZipFile::compressFpToFp(FILE* dstFp, FILE* srcFp,
    881     const void* data, size_t size, unsigned long* pCRC32)
    882 {
    883     status_t result = NO_ERROR;
    884     const size_t kBufSize = 1024 * 1024;
    885     unsigned char* inBuf = NULL;
    886     unsigned char* outBuf = NULL;
    887     size_t outSize = 0;
    888     bool atEof = false;     // no feof() aviailable yet
    889     unsigned long crc;
    890     ZopfliOptions options;
    891     unsigned char bp = 0;
    892 
    893     ZopfliInitOptions(&options);
    894 
    895     crc = crc32(0L, Z_NULL, 0);
    896 
    897     if (data) {
    898         crc = crc32(crc, (const unsigned char*)data, size);
    899         ZopfliDeflate(&options, 2, true, (const unsigned char*)data, size, &bp,
    900             &outBuf, &outSize);
    901     } else {
    902         /*
    903          * Create an input buffer and an output buffer.
    904          */
    905         inBuf = new unsigned char[kBufSize];
    906         if (inBuf == NULL) {
    907             result = NO_MEMORY;
    908             goto bail;
    909         }
    910 
    911         /*
    912          * Loop while we have data.
    913          */
    914         do {
    915             size_t getSize;
    916             getSize = fread(inBuf, 1, kBufSize, srcFp);
    917             if (ferror(srcFp)) {
    918                 ALOGD("deflate read failed (errno=%d)\n", errno);
    919                 delete[] inBuf;
    920                 goto bail;
    921             }
    922             if (getSize < kBufSize) {
    923                 ALOGV("+++  got %d bytes, EOF reached\n",
    924                     (int)getSize);
    925                 atEof = true;
    926             }
    927 
    928             crc = crc32(crc, inBuf, getSize);
    929             ZopfliDeflate(&options, 2, atEof, inBuf, getSize, &bp, &outBuf, &outSize);
    930         } while (!atEof);
    931         delete[] inBuf;
    932     }
    933 
    934     ALOGV("+++ writing %d bytes\n", (int)outSize);
    935     if (fwrite(outBuf, 1, outSize, dstFp) != outSize) {
    936         ALOGD("write %d failed in deflate\n", (int)outSize);
    937         goto bail;
    938     }
    939 
    940     *pCRC32 = crc;
    941 
    942 bail:
    943     free(outBuf);
    944 
    945     return result;
    946 }
    947 
    948 /*
    949  * Mark an entry as deleted.
    950  *
    951  * We will eventually need to crunch the file down, but if several files
    952  * are being removed (perhaps as part of an "update" process) we can make
    953  * things considerably faster by deferring the removal to "flush" time.
    954  */
    955 status_t ZipFile::remove(ZipEntry* pEntry)
    956 {
    957     /*
    958      * Should verify that pEntry is actually part of this archive, and
    959      * not some stray ZipEntry from a different file.
    960      */
    961 
    962     /* mark entry as deleted, and mark archive as dirty */
    963     pEntry->setDeleted();
    964     mNeedCDRewrite = true;
    965     return NO_ERROR;
    966 }
    967 
    968 /*
    969  * Flush any pending writes.
    970  *
    971  * In particular, this will crunch out deleted entries, and write the
    972  * Central Directory and EOCD if we have stomped on them.
    973  */
    974 status_t ZipFile::flush(void)
    975 {
    976     status_t result = NO_ERROR;
    977     long eocdPosn;
    978     int i, count;
    979 
    980     if (mReadOnly)
    981         return INVALID_OPERATION;
    982     if (!mNeedCDRewrite)
    983         return NO_ERROR;
    984 
    985     assert(mZipFp != NULL);
    986 
    987     result = crunchArchive();
    988     if (result != NO_ERROR)
    989         return result;
    990 
    991     if (fseek(mZipFp, mEOCD.mCentralDirOffset, SEEK_SET) != 0)
    992         return UNKNOWN_ERROR;
    993 
    994     count = mEntries.size();
    995     for (i = 0; i < count; i++) {
    996         ZipEntry* pEntry = mEntries[i];
    997         pEntry->mCDE.write(mZipFp);
    998     }
    999 
   1000     eocdPosn = ftell(mZipFp);
   1001     mEOCD.mCentralDirSize = eocdPosn - mEOCD.mCentralDirOffset;
   1002 
   1003     mEOCD.write(mZipFp);
   1004 
   1005     /*
   1006      * If we had some stuff bloat up during compression and get replaced
   1007      * with plain files, or if we deleted some entries, there's a lot
   1008      * of wasted space at the end of the file.  Remove it now.
   1009      */
   1010     if (ftruncate(fileno(mZipFp), ftell(mZipFp)) != 0) {
   1011         ALOGW("ftruncate failed %ld: %s\n", ftell(mZipFp), strerror(errno));
   1012         // not fatal
   1013     }
   1014 
   1015     /* should we clear the "newly added" flag in all entries now? */
   1016 
   1017     mNeedCDRewrite = false;
   1018     return NO_ERROR;
   1019 }
   1020 
   1021 /*
   1022  * Crunch deleted files out of an archive by shifting the later files down.
   1023  *
   1024  * Because we're not using a temp file, we do the operation inside the
   1025  * current file.
   1026  */
   1027 status_t ZipFile::crunchArchive(void)
   1028 {
   1029     status_t result = NO_ERROR;
   1030     int i, count;
   1031     long delCount, adjust;
   1032 
   1033 #if 0
   1034     printf("CONTENTS:\n");
   1035     for (i = 0; i < (int) mEntries.size(); i++) {
   1036         printf(" %d: lfhOff=%ld del=%d\n",
   1037             i, mEntries[i]->getLFHOffset(), mEntries[i]->getDeleted());
   1038     }
   1039     printf("  END is %ld\n", (long) mEOCD.mCentralDirOffset);
   1040 #endif
   1041 
   1042     /*
   1043      * Roll through the set of files, shifting them as appropriate.  We
   1044      * could probably get a slight performance improvement by sliding
   1045      * multiple files down at once (because we could use larger reads
   1046      * when operating on batches of small files), but it's not that useful.
   1047      */
   1048     count = mEntries.size();
   1049     delCount = adjust = 0;
   1050     for (i = 0; i < count; i++) {
   1051         ZipEntry* pEntry = mEntries[i];
   1052         long span;
   1053 
   1054         if (pEntry->getLFHOffset() != 0) {
   1055             long nextOffset;
   1056 
   1057             /* Get the length of this entry by finding the offset
   1058              * of the next entry.  Directory entries don't have
   1059              * file offsets, so we need to find the next non-directory
   1060              * entry.
   1061              */
   1062             nextOffset = 0;
   1063             for (int ii = i+1; nextOffset == 0 && ii < count; ii++)
   1064                 nextOffset = mEntries[ii]->getLFHOffset();
   1065             if (nextOffset == 0)
   1066                 nextOffset = mEOCD.mCentralDirOffset;
   1067             span = nextOffset - pEntry->getLFHOffset();
   1068 
   1069             assert(span >= ZipEntry::LocalFileHeader::kLFHLen);
   1070         } else {
   1071             /* This is a directory entry.  It doesn't have
   1072              * any actual file contents, so there's no need to
   1073              * move anything.
   1074              */
   1075             span = 0;
   1076         }
   1077 
   1078         //printf("+++ %d: off=%ld span=%ld del=%d [count=%d]\n",
   1079         //    i, pEntry->getLFHOffset(), span, pEntry->getDeleted(), count);
   1080 
   1081         if (pEntry->getDeleted()) {
   1082             adjust += span;
   1083             delCount++;
   1084 
   1085             delete pEntry;
   1086             mEntries.removeAt(i);
   1087 
   1088             /* adjust loop control */
   1089             count--;
   1090             i--;
   1091         } else if (span != 0 && adjust > 0) {
   1092             /* shuffle this entry back */
   1093             //printf("+++ Shuffling '%s' back %ld\n",
   1094             //    pEntry->getFileName(), adjust);
   1095             result = filemove(mZipFp, pEntry->getLFHOffset() - adjust,
   1096                         pEntry->getLFHOffset(), span);
   1097             if (result != NO_ERROR) {
   1098                 /* this is why you use a temp file */
   1099                 ALOGE("error during crunch - archive is toast\n");
   1100                 return result;
   1101             }
   1102 
   1103             pEntry->setLFHOffset(pEntry->getLFHOffset() - adjust);
   1104         }
   1105     }
   1106 
   1107     /*
   1108      * Fix EOCD info.  We have to wait until the end to do some of this
   1109      * because we use mCentralDirOffset to determine "span" for the
   1110      * last entry.
   1111      */
   1112     mEOCD.mCentralDirOffset -= adjust;
   1113     mEOCD.mNumEntries -= delCount;
   1114     mEOCD.mTotalNumEntries -= delCount;
   1115     mEOCD.mCentralDirSize = 0;  // mark invalid; set by flush()
   1116 
   1117     assert(mEOCD.mNumEntries == mEOCD.mTotalNumEntries);
   1118     assert(mEOCD.mNumEntries == count);
   1119 
   1120     return result;
   1121 }
   1122 
   1123 /*
   1124  * Works like memmove(), but on pieces of a file.
   1125  */
   1126 status_t ZipFile::filemove(FILE* fp, off_t dst, off_t src, size_t n)
   1127 {
   1128     if (dst == src || n <= 0)
   1129         return NO_ERROR;
   1130 
   1131     unsigned char readBuf[32768];
   1132 
   1133     if (dst < src) {
   1134         /* shift stuff toward start of file; must read from start */
   1135         while (n != 0) {
   1136             size_t getSize = sizeof(readBuf);
   1137             if (getSize > n)
   1138                 getSize = n;
   1139 
   1140             if (fseek(fp, (long) src, SEEK_SET) != 0) {
   1141                 ALOGD("filemove src seek %ld failed\n", (long) src);
   1142                 return UNKNOWN_ERROR;
   1143             }
   1144 
   1145             if (fread(readBuf, 1, getSize, fp) != getSize) {
   1146                 ALOGD("filemove read %ld off=%ld failed\n",
   1147                     (long) getSize, (long) src);
   1148                 return UNKNOWN_ERROR;
   1149             }
   1150 
   1151             if (fseek(fp, (long) dst, SEEK_SET) != 0) {
   1152                 ALOGD("filemove dst seek %ld failed\n", (long) dst);
   1153                 return UNKNOWN_ERROR;
   1154             }
   1155 
   1156             if (fwrite(readBuf, 1, getSize, fp) != getSize) {
   1157                 ALOGD("filemove write %ld off=%ld failed\n",
   1158                     (long) getSize, (long) dst);
   1159                 return UNKNOWN_ERROR;
   1160             }
   1161 
   1162             src += getSize;
   1163             dst += getSize;
   1164             n -= getSize;
   1165         }
   1166     } else {
   1167         /* shift stuff toward end of file; must read from end */
   1168         assert(false);      // write this someday, maybe
   1169         return UNKNOWN_ERROR;
   1170     }
   1171 
   1172     return NO_ERROR;
   1173 }
   1174 
   1175 
   1176 /*
   1177  * Get the modification time from a file descriptor.
   1178  */
   1179 time_t ZipFile::getModTime(int fd)
   1180 {
   1181     struct stat sb;
   1182 
   1183     if (fstat(fd, &sb) < 0) {
   1184         ALOGD("HEY: fstat on fd %d failed\n", fd);
   1185         return (time_t) -1;
   1186     }
   1187 
   1188     return sb.st_mtime;
   1189 }
   1190 
   1191 
   1192 #if 0       /* this is a bad idea */
   1193 /*
   1194  * Get a copy of the Zip file descriptor.
   1195  *
   1196  * We don't allow this if the file was opened read-write because we tend
   1197  * to leave the file contents in an uncertain state between calls to
   1198  * flush().  The duplicated file descriptor should only be valid for reads.
   1199  */
   1200 int ZipFile::getZipFd(void) const
   1201 {
   1202     if (!mReadOnly)
   1203         return INVALID_OPERATION;
   1204     assert(mZipFp != NULL);
   1205 
   1206     int fd;
   1207     fd = dup(fileno(mZipFp));
   1208     if (fd < 0) {
   1209         ALOGD("didn't work, errno=%d\n", errno);
   1210     }
   1211 
   1212     return fd;
   1213 }
   1214 #endif
   1215 
   1216 
   1217 #if 0
   1218 /*
   1219  * Expand data.
   1220  */
   1221 bool ZipFile::uncompress(const ZipEntry* pEntry, void* buf) const
   1222 {
   1223     return false;
   1224 }
   1225 #endif
   1226 
   1227 // free the memory when you're done
   1228 void* ZipFile::uncompress(const ZipEntry* entry) const
   1229 {
   1230     size_t unlen = entry->getUncompressedLen();
   1231     size_t clen = entry->getCompressedLen();
   1232 
   1233     void* buf = malloc(unlen);
   1234     if (buf == NULL) {
   1235         return NULL;
   1236     }
   1237 
   1238     fseek(mZipFp, 0, SEEK_SET);
   1239 
   1240     off_t offset = entry->getFileOffset();
   1241     if (fseek(mZipFp, offset, SEEK_SET) != 0) {
   1242         goto bail;
   1243     }
   1244 
   1245     switch (entry->getCompressionMethod())
   1246     {
   1247         case ZipEntry::kCompressStored: {
   1248             ssize_t amt = fread(buf, 1, unlen, mZipFp);
   1249             if (amt != (ssize_t)unlen) {
   1250                 goto bail;
   1251             }
   1252 #if 0
   1253             printf("data...\n");
   1254             const unsigned char* p = (unsigned char*)buf;
   1255             const unsigned char* end = p+unlen;
   1256             for (int i=0; i<32 && p < end; i++) {
   1257                 printf("0x%08x ", (int)(offset+(i*0x10)));
   1258                 for (int j=0; j<0x10 && p < end; j++) {
   1259                     printf(" %02x", *p);
   1260                     p++;
   1261                 }
   1262                 printf("\n");
   1263             }
   1264 #endif
   1265 
   1266             }
   1267             break;
   1268         case ZipEntry::kCompressDeflated: {
   1269             if (!ZipUtils::inflateToBuffer(mZipFp, buf, unlen, clen)) {
   1270                 goto bail;
   1271             }
   1272             }
   1273             break;
   1274         default:
   1275             goto bail;
   1276     }
   1277     return buf;
   1278 
   1279 bail:
   1280     free(buf);
   1281     return NULL;
   1282 }
   1283 
   1284 
   1285 /*
   1286  * ===========================================================================
   1287  *      ZipFile::EndOfCentralDir
   1288  * ===========================================================================
   1289  */
   1290 
   1291 /*
   1292  * Read the end-of-central-dir fields.
   1293  *
   1294  * "buf" should be positioned at the EOCD signature, and should contain
   1295  * the entire EOCD area including the comment.
   1296  */
   1297 status_t ZipFile::EndOfCentralDir::readBuf(const unsigned char* buf, int len)
   1298 {
   1299     /* don't allow re-use */
   1300     assert(mComment == NULL);
   1301 
   1302     if (len < kEOCDLen) {
   1303         /* looks like ZIP file got truncated */
   1304         ALOGD(" Zip EOCD: expected >= %d bytes, found %d\n",
   1305             kEOCDLen, len);
   1306         return INVALID_OPERATION;
   1307     }
   1308 
   1309     /* this should probably be an assert() */
   1310     if (ZipEntry::getLongLE(&buf[0x00]) != kSignature)
   1311         return UNKNOWN_ERROR;
   1312 
   1313     mDiskNumber = ZipEntry::getShortLE(&buf[0x04]);
   1314     mDiskWithCentralDir = ZipEntry::getShortLE(&buf[0x06]);
   1315     mNumEntries = ZipEntry::getShortLE(&buf[0x08]);
   1316     mTotalNumEntries = ZipEntry::getShortLE(&buf[0x0a]);
   1317     mCentralDirSize = ZipEntry::getLongLE(&buf[0x0c]);
   1318     mCentralDirOffset = ZipEntry::getLongLE(&buf[0x10]);
   1319     mCommentLen = ZipEntry::getShortLE(&buf[0x14]);
   1320 
   1321     // TODO: validate mCentralDirOffset
   1322 
   1323     if (mCommentLen > 0) {
   1324         if (kEOCDLen + mCommentLen > len) {
   1325             ALOGD("EOCD(%d) + comment(%d) exceeds len (%d)\n",
   1326                 kEOCDLen, mCommentLen, len);
   1327             return UNKNOWN_ERROR;
   1328         }
   1329         mComment = new unsigned char[mCommentLen];
   1330         memcpy(mComment, buf + kEOCDLen, mCommentLen);
   1331     }
   1332 
   1333     return NO_ERROR;
   1334 }
   1335 
   1336 /*
   1337  * Write an end-of-central-directory section.
   1338  */
   1339 status_t ZipFile::EndOfCentralDir::write(FILE* fp)
   1340 {
   1341     unsigned char buf[kEOCDLen];
   1342 
   1343     ZipEntry::putLongLE(&buf[0x00], kSignature);
   1344     ZipEntry::putShortLE(&buf[0x04], mDiskNumber);
   1345     ZipEntry::putShortLE(&buf[0x06], mDiskWithCentralDir);
   1346     ZipEntry::putShortLE(&buf[0x08], mNumEntries);
   1347     ZipEntry::putShortLE(&buf[0x0a], mTotalNumEntries);
   1348     ZipEntry::putLongLE(&buf[0x0c], mCentralDirSize);
   1349     ZipEntry::putLongLE(&buf[0x10], mCentralDirOffset);
   1350     ZipEntry::putShortLE(&buf[0x14], mCommentLen);
   1351 
   1352     if (fwrite(buf, 1, kEOCDLen, fp) != kEOCDLen)
   1353         return UNKNOWN_ERROR;
   1354     if (mCommentLen > 0) {
   1355         assert(mComment != NULL);
   1356         if (fwrite(mComment, mCommentLen, 1, fp) != mCommentLen)
   1357             return UNKNOWN_ERROR;
   1358     }
   1359 
   1360     return NO_ERROR;
   1361 }
   1362 
   1363 /*
   1364  * Dump the contents of an EndOfCentralDir object.
   1365  */
   1366 void ZipFile::EndOfCentralDir::dump(void) const
   1367 {
   1368     ALOGD(" EndOfCentralDir contents:\n");
   1369     ALOGD("  diskNum=%u diskWCD=%u numEnt=%u totalNumEnt=%u\n",
   1370         mDiskNumber, mDiskWithCentralDir, mNumEntries, mTotalNumEntries);
   1371     ALOGD("  centDirSize=%lu centDirOff=%lu commentLen=%u\n",
   1372         mCentralDirSize, mCentralDirOffset, mCommentLen);
   1373 }
   1374 
   1375