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// Provide access to a read-only asset.
19//
20
21#define LOG_TAG "asset"
22//#define NDEBUG 0
23
24#include <androidfw/Asset.h>
25#include <androidfw/StreamingZipInflater.h>
26#include <androidfw/Util.h>
27#include <androidfw/ZipFileRO.h>
28#include <androidfw/ZipUtils.h>
29#include <utils/Atomic.h>
30#include <utils/FileMap.h>
31#include <utils/Log.h>
32#include <utils/threads.h>
33
34#include <assert.h>
35#include <errno.h>
36#include <fcntl.h>
37#include <memory.h>
38#include <string.h>
39#include <sys/stat.h>
40#include <sys/types.h>
41#include <unistd.h>
42
43using namespace android;
44
45#ifndef O_BINARY
46# define O_BINARY 0
47#endif
48
49static const bool kIsDebug = false;
50
51static Mutex gAssetLock;
52static int32_t gCount = 0;
53static Asset* gHead = NULL;
54static Asset* gTail = NULL;
55
56void Asset::registerAsset(Asset* asset)
57{
58    AutoMutex _l(gAssetLock);
59    gCount++;
60    asset->mNext = asset->mPrev = NULL;
61    if (gTail == NULL) {
62        gHead = gTail = asset;
63    } else {
64        asset->mPrev = gTail;
65        gTail->mNext = asset;
66        gTail = asset;
67    }
68
69    if (kIsDebug) {
70        ALOGI("Creating Asset %p #%d\n", asset, gCount);
71    }
72}
73
74void Asset::unregisterAsset(Asset* asset)
75{
76    AutoMutex _l(gAssetLock);
77    gCount--;
78    if (gHead == asset) {
79        gHead = asset->mNext;
80    }
81    if (gTail == asset) {
82        gTail = asset->mPrev;
83    }
84    if (asset->mNext != NULL) {
85        asset->mNext->mPrev = asset->mPrev;
86    }
87    if (asset->mPrev != NULL) {
88        asset->mPrev->mNext = asset->mNext;
89    }
90    asset->mNext = asset->mPrev = NULL;
91
92    if (kIsDebug) {
93        ALOGI("Destroying Asset in %p #%d\n", asset, gCount);
94    }
95}
96
97int32_t Asset::getGlobalCount()
98{
99    AutoMutex _l(gAssetLock);
100    return gCount;
101}
102
103String8 Asset::getAssetAllocations()
104{
105    AutoMutex _l(gAssetLock);
106    String8 res;
107    Asset* cur = gHead;
108    while (cur != NULL) {
109        if (cur->isAllocated()) {
110            res.append("    ");
111            res.append(cur->getAssetSource());
112            off64_t size = (cur->getLength()+512)/1024;
113            char buf[64];
114            snprintf(buf, sizeof(buf), ": %dK\n", (int)size);
115            res.append(buf);
116        }
117        cur = cur->mNext;
118    }
119
120    return res;
121}
122
123Asset::Asset(void)
124    : mAccessMode(ACCESS_UNKNOWN), mNext(NULL), mPrev(NULL)
125{
126}
127
128/*
129 * Create a new Asset from a file on disk.  There is a fair chance that
130 * the file doesn't actually exist.
131 *
132 * We can use "mode" to decide how we want to go about it.
133 */
134/*static*/ Asset* Asset::createFromFile(const char* fileName, AccessMode mode)
135{
136    _FileAsset* pAsset;
137    status_t result;
138    off64_t length;
139    int fd;
140
141    fd = open(fileName, O_RDONLY | O_BINARY);
142    if (fd < 0)
143        return NULL;
144
145    /*
146     * Under Linux, the lseek fails if we actually opened a directory.  To
147     * be correct we should test the file type explicitly, but since we
148     * always open things read-only it doesn't really matter, so there's
149     * no value in incurring the extra overhead of an fstat() call.
150     */
151    // TODO(kroot): replace this with fstat despite the plea above.
152#if 1
153    length = lseek64(fd, 0, SEEK_END);
154    if (length < 0) {
155        ::close(fd);
156        return NULL;
157    }
158    (void) lseek64(fd, 0, SEEK_SET);
159#else
160    struct stat st;
161    if (fstat(fd, &st) < 0) {
162        ::close(fd);
163        return NULL;
164    }
165
166    if (!S_ISREG(st.st_mode)) {
167        ::close(fd);
168        return NULL;
169    }
170#endif
171
172    pAsset = new _FileAsset;
173    result = pAsset->openChunk(fileName, fd, 0, length);
174    if (result != NO_ERROR) {
175        delete pAsset;
176        return NULL;
177    }
178
179    pAsset->mAccessMode = mode;
180    return pAsset;
181}
182
183
184/*
185 * Create a new Asset from a compressed file on disk.  There is a fair chance
186 * that the file doesn't actually exist.
187 *
188 * We currently support gzip files.  We might want to handle .bz2 someday.
189 */
190/*static*/ Asset* Asset::createFromCompressedFile(const char* fileName,
191    AccessMode mode)
192{
193    _CompressedAsset* pAsset;
194    status_t result;
195    off64_t fileLen;
196    bool scanResult;
197    long offset;
198    int method;
199    long uncompressedLen, compressedLen;
200    int fd;
201
202    fd = open(fileName, O_RDONLY | O_BINARY);
203    if (fd < 0)
204        return NULL;
205
206    fileLen = lseek(fd, 0, SEEK_END);
207    if (fileLen < 0) {
208        ::close(fd);
209        return NULL;
210    }
211    (void) lseek(fd, 0, SEEK_SET);
212
213    /* want buffered I/O for the file scan; must dup so fclose() is safe */
214    FILE* fp = fdopen(dup(fd), "rb");
215    if (fp == NULL) {
216        ::close(fd);
217        return NULL;
218    }
219
220    unsigned long crc32;
221    scanResult = ZipUtils::examineGzip(fp, &method, &uncompressedLen,
222                    &compressedLen, &crc32);
223    offset = ftell(fp);
224    fclose(fp);
225    if (!scanResult) {
226        ALOGD("File '%s' is not in gzip format\n", fileName);
227        ::close(fd);
228        return NULL;
229    }
230
231    pAsset = new _CompressedAsset;
232    result = pAsset->openChunk(fd, offset, method, uncompressedLen,
233                compressedLen);
234    if (result != NO_ERROR) {
235        delete pAsset;
236        return NULL;
237    }
238
239    pAsset->mAccessMode = mode;
240    return pAsset;
241}
242
243
244#if 0
245/*
246 * Create a new Asset from part of an open file.
247 */
248/*static*/ Asset* Asset::createFromFileSegment(int fd, off64_t offset,
249    size_t length, AccessMode mode)
250{
251    _FileAsset* pAsset;
252    status_t result;
253
254    pAsset = new _FileAsset;
255    result = pAsset->openChunk(NULL, fd, offset, length);
256    if (result != NO_ERROR)
257        return NULL;
258
259    pAsset->mAccessMode = mode;
260    return pAsset;
261}
262
263/*
264 * Create a new Asset from compressed data in an open file.
265 */
266/*static*/ Asset* Asset::createFromCompressedData(int fd, off64_t offset,
267    int compressionMethod, size_t uncompressedLen, size_t compressedLen,
268    AccessMode mode)
269{
270    _CompressedAsset* pAsset;
271    status_t result;
272
273    pAsset = new _CompressedAsset;
274    result = pAsset->openChunk(fd, offset, compressionMethod,
275                uncompressedLen, compressedLen);
276    if (result != NO_ERROR)
277        return NULL;
278
279    pAsset->mAccessMode = mode;
280    return pAsset;
281}
282#endif
283
284/*
285 * Create a new Asset from a memory mapping.
286 */
287/*static*/ Asset* Asset::createFromUncompressedMap(FileMap* dataMap,
288    AccessMode mode)
289{
290    _FileAsset* pAsset;
291    status_t result;
292
293    pAsset = new _FileAsset;
294    result = pAsset->openChunk(dataMap);
295    if (result != NO_ERROR)
296        return NULL;
297
298    pAsset->mAccessMode = mode;
299    return pAsset;
300}
301
302/*static*/ std::unique_ptr<Asset> Asset::createFromUncompressedMap(std::unique_ptr<FileMap> dataMap,
303    AccessMode mode)
304{
305    std::unique_ptr<_FileAsset> pAsset = util::make_unique<_FileAsset>();
306
307    status_t result = pAsset->openChunk(dataMap.get());
308    if (result != NO_ERROR) {
309        return NULL;
310    }
311
312    // We succeeded, so relinquish control of dataMap
313    (void) dataMap.release();
314    pAsset->mAccessMode = mode;
315    return std::move(pAsset);
316}
317
318/*
319 * Create a new Asset from compressed data in a memory mapping.
320 */
321/*static*/ Asset* Asset::createFromCompressedMap(FileMap* dataMap,
322    size_t uncompressedLen, AccessMode mode)
323{
324    _CompressedAsset* pAsset;
325    status_t result;
326
327    pAsset = new _CompressedAsset;
328    result = pAsset->openChunk(dataMap, uncompressedLen);
329    if (result != NO_ERROR)
330        return NULL;
331
332    pAsset->mAccessMode = mode;
333    return pAsset;
334}
335
336/*static*/ std::unique_ptr<Asset> Asset::createFromCompressedMap(std::unique_ptr<FileMap> dataMap,
337    size_t uncompressedLen, AccessMode mode)
338{
339  std::unique_ptr<_CompressedAsset> pAsset = util::make_unique<_CompressedAsset>();
340
341  status_t result = pAsset->openChunk(dataMap.get(), uncompressedLen);
342  if (result != NO_ERROR) {
343      return NULL;
344  }
345
346  // We succeeded, so relinquish control of dataMap
347  (void) dataMap.release();
348  pAsset->mAccessMode = mode;
349  return std::move(pAsset);
350}
351
352/*
353 * Do generic seek() housekeeping.  Pass in the offset/whence values from
354 * the seek request, along with the current chunk offset and the chunk
355 * length.
356 *
357 * Returns the new chunk offset, or -1 if the seek is illegal.
358 */
359off64_t Asset::handleSeek(off64_t offset, int whence, off64_t curPosn, off64_t maxPosn)
360{
361    off64_t newOffset;
362
363    switch (whence) {
364    case SEEK_SET:
365        newOffset = offset;
366        break;
367    case SEEK_CUR:
368        newOffset = curPosn + offset;
369        break;
370    case SEEK_END:
371        newOffset = maxPosn + offset;
372        break;
373    default:
374        ALOGW("unexpected whence %d\n", whence);
375        // this was happening due to an off64_t size mismatch
376        assert(false);
377        return (off64_t) -1;
378    }
379
380    if (newOffset < 0 || newOffset > maxPosn) {
381        ALOGW("seek out of range: want %ld, end=%ld\n",
382            (long) newOffset, (long) maxPosn);
383        return (off64_t) -1;
384    }
385
386    return newOffset;
387}
388
389
390/*
391 * ===========================================================================
392 *      _FileAsset
393 * ===========================================================================
394 */
395
396/*
397 * Constructor.
398 */
399_FileAsset::_FileAsset(void)
400    : mStart(0), mLength(0), mOffset(0), mFp(NULL), mFileName(NULL), mMap(NULL), mBuf(NULL)
401{
402    // Register the Asset with the global list here after it is fully constructed and its
403    // vtable pointer points to this concrete type. b/31113965
404    registerAsset(this);
405}
406
407/*
408 * Destructor.  Release resources.
409 */
410_FileAsset::~_FileAsset(void)
411{
412    close();
413
414    // Unregister the Asset from the global list here before it is destructed and while its vtable
415    // pointer still points to this concrete type. b/31113965
416    unregisterAsset(this);
417}
418
419/*
420 * Operate on a chunk of an uncompressed file.
421 *
422 * Zero-length chunks are allowed.
423 */
424status_t _FileAsset::openChunk(const char* fileName, int fd, off64_t offset, size_t length)
425{
426    assert(mFp == NULL);    // no reopen
427    assert(mMap == NULL);
428    assert(fd >= 0);
429    assert(offset >= 0);
430
431    /*
432     * Seek to end to get file length.
433     */
434    off64_t fileLength;
435    fileLength = lseek64(fd, 0, SEEK_END);
436    if (fileLength == (off64_t) -1) {
437        // probably a bad file descriptor
438        ALOGD("failed lseek (errno=%d)\n", errno);
439        return UNKNOWN_ERROR;
440    }
441
442    if ((off64_t) (offset + length) > fileLength) {
443        ALOGD("start (%ld) + len (%ld) > end (%ld)\n",
444            (long) offset, (long) length, (long) fileLength);
445        return BAD_INDEX;
446    }
447
448    /* after fdopen, the fd will be closed on fclose() */
449    mFp = fdopen(fd, "rb");
450    if (mFp == NULL)
451        return UNKNOWN_ERROR;
452
453    mStart = offset;
454    mLength = length;
455    assert(mOffset == 0);
456
457    /* seek the FILE* to the start of chunk */
458    if (fseek(mFp, mStart, SEEK_SET) != 0) {
459        assert(false);
460    }
461
462    mFileName = fileName != NULL ? strdup(fileName) : NULL;
463
464    return NO_ERROR;
465}
466
467/*
468 * Create the chunk from the map.
469 */
470status_t _FileAsset::openChunk(FileMap* dataMap)
471{
472    assert(mFp == NULL);    // no reopen
473    assert(mMap == NULL);
474    assert(dataMap != NULL);
475
476    mMap = dataMap;
477    mStart = -1;            // not used
478    mLength = dataMap->getDataLength();
479    assert(mOffset == 0);
480
481    return NO_ERROR;
482}
483
484/*
485 * Read a chunk of data.
486 */
487ssize_t _FileAsset::read(void* buf, size_t count)
488{
489    size_t maxLen;
490    size_t actual;
491
492    assert(mOffset >= 0 && mOffset <= mLength);
493
494    if (getAccessMode() == ACCESS_BUFFER) {
495        /*
496         * On first access, read or map the entire file.  The caller has
497         * requested buffer access, either because they're going to be
498         * using the buffer or because what they're doing has appropriate
499         * performance needs and access patterns.
500         */
501        if (mBuf == NULL)
502            getBuffer(false);
503    }
504
505    /* adjust count if we're near EOF */
506    maxLen = mLength - mOffset;
507    if (count > maxLen)
508        count = maxLen;
509
510    if (!count)
511        return 0;
512
513    if (mMap != NULL) {
514        /* copy from mapped area */
515        //printf("map read\n");
516        memcpy(buf, (char*)mMap->getDataPtr() + mOffset, count);
517        actual = count;
518    } else if (mBuf != NULL) {
519        /* copy from buffer */
520        //printf("buf read\n");
521        memcpy(buf, (char*)mBuf + mOffset, count);
522        actual = count;
523    } else {
524        /* read from the file */
525        //printf("file read\n");
526        if (ftell(mFp) != mStart + mOffset) {
527            ALOGE("Hosed: %ld != %ld+%ld\n",
528                ftell(mFp), (long) mStart, (long) mOffset);
529            assert(false);
530        }
531
532        /*
533         * This returns 0 on error or eof.  We need to use ferror() or feof()
534         * to tell the difference, but we don't currently have those on the
535         * device.  However, we know how much data is *supposed* to be in the
536         * file, so if we don't read the full amount we know something is
537         * hosed.
538         */
539        actual = fread(buf, 1, count, mFp);
540        if (actual == 0)        // something failed -- I/O error?
541            return -1;
542
543        assert(actual == count);
544    }
545
546    mOffset += actual;
547    return actual;
548}
549
550/*
551 * Seek to a new position.
552 */
553off64_t _FileAsset::seek(off64_t offset, int whence)
554{
555    off64_t newPosn;
556    off64_t actualOffset;
557
558    // compute new position within chunk
559    newPosn = handleSeek(offset, whence, mOffset, mLength);
560    if (newPosn == (off64_t) -1)
561        return newPosn;
562
563    actualOffset = mStart + newPosn;
564
565    if (mFp != NULL) {
566        if (fseek(mFp, (long) actualOffset, SEEK_SET) != 0)
567            return (off64_t) -1;
568    }
569
570    mOffset = actualOffset - mStart;
571    return mOffset;
572}
573
574/*
575 * Close the asset.
576 */
577void _FileAsset::close(void)
578{
579    if (mMap != NULL) {
580        delete mMap;
581        mMap = NULL;
582    }
583    if (mBuf != NULL) {
584        delete[] mBuf;
585        mBuf = NULL;
586    }
587
588    if (mFileName != NULL) {
589        free(mFileName);
590        mFileName = NULL;
591    }
592
593    if (mFp != NULL) {
594        // can only be NULL when called from destructor
595        // (otherwise we would never return this object)
596        fclose(mFp);
597        mFp = NULL;
598    }
599}
600
601/*
602 * Return a read-only pointer to a buffer.
603 *
604 * We can either read the whole thing in or map the relevant piece of
605 * the source file.  Ideally a map would be established at a higher
606 * level and we'd be using a different object, but we didn't, so we
607 * deal with it here.
608 */
609const void* _FileAsset::getBuffer(bool wordAligned)
610{
611    /* subsequent requests just use what we did previously */
612    if (mBuf != NULL)
613        return mBuf;
614    if (mMap != NULL) {
615        if (!wordAligned) {
616            return  mMap->getDataPtr();
617        }
618        return ensureAlignment(mMap);
619    }
620
621    assert(mFp != NULL);
622
623    if (mLength < kReadVsMapThreshold) {
624        unsigned char* buf;
625        long allocLen;
626
627        /* zero-length files are allowed; not sure about zero-len allocs */
628        /* (works fine with gcc + x86linux) */
629        allocLen = mLength;
630        if (mLength == 0)
631            allocLen = 1;
632
633        buf = new unsigned char[allocLen];
634        if (buf == NULL) {
635            ALOGE("alloc of %ld bytes failed\n", (long) allocLen);
636            return NULL;
637        }
638
639        ALOGV("Asset %p allocating buffer size %d (smaller than threshold)", this, (int)allocLen);
640        if (mLength > 0) {
641            long oldPosn = ftell(mFp);
642            fseek(mFp, mStart, SEEK_SET);
643            if (fread(buf, 1, mLength, mFp) != (size_t) mLength) {
644                ALOGE("failed reading %ld bytes\n", (long) mLength);
645                delete[] buf;
646                return NULL;
647            }
648            fseek(mFp, oldPosn, SEEK_SET);
649        }
650
651        ALOGV(" getBuffer: loaded into buffer\n");
652
653        mBuf = buf;
654        return mBuf;
655    } else {
656        FileMap* map;
657
658        map = new FileMap;
659        if (!map->create(NULL, fileno(mFp), mStart, mLength, true)) {
660            delete map;
661            return NULL;
662        }
663
664        ALOGV(" getBuffer: mapped\n");
665
666        mMap = map;
667        if (!wordAligned) {
668            return  mMap->getDataPtr();
669        }
670        return ensureAlignment(mMap);
671    }
672}
673
674int _FileAsset::openFileDescriptor(off64_t* outStart, off64_t* outLength) const
675{
676    if (mMap != NULL) {
677        const char* fname = mMap->getFileName();
678        if (fname == NULL) {
679            fname = mFileName;
680        }
681        if (fname == NULL) {
682            return -1;
683        }
684        *outStart = mMap->getDataOffset();
685        *outLength = mMap->getDataLength();
686        return open(fname, O_RDONLY | O_BINARY);
687    }
688    if (mFileName == NULL) {
689        return -1;
690    }
691    *outStart = mStart;
692    *outLength = mLength;
693    return open(mFileName, O_RDONLY | O_BINARY);
694}
695
696const void* _FileAsset::ensureAlignment(FileMap* map)
697{
698    void* data = map->getDataPtr();
699    if ((((size_t)data)&0x3) == 0) {
700        // We can return this directly if it is aligned on a word
701        // boundary.
702        ALOGV("Returning aligned FileAsset %p (%s).", this,
703                getAssetSource());
704        return data;
705    }
706    // If not aligned on a word boundary, then we need to copy it into
707    // our own buffer.
708    ALOGV("Copying FileAsset %p (%s) to buffer size %d to make it aligned.", this,
709            getAssetSource(), (int)mLength);
710    unsigned char* buf = new unsigned char[mLength];
711    if (buf == NULL) {
712        ALOGE("alloc of %ld bytes failed\n", (long) mLength);
713        return NULL;
714    }
715    memcpy(buf, data, mLength);
716    mBuf = buf;
717    return buf;
718}
719
720/*
721 * ===========================================================================
722 *      _CompressedAsset
723 * ===========================================================================
724 */
725
726/*
727 * Constructor.
728 */
729_CompressedAsset::_CompressedAsset(void)
730    : mStart(0), mCompressedLen(0), mUncompressedLen(0), mOffset(0),
731      mMap(NULL), mFd(-1), mZipInflater(NULL), mBuf(NULL)
732{
733    // Register the Asset with the global list here after it is fully constructed and its
734    // vtable pointer points to this concrete type. b/31113965
735    registerAsset(this);
736}
737
738/*
739 * Destructor.  Release resources.
740 */
741_CompressedAsset::~_CompressedAsset(void)
742{
743    close();
744
745    // Unregister the Asset from the global list here before it is destructed and while its vtable
746    // pointer still points to this concrete type. b/31113965
747    unregisterAsset(this);
748}
749
750/*
751 * Open a chunk of compressed data inside a file.
752 *
753 * This currently just sets up some values and returns.  On the first
754 * read, we expand the entire file into a buffer and return data from it.
755 */
756status_t _CompressedAsset::openChunk(int fd, off64_t offset,
757    int compressionMethod, size_t uncompressedLen, size_t compressedLen)
758{
759    assert(mFd < 0);        // no re-open
760    assert(mMap == NULL);
761    assert(fd >= 0);
762    assert(offset >= 0);
763    assert(compressedLen > 0);
764
765    if (compressionMethod != ZipFileRO::kCompressDeflated) {
766        assert(false);
767        return UNKNOWN_ERROR;
768    }
769
770    mStart = offset;
771    mCompressedLen = compressedLen;
772    mUncompressedLen = uncompressedLen;
773    assert(mOffset == 0);
774    mFd = fd;
775    assert(mBuf == NULL);
776
777    if (uncompressedLen > StreamingZipInflater::OUTPUT_CHUNK_SIZE) {
778        mZipInflater = new StreamingZipInflater(mFd, offset, uncompressedLen, compressedLen);
779    }
780
781    return NO_ERROR;
782}
783
784/*
785 * Open a chunk of compressed data in a mapped region.
786 *
787 * Nothing is expanded until the first read call.
788 */
789status_t _CompressedAsset::openChunk(FileMap* dataMap, size_t uncompressedLen)
790{
791    assert(mFd < 0);        // no re-open
792    assert(mMap == NULL);
793    assert(dataMap != NULL);
794
795    mMap = dataMap;
796    mStart = -1;        // not used
797    mCompressedLen = dataMap->getDataLength();
798    mUncompressedLen = uncompressedLen;
799    assert(mOffset == 0);
800
801    if (uncompressedLen > StreamingZipInflater::OUTPUT_CHUNK_SIZE) {
802        mZipInflater = new StreamingZipInflater(dataMap, uncompressedLen);
803    }
804    return NO_ERROR;
805}
806
807/*
808 * Read data from a chunk of compressed data.
809 *
810 * [For now, that's just copying data out of a buffer.]
811 */
812ssize_t _CompressedAsset::read(void* buf, size_t count)
813{
814    size_t maxLen;
815    size_t actual;
816
817    assert(mOffset >= 0 && mOffset <= mUncompressedLen);
818
819    /* If we're relying on a streaming inflater, go through that */
820    if (mZipInflater) {
821        actual = mZipInflater->read(buf, count);
822    } else {
823        if (mBuf == NULL) {
824            if (getBuffer(false) == NULL)
825                return -1;
826        }
827        assert(mBuf != NULL);
828
829        /* adjust count if we're near EOF */
830        maxLen = mUncompressedLen - mOffset;
831        if (count > maxLen)
832            count = maxLen;
833
834        if (!count)
835            return 0;
836
837        /* copy from buffer */
838        //printf("comp buf read\n");
839        memcpy(buf, (char*)mBuf + mOffset, count);
840        actual = count;
841    }
842
843    mOffset += actual;
844    return actual;
845}
846
847/*
848 * Handle a seek request.
849 *
850 * If we're working in a streaming mode, this is going to be fairly
851 * expensive, because it requires plowing through a bunch of compressed
852 * data.
853 */
854off64_t _CompressedAsset::seek(off64_t offset, int whence)
855{
856    off64_t newPosn;
857
858    // compute new position within chunk
859    newPosn = handleSeek(offset, whence, mOffset, mUncompressedLen);
860    if (newPosn == (off64_t) -1)
861        return newPosn;
862
863    if (mZipInflater) {
864        mZipInflater->seekAbsolute(newPosn);
865    }
866    mOffset = newPosn;
867    return mOffset;
868}
869
870/*
871 * Close the asset.
872 */
873void _CompressedAsset::close(void)
874{
875    if (mMap != NULL) {
876        delete mMap;
877        mMap = NULL;
878    }
879
880    delete[] mBuf;
881    mBuf = NULL;
882
883    delete mZipInflater;
884    mZipInflater = NULL;
885
886    if (mFd > 0) {
887        ::close(mFd);
888        mFd = -1;
889    }
890}
891
892/*
893 * Get a pointer to a read-only buffer of data.
894 *
895 * The first time this is called, we expand the compressed data into a
896 * buffer.
897 */
898const void* _CompressedAsset::getBuffer(bool)
899{
900    unsigned char* buf = NULL;
901
902    if (mBuf != NULL)
903        return mBuf;
904
905    /*
906     * Allocate a buffer and read the file into it.
907     */
908    buf = new unsigned char[mUncompressedLen];
909    if (buf == NULL) {
910        ALOGW("alloc %ld bytes failed\n", (long) mUncompressedLen);
911        goto bail;
912    }
913
914    if (mMap != NULL) {
915        if (!ZipUtils::inflateToBuffer(mMap->getDataPtr(), buf,
916                mUncompressedLen, mCompressedLen))
917            goto bail;
918    } else {
919        assert(mFd >= 0);
920
921        /*
922         * Seek to the start of the compressed data.
923         */
924        if (lseek(mFd, mStart, SEEK_SET) != mStart)
925            goto bail;
926
927        /*
928         * Expand the data into it.
929         */
930        if (!ZipUtils::inflateToBuffer(mFd, buf, mUncompressedLen,
931                mCompressedLen))
932            goto bail;
933    }
934
935    /*
936     * Success - now that we have the full asset in RAM we
937     * no longer need the streaming inflater
938     */
939    delete mZipInflater;
940    mZipInflater = NULL;
941
942    mBuf = buf;
943    buf = NULL;
944
945bail:
946    delete[] buf;
947    return mBuf;
948}
949
950