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