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