2 * Copyright (C) 2006 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
18 // Provide access to a read-only asset.
21 #define LOG_TAG "asset"
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>
40 #include <sys/types.h>
43 using namespace android;
49 static const bool kIsDebug = false;
51 static Mutex gAssetLock;
52 static int32_t gCount = 0;
53 static Asset* gHead = NULL;
54 static Asset* gTail = NULL;
56 void Asset::registerAsset(Asset* asset)
58 AutoMutex _l(gAssetLock);
60 asset->mNext = asset->mPrev = NULL;
62 gHead = gTail = asset;
70 ALOGI("Creating Asset %p #%d\n", asset, gCount);
74 void Asset::unregisterAsset(Asset* asset)
76 AutoMutex _l(gAssetLock);
84 if (asset->mNext != NULL) {
85 asset->mNext->mPrev = asset->mPrev;
87 if (asset->mPrev != NULL) {
88 asset->mPrev->mNext = asset->mNext;
90 asset->mNext = asset->mPrev = NULL;
93 ALOGI("Destroying Asset in %p #%d\n", asset, gCount);
97 int32_t Asset::getGlobalCount()
99 AutoMutex _l(gAssetLock);
103 String8 Asset::getAssetAllocations()
105 AutoMutex _l(gAssetLock);
108 while (cur != NULL) {
109 if (cur->isAllocated()) {
111 res.append(cur->getAssetSource());
112 off64_t size = (cur->getLength()+512)/1024;
114 snprintf(buf, sizeof(buf), ": %dK\n", (int)size);
124 : mAccessMode(ACCESS_UNKNOWN), mNext(NULL), mPrev(NULL)
129 * Create a new Asset from a file on disk. There is a fair chance that
130 * the file doesn't actually exist.
132 * We can use "mode" to decide how we want to go about it.
134 /*static*/ Asset* Asset::createFromFile(const char* fileName, AccessMode mode)
141 fd = open(fileName, O_RDONLY | O_BINARY);
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.
151 // TODO(kroot): replace this with fstat despite the plea above.
153 length = lseek64(fd, 0, SEEK_END);
158 (void) lseek64(fd, 0, SEEK_SET);
161 if (fstat(fd, &st) < 0) {
166 if (!S_ISREG(st.st_mode)) {
172 pAsset = new _FileAsset;
173 result = pAsset->openChunk(fileName, fd, 0, length);
174 if (result != NO_ERROR) {
179 pAsset->mAccessMode = mode;
185 * Create a new Asset from a compressed file on disk. There is a fair chance
186 * that the file doesn't actually exist.
188 * We currently support gzip files. We might want to handle .bz2 someday.
190 /*static*/ Asset* Asset::createFromCompressedFile(const char* fileName,
193 _CompressedAsset* pAsset;
199 long uncompressedLen, compressedLen;
202 fd = open(fileName, O_RDONLY | O_BINARY);
206 fileLen = lseek(fd, 0, SEEK_END);
211 (void) lseek(fd, 0, SEEK_SET);
213 /* want buffered I/O for the file scan; must dup so fclose() is safe */
214 FILE* fp = fdopen(dup(fd), "rb");
221 scanResult = ZipUtils::examineGzip(fp, &method, &uncompressedLen,
222 &compressedLen, &crc32);
226 ALOGD("File '%s' is not in gzip format\n", fileName);
231 pAsset = new _CompressedAsset;
232 result = pAsset->openChunk(fd, offset, method, uncompressedLen,
234 if (result != NO_ERROR) {
239 pAsset->mAccessMode = mode;
246 * Create a new Asset from part of an open file.
248 /*static*/ Asset* Asset::createFromFileSegment(int fd, off64_t offset,
249 size_t length, AccessMode mode)
254 pAsset = new _FileAsset;
255 result = pAsset->openChunk(NULL, fd, offset, length);
256 if (result != NO_ERROR)
259 pAsset->mAccessMode = mode;
264 * Create a new Asset from compressed data in an open file.
266 /*static*/ Asset* Asset::createFromCompressedData(int fd, off64_t offset,
267 int compressionMethod, size_t uncompressedLen, size_t compressedLen,
270 _CompressedAsset* pAsset;
273 pAsset = new _CompressedAsset;
274 result = pAsset->openChunk(fd, offset, compressionMethod,
275 uncompressedLen, compressedLen);
276 if (result != NO_ERROR)
279 pAsset->mAccessMode = mode;
285 * Create a new Asset from a memory mapping.
287 /*static*/ Asset* Asset::createFromUncompressedMap(FileMap* dataMap,
293 pAsset = new _FileAsset;
294 result = pAsset->openChunk(dataMap);
295 if (result != NO_ERROR)
298 pAsset->mAccessMode = mode;
302 /*static*/ std::unique_ptr<Asset> Asset::createFromUncompressedMap(std::unique_ptr<FileMap> dataMap,
305 std::unique_ptr<_FileAsset> pAsset = util::make_unique<_FileAsset>();
307 status_t result = pAsset->openChunk(dataMap.get());
308 if (result != NO_ERROR) {
312 // We succeeded, so relinquish control of dataMap
313 (void) dataMap.release();
314 pAsset->mAccessMode = mode;
315 return std::move(pAsset);
319 * Create a new Asset from compressed data in a memory mapping.
321 /*static*/ Asset* Asset::createFromCompressedMap(FileMap* dataMap,
322 size_t uncompressedLen, AccessMode mode)
324 _CompressedAsset* pAsset;
327 pAsset = new _CompressedAsset;
328 result = pAsset->openChunk(dataMap, uncompressedLen);
329 if (result != NO_ERROR)
332 pAsset->mAccessMode = mode;
336 /*static*/ std::unique_ptr<Asset> Asset::createFromCompressedMap(std::unique_ptr<FileMap> dataMap,
337 size_t uncompressedLen, AccessMode mode)
339 std::unique_ptr<_CompressedAsset> pAsset = util::make_unique<_CompressedAsset>();
341 status_t result = pAsset->openChunk(dataMap.get(), uncompressedLen);
342 if (result != NO_ERROR) {
346 // We succeeded, so relinquish control of dataMap
347 (void) dataMap.release();
348 pAsset->mAccessMode = mode;
349 return std::move(pAsset);
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
357 * Returns the new chunk offset, or -1 if the seek is illegal.
359 off64_t Asset::handleSeek(off64_t offset, int whence, off64_t curPosn, off64_t maxPosn)
368 newOffset = curPosn + offset;
371 newOffset = maxPosn + offset;
374 ALOGW("unexpected whence %d\n", whence);
375 // this was happening due to an off64_t size mismatch
380 if (newOffset < 0 || newOffset > maxPosn) {
381 ALOGW("seek out of range: want %ld, end=%ld\n",
382 (long) newOffset, (long) maxPosn);
391 * ===========================================================================
393 * ===========================================================================
399 _FileAsset::_FileAsset(void)
400 : mStart(0), mLength(0), mOffset(0), mFp(NULL), mFileName(NULL), mMap(NULL), mBuf(NULL)
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
408 * Destructor. Release resources.
410 _FileAsset::~_FileAsset(void)
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);
420 * Operate on a chunk of an uncompressed file.
422 * Zero-length chunks are allowed.
424 status_t _FileAsset::openChunk(const char* fileName, int fd, off64_t offset, size_t length)
426 assert(mFp == NULL); // no reopen
427 assert(mMap == NULL);
432 * Seek to end to get file length.
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;
442 if ((off64_t) (offset + length) > fileLength) {
443 ALOGD("start (%ld) + len (%ld) > end (%ld)\n",
444 (long) offset, (long) length, (long) fileLength);
448 /* after fdopen, the fd will be closed on fclose() */
449 mFp = fdopen(fd, "rb");
451 return UNKNOWN_ERROR;
455 assert(mOffset == 0);
457 /* seek the FILE* to the start of chunk */
458 if (fseek(mFp, mStart, SEEK_SET) != 0) {
462 mFileName = fileName != NULL ? strdup(fileName) : NULL;
468 * Create the chunk from the map.
470 status_t _FileAsset::openChunk(FileMap* dataMap)
472 assert(mFp == NULL); // no reopen
473 assert(mMap == NULL);
474 assert(dataMap != NULL);
477 mStart = -1; // not used
478 mLength = dataMap->getDataLength();
479 assert(mOffset == 0);
485 * Read a chunk of data.
487 ssize_t _FileAsset::read(void* buf, size_t count)
492 assert(mOffset >= 0 && mOffset <= mLength);
494 if (getAccessMode() == ACCESS_BUFFER) {
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.
505 /* adjust count if we're near EOF */
506 maxLen = mLength - mOffset;
514 /* copy from mapped area */
515 //printf("map read\n");
516 memcpy(buf, (char*)mMap->getDataPtr() + mOffset, count);
518 } else if (mBuf != NULL) {
519 /* copy from buffer */
520 //printf("buf read\n");
521 memcpy(buf, (char*)mBuf + mOffset, count);
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);
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
539 actual = fread(buf, 1, count, mFp);
540 if (actual == 0) // something failed -- I/O error?
543 assert(actual == count);
551 * Seek to a new position.
553 off64_t _FileAsset::seek(off64_t offset, int whence)
556 off64_t actualOffset;
558 // compute new position within chunk
559 newPosn = handleSeek(offset, whence, mOffset, mLength);
560 if (newPosn == (off64_t) -1)
563 actualOffset = mStart + newPosn;
566 if (fseek(mFp, (long) actualOffset, SEEK_SET) != 0)
570 mOffset = actualOffset - mStart;
577 void _FileAsset::close(void)
588 if (mFileName != NULL) {
594 // can only be NULL when called from destructor
595 // (otherwise we would never return this object)
602 * Return a read-only pointer to a buffer.
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
609 const void* _FileAsset::getBuffer(bool wordAligned)
611 /* subsequent requests just use what we did previously */
616 return mMap->getDataPtr();
618 return ensureAlignment(mMap);
623 if (mLength < kReadVsMapThreshold) {
627 /* zero-length files are allowed; not sure about zero-len allocs */
628 /* (works fine with gcc + x86linux) */
633 buf = new unsigned char[allocLen];
635 ALOGE("alloc of %ld bytes failed\n", (long) allocLen);
639 ALOGV("Asset %p allocating buffer size %d (smaller than threshold)", this, (int)allocLen);
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);
648 fseek(mFp, oldPosn, SEEK_SET);
651 ALOGV(" getBuffer: loaded into buffer\n");
659 if (!map->create(NULL, fileno(mFp), mStart, mLength, true)) {
664 ALOGV(" getBuffer: mapped\n");
668 return mMap->getDataPtr();
670 return ensureAlignment(mMap);
674 int _FileAsset::openFileDescriptor(off64_t* outStart, off64_t* outLength) const
677 const char* fname = mMap->getFileName();
684 *outStart = mMap->getDataOffset();
685 *outLength = mMap->getDataLength();
686 return open(fname, O_RDONLY | O_BINARY);
688 if (mFileName == NULL) {
692 *outLength = mLength;
693 return open(mFileName, O_RDONLY | O_BINARY);
696 const void* _FileAsset::ensureAlignment(FileMap* map)
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
702 ALOGV("Returning aligned FileAsset %p (%s).", this,
706 // If not aligned on a word boundary, then we need to copy it into
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];
712 ALOGE("alloc of %ld bytes failed\n", (long) mLength);
715 memcpy(buf, data, mLength);
721 * ===========================================================================
723 * ===========================================================================
729 _CompressedAsset::_CompressedAsset(void)
730 : mStart(0), mCompressedLen(0), mUncompressedLen(0), mOffset(0),
731 mMap(NULL), mFd(-1), mZipInflater(NULL), mBuf(NULL)
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
739 * Destructor. Release resources.
741 _CompressedAsset::~_CompressedAsset(void)
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);
751 * Open a chunk of compressed data inside a file.
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.
756 status_t _CompressedAsset::openChunk(int fd, off64_t offset,
757 int compressionMethod, size_t uncompressedLen, size_t compressedLen)
759 assert(mFd < 0); // no re-open
760 assert(mMap == NULL);
763 assert(compressedLen > 0);
765 if (compressionMethod != ZipFileRO::kCompressDeflated) {
767 return UNKNOWN_ERROR;
771 mCompressedLen = compressedLen;
772 mUncompressedLen = uncompressedLen;
773 assert(mOffset == 0);
775 assert(mBuf == NULL);
777 if (uncompressedLen > StreamingZipInflater::OUTPUT_CHUNK_SIZE) {
778 mZipInflater = new StreamingZipInflater(mFd, offset, uncompressedLen, compressedLen);
785 * Open a chunk of compressed data in a mapped region.
787 * Nothing is expanded until the first read call.
789 status_t _CompressedAsset::openChunk(FileMap* dataMap, size_t uncompressedLen)
791 assert(mFd < 0); // no re-open
792 assert(mMap == NULL);
793 assert(dataMap != NULL);
796 mStart = -1; // not used
797 mCompressedLen = dataMap->getDataLength();
798 mUncompressedLen = uncompressedLen;
799 assert(mOffset == 0);
801 if (uncompressedLen > StreamingZipInflater::OUTPUT_CHUNK_SIZE) {
802 mZipInflater = new StreamingZipInflater(dataMap, uncompressedLen);
808 * Read data from a chunk of compressed data.
810 * [For now, that's just copying data out of a buffer.]
812 ssize_t _CompressedAsset::read(void* buf, size_t count)
817 assert(mOffset >= 0 && mOffset <= mUncompressedLen);
819 /* If we're relying on a streaming inflater, go through that */
821 actual = mZipInflater->read(buf, count);
824 if (getBuffer(false) == NULL)
827 assert(mBuf != NULL);
829 /* adjust count if we're near EOF */
830 maxLen = mUncompressedLen - mOffset;
837 /* copy from buffer */
838 //printf("comp buf read\n");
839 memcpy(buf, (char*)mBuf + mOffset, count);
848 * Handle a seek request.
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
854 off64_t _CompressedAsset::seek(off64_t offset, int whence)
858 // compute new position within chunk
859 newPosn = handleSeek(offset, whence, mOffset, mUncompressedLen);
860 if (newPosn == (off64_t) -1)
864 mZipInflater->seekAbsolute(newPosn);
873 void _CompressedAsset::close(void)
893 * Get a pointer to a read-only buffer of data.
895 * The first time this is called, we expand the compressed data into a
898 const void* _CompressedAsset::getBuffer(bool)
900 unsigned char* buf = NULL;
906 * Allocate a buffer and read the file into it.
908 buf = new unsigned char[mUncompressedLen];
910 ALOGW("alloc %ld bytes failed\n", (long) mUncompressedLen);
915 if (!ZipUtils::inflateToBuffer(mMap->getDataPtr(), buf,
916 mUncompressedLen, mCompressedLen))
922 * Seek to the start of the compressed data.
924 if (lseek(mFd, mStart, SEEK_SET) != mStart)
928 * Expand the data into it.
930 if (!ZipUtils::inflateToBuffer(mFd, buf, mUncompressedLen,
936 * Success - now that we have the full asset in RAM we
937 * no longer need the streaming inflater