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 <utils/Asset.h>
25 #include <utils/Atomic.h>
26 #include <utils/FileMap.h>
27 #include <utils/StreamingZipInflater.h>
28 #include <utils/ZipUtils.h>
29 #include <utils/ZipFileRO.h>
30 #include <utils/Log.h>
31 #include <utils/threads.h>
40 #include <sys/types.h>
42 using namespace android;
48 static Mutex gAssetLock;
49 static int32_t gCount = 0;
50 static Asset* gHead = NULL;
51 static Asset* gTail = NULL;
53 int32_t Asset::getGlobalCount()
55 AutoMutex _l(gAssetLock);
59 String8 Asset::getAssetAllocations()
61 AutoMutex _l(gAssetLock);
65 if (cur->isAllocated()) {
67 res.append(cur->getAssetSource());
68 off64_t size = (cur->getLength()+512)/1024;
70 sprintf(buf, ": %dK\n", (int)size);
80 : mAccessMode(ACCESS_UNKNOWN)
82 AutoMutex _l(gAssetLock);
92 //LOGI("Creating Asset %p #%d\n", this, gCount);
97 AutoMutex _l(gAssetLock);
106 mNext->mPrev = mPrev;
109 mPrev->mNext = mNext;
111 mNext = mPrev = NULL;
112 //LOGI("Destroying Asset in %p #%d\n", this, gCount);
116 * Create a new Asset from a file on disk. There is a fair chance that
117 * the file doesn't actually exist.
119 * We can use "mode" to decide how we want to go about it.
121 /*static*/ Asset* Asset::createFromFile(const char* fileName, AccessMode mode)
128 fd = open(fileName, O_RDONLY | O_BINARY);
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.
138 // TODO(kroot): replace this with fstat despite the plea above.
140 length = lseek64(fd, 0, SEEK_END);
145 (void) lseek64(fd, 0, SEEK_SET);
148 if (fstat(fd, &st) < 0) {
153 if (!S_ISREG(st.st_mode)) {
159 pAsset = new _FileAsset;
160 result = pAsset->openChunk(fileName, fd, 0, length);
161 if (result != NO_ERROR) {
166 pAsset->mAccessMode = mode;
172 * Create a new Asset from a compressed file on disk. There is a fair chance
173 * that the file doesn't actually exist.
175 * We currently support gzip files. We might want to handle .bz2 someday.
177 /*static*/ Asset* Asset::createFromCompressedFile(const char* fileName,
180 _CompressedAsset* pAsset;
186 long uncompressedLen, compressedLen;
189 fd = open(fileName, O_RDONLY | O_BINARY);
193 fileLen = lseek(fd, 0, SEEK_END);
198 (void) lseek(fd, 0, SEEK_SET);
200 /* want buffered I/O for the file scan; must dup so fclose() is safe */
201 FILE* fp = fdopen(dup(fd), "rb");
208 scanResult = ZipUtils::examineGzip(fp, &method, &uncompressedLen,
209 &compressedLen, &crc32);
213 LOGD("File '%s' is not in gzip format\n", fileName);
218 pAsset = new _CompressedAsset;
219 result = pAsset->openChunk(fd, offset, method, uncompressedLen,
221 if (result != NO_ERROR) {
226 pAsset->mAccessMode = mode;
233 * Create a new Asset from part of an open file.
235 /*static*/ Asset* Asset::createFromFileSegment(int fd, off64_t offset,
236 size_t length, AccessMode mode)
241 pAsset = new _FileAsset;
242 result = pAsset->openChunk(NULL, fd, offset, length);
243 if (result != NO_ERROR)
246 pAsset->mAccessMode = mode;
251 * Create a new Asset from compressed data in an open file.
253 /*static*/ Asset* Asset::createFromCompressedData(int fd, off64_t offset,
254 int compressionMethod, size_t uncompressedLen, size_t compressedLen,
257 _CompressedAsset* pAsset;
260 pAsset = new _CompressedAsset;
261 result = pAsset->openChunk(fd, offset, compressionMethod,
262 uncompressedLen, compressedLen);
263 if (result != NO_ERROR)
266 pAsset->mAccessMode = mode;
272 * Create a new Asset from a memory mapping.
274 /*static*/ Asset* Asset::createFromUncompressedMap(FileMap* dataMap,
280 pAsset = new _FileAsset;
281 result = pAsset->openChunk(dataMap);
282 if (result != NO_ERROR)
285 pAsset->mAccessMode = mode;
290 * Create a new Asset from compressed data in a memory mapping.
292 /*static*/ Asset* Asset::createFromCompressedMap(FileMap* dataMap,
293 int method, size_t uncompressedLen, AccessMode mode)
295 _CompressedAsset* pAsset;
298 pAsset = new _CompressedAsset;
299 result = pAsset->openChunk(dataMap, method, uncompressedLen);
300 if (result != NO_ERROR)
303 pAsset->mAccessMode = mode;
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
313 * Returns the new chunk offset, or -1 if the seek is illegal.
315 off64_t Asset::handleSeek(off64_t offset, int whence, off64_t curPosn, off64_t maxPosn)
324 newOffset = curPosn + offset;
327 newOffset = maxPosn + offset;
330 LOGW("unexpected whence %d\n", whence);
331 // this was happening due to an off64_t size mismatch
336 if (newOffset < 0 || newOffset > maxPosn) {
337 LOGW("seek out of range: want %ld, end=%ld\n",
338 (long) newOffset, (long) maxPosn);
347 * ===========================================================================
349 * ===========================================================================
355 _FileAsset::_FileAsset(void)
356 : mStart(0), mLength(0), mOffset(0), mFp(NULL), mFileName(NULL), mMap(NULL), mBuf(NULL)
361 * Destructor. Release resources.
363 _FileAsset::~_FileAsset(void)
369 * Operate on a chunk of an uncompressed file.
371 * Zero-length chunks are allowed.
373 status_t _FileAsset::openChunk(const char* fileName, int fd, off64_t offset, size_t length)
375 assert(mFp == NULL); // no reopen
376 assert(mMap == NULL);
381 * Seek to end to get file length.
384 fileLength = lseek64(fd, 0, SEEK_END);
385 if (fileLength == (off64_t) -1) {
386 // probably a bad file descriptor
387 LOGD("failed lseek (errno=%d)\n", errno);
388 return UNKNOWN_ERROR;
391 if ((off64_t) (offset + length) > fileLength) {
392 LOGD("start (%ld) + len (%ld) > end (%ld)\n",
393 (long) offset, (long) length, (long) fileLength);
397 /* after fdopen, the fd will be closed on fclose() */
398 mFp = fdopen(fd, "rb");
400 return UNKNOWN_ERROR;
404 assert(mOffset == 0);
406 /* seek the FILE* to the start of chunk */
407 if (fseek(mFp, mStart, SEEK_SET) != 0) {
411 mFileName = fileName != NULL ? strdup(fileName) : NULL;
417 * Create the chunk from the map.
419 status_t _FileAsset::openChunk(FileMap* dataMap)
421 assert(mFp == NULL); // no reopen
422 assert(mMap == NULL);
423 assert(dataMap != NULL);
426 mStart = -1; // not used
427 mLength = dataMap->getDataLength();
428 assert(mOffset == 0);
434 * Read a chunk of data.
436 ssize_t _FileAsset::read(void* buf, size_t count)
441 assert(mOffset >= 0 && mOffset <= mLength);
443 if (getAccessMode() == ACCESS_BUFFER) {
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.
454 /* adjust count if we're near EOF */
455 maxLen = mLength - mOffset;
463 /* copy from mapped area */
464 //printf("map read\n");
465 memcpy(buf, (char*)mMap->getDataPtr() + mOffset, count);
467 } else if (mBuf != NULL) {
468 /* copy from buffer */
469 //printf("buf read\n");
470 memcpy(buf, (char*)mBuf + mOffset, count);
473 /* read from the file */
474 //printf("file read\n");
475 if (ftell(mFp) != mStart + mOffset) {
476 LOGE("Hosed: %ld != %ld+%ld\n",
477 ftell(mFp), (long) mStart, (long) mOffset);
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
488 actual = fread(buf, 1, count, mFp);
489 if (actual == 0) // something failed -- I/O error?
492 assert(actual == count);
500 * Seek to a new position.
502 off64_t _FileAsset::seek(off64_t offset, int whence)
505 off64_t actualOffset;
507 // compute new position within chunk
508 newPosn = handleSeek(offset, whence, mOffset, mLength);
509 if (newPosn == (off64_t) -1)
512 actualOffset = mStart + newPosn;
515 if (fseek(mFp, (long) actualOffset, SEEK_SET) != 0)
519 mOffset = actualOffset - mStart;
526 void _FileAsset::close(void)
537 if (mFileName != NULL) {
543 // can only be NULL when called from destructor
544 // (otherwise we would never return this object)
551 * Return a read-only pointer to a buffer.
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
558 const void* _FileAsset::getBuffer(bool wordAligned)
560 /* subsequent requests just use what we did previously */
565 return mMap->getDataPtr();
567 return ensureAlignment(mMap);
572 if (mLength < kReadVsMapThreshold) {
576 /* zero-length files are allowed; not sure about zero-len allocs */
577 /* (works fine with gcc + x86linux) */
582 buf = new unsigned char[allocLen];
584 LOGE("alloc of %ld bytes failed\n", (long) allocLen);
588 LOGV("Asset %p allocating buffer size %d (smaller than threshold)", this, (int)allocLen);
590 long oldPosn = ftell(mFp);
591 fseek(mFp, mStart, SEEK_SET);
592 if (fread(buf, 1, mLength, mFp) != (size_t) mLength) {
593 LOGE("failed reading %ld bytes\n", (long) mLength);
597 fseek(mFp, oldPosn, SEEK_SET);
600 LOGV(" getBuffer: loaded into buffer\n");
608 if (!map->create(NULL, fileno(mFp), mStart, mLength, true)) {
613 LOGV(" getBuffer: mapped\n");
617 return mMap->getDataPtr();
619 return ensureAlignment(mMap);
623 int _FileAsset::openFileDescriptor(off64_t* outStart, off64_t* outLength) const
626 const char* fname = mMap->getFileName();
633 *outStart = mMap->getDataOffset();
634 *outLength = mMap->getDataLength();
635 return open(fname, O_RDONLY | O_BINARY);
637 if (mFileName == NULL) {
641 *outLength = mLength;
642 return open(mFileName, O_RDONLY | O_BINARY);
645 const void* _FileAsset::ensureAlignment(FileMap* map)
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
651 LOGV("Returning aligned FileAsset %p (%s).", this,
655 // If not aligned on a word boundary, then we need to copy it into
657 LOGV("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];
661 LOGE("alloc of %ld bytes failed\n", (long) mLength);
664 memcpy(buf, data, mLength);
670 * ===========================================================================
672 * ===========================================================================
678 _CompressedAsset::_CompressedAsset(void)
679 : mStart(0), mCompressedLen(0), mUncompressedLen(0), mOffset(0),
680 mMap(NULL), mFd(-1), mZipInflater(NULL), mBuf(NULL)
685 * Destructor. Release resources.
687 _CompressedAsset::~_CompressedAsset(void)
693 * Open a chunk of compressed data inside a file.
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.
698 status_t _CompressedAsset::openChunk(int fd, off64_t offset,
699 int compressionMethod, size_t uncompressedLen, size_t compressedLen)
701 assert(mFd < 0); // no re-open
702 assert(mMap == NULL);
705 assert(compressedLen > 0);
707 if (compressionMethod != ZipFileRO::kCompressDeflated) {
709 return UNKNOWN_ERROR;
713 mCompressedLen = compressedLen;
714 mUncompressedLen = uncompressedLen;
715 assert(mOffset == 0);
717 assert(mBuf == NULL);
719 if (uncompressedLen > StreamingZipInflater::OUTPUT_CHUNK_SIZE) {
720 mZipInflater = new StreamingZipInflater(mFd, offset, uncompressedLen, compressedLen);
727 * Open a chunk of compressed data in a mapped region.
729 * Nothing is expanded until the first read call.
731 status_t _CompressedAsset::openChunk(FileMap* dataMap, int compressionMethod,
732 size_t uncompressedLen)
734 assert(mFd < 0); // no re-open
735 assert(mMap == NULL);
736 assert(dataMap != NULL);
738 if (compressionMethod != ZipFileRO::kCompressDeflated) {
740 return UNKNOWN_ERROR;
744 mStart = -1; // not used
745 mCompressedLen = dataMap->getDataLength();
746 mUncompressedLen = uncompressedLen;
747 assert(mOffset == 0);
749 if (uncompressedLen > StreamingZipInflater::OUTPUT_CHUNK_SIZE) {
750 mZipInflater = new StreamingZipInflater(dataMap, uncompressedLen);
756 * Read data from a chunk of compressed data.
758 * [For now, that's just copying data out of a buffer.]
760 ssize_t _CompressedAsset::read(void* buf, size_t count)
765 assert(mOffset >= 0 && mOffset <= mUncompressedLen);
767 /* If we're relying on a streaming inflater, go through that */
769 actual = mZipInflater->read(buf, count);
772 if (getBuffer(false) == NULL)
775 assert(mBuf != NULL);
777 /* adjust count if we're near EOF */
778 maxLen = mUncompressedLen - mOffset;
785 /* copy from buffer */
786 //printf("comp buf read\n");
787 memcpy(buf, (char*)mBuf + mOffset, count);
796 * Handle a seek request.
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
802 off64_t _CompressedAsset::seek(off64_t offset, int whence)
806 // compute new position within chunk
807 newPosn = handleSeek(offset, whence, mOffset, mUncompressedLen);
808 if (newPosn == (off64_t) -1)
812 mZipInflater->seekAbsolute(newPosn);
821 void _CompressedAsset::close(void)
841 * Get a pointer to a read-only buffer of data.
843 * The first time this is called, we expand the compressed data into a
846 const void* _CompressedAsset::getBuffer(bool wordAligned)
848 unsigned char* buf = NULL;
854 * Allocate a buffer and read the file into it.
856 buf = new unsigned char[mUncompressedLen];
858 LOGW("alloc %ld bytes failed\n", (long) mUncompressedLen);
863 if (!ZipFileRO::inflateBuffer(buf, mMap->getDataPtr(),
864 mUncompressedLen, mCompressedLen))
870 * Seek to the start of the compressed data.
872 if (lseek(mFd, mStart, SEEK_SET) != mStart)
876 * Expand the data into it.
878 if (!ZipUtils::inflateToBuffer(mFd, buf, mUncompressedLen,
884 * Success - now that we have the full asset in RAM we
885 * no longer need the streaming inflater