diff options
Diffstat (limited to 'libs/utils/Asset.cpp')
-rw-r--r-- | libs/utils/Asset.cpp | 813 |
1 files changed, 813 insertions, 0 deletions
diff --git a/libs/utils/Asset.cpp b/libs/utils/Asset.cpp new file mode 100644 index 0000000..91203dd --- /dev/null +++ b/libs/utils/Asset.cpp @@ -0,0 +1,813 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// +// Provide access to a read-only asset. +// + +#define LOG_TAG "asset" +//#define NDEBUG 0 + +#include <utils/Asset.h> +#include <utils/Atomic.h> +#include <utils/FileMap.h> +#include <utils/ZipUtils.h> +#include <utils/ZipFileRO.h> +#include <utils/Log.h> + +#include <string.h> +#include <memory.h> +#include <fcntl.h> +#include <errno.h> +#include <assert.h> + +using namespace android; + +#ifndef O_BINARY +# define O_BINARY 0 +#endif + +static volatile int32_t gCount = 0; + +int32_t Asset::getGlobalCount() +{ + return gCount; +} + +Asset::Asset(void) + : mAccessMode(ACCESS_UNKNOWN) +{ + int count = android_atomic_inc(&gCount)+1; + //LOGI("Creating Asset %p #%d\n", this, count); +} + +Asset::~Asset(void) +{ + int count = android_atomic_dec(&gCount); + //LOGI("Destroying Asset in %p #%d\n", this, count); +} + +/* + * Create a new Asset from a file on disk. There is a fair chance that + * the file doesn't actually exist. + * + * We can use "mode" to decide how we want to go about it. + */ +/*static*/ Asset* Asset::createFromFile(const char* fileName, AccessMode mode) +{ + _FileAsset* pAsset; + status_t result; + off_t length; + int fd; + + fd = open(fileName, O_RDONLY | O_BINARY); + if (fd < 0) + return NULL; + + /* + * Under Linux, the lseek fails if we actually opened a directory. To + * be correct we should test the file type explicitly, but since we + * always open things read-only it doesn't really matter, so there's + * no value in incurring the extra overhead of an fstat() call. + */ + length = lseek(fd, 0, SEEK_END); + if (length < 0) { + ::close(fd); + return NULL; + } + (void) lseek(fd, 0, SEEK_SET); + + pAsset = new _FileAsset; + result = pAsset->openChunk(fileName, fd, 0, length); + if (result != NO_ERROR) { + delete pAsset; + return NULL; + } + + pAsset->mAccessMode = mode; + return pAsset; +} + + +/* + * Create a new Asset from a compressed file on disk. There is a fair chance + * that the file doesn't actually exist. + * + * We currently support gzip files. We might want to handle .bz2 someday. + */ +/*static*/ Asset* Asset::createFromCompressedFile(const char* fileName, + AccessMode mode) +{ + _CompressedAsset* pAsset; + status_t result; + off_t fileLen; + bool scanResult; + long offset; + int method; + long uncompressedLen, compressedLen; + int fd; + + fd = open(fileName, O_RDONLY | O_BINARY); + if (fd < 0) + return NULL; + + fileLen = lseek(fd, 0, SEEK_END); + if (fileLen < 0) { + ::close(fd); + return NULL; + } + (void) lseek(fd, 0, SEEK_SET); + + /* want buffered I/O for the file scan; must dup so fclose() is safe */ + FILE* fp = fdopen(dup(fd), "rb"); + if (fp == NULL) { + ::close(fd); + return NULL; + } + + unsigned long crc32; + scanResult = ZipUtils::examineGzip(fp, &method, &uncompressedLen, + &compressedLen, &crc32); + offset = ftell(fp); + fclose(fp); + if (!scanResult) { + LOGD("File '%s' is not in gzip format\n", fileName); + ::close(fd); + return NULL; + } + + pAsset = new _CompressedAsset; + result = pAsset->openChunk(fd, offset, method, uncompressedLen, + compressedLen); + if (result != NO_ERROR) { + delete pAsset; + return NULL; + } + + pAsset->mAccessMode = mode; + return pAsset; +} + + +#if 0 +/* + * Create a new Asset from part of an open file. + */ +/*static*/ Asset* Asset::createFromFileSegment(int fd, off_t offset, + size_t length, AccessMode mode) +{ + _FileAsset* pAsset; + status_t result; + + pAsset = new _FileAsset; + result = pAsset->openChunk(NULL, fd, offset, length); + if (result != NO_ERROR) + return NULL; + + pAsset->mAccessMode = mode; + return pAsset; +} + +/* + * Create a new Asset from compressed data in an open file. + */ +/*static*/ Asset* Asset::createFromCompressedData(int fd, off_t offset, + int compressionMethod, size_t uncompressedLen, size_t compressedLen, + AccessMode mode) +{ + _CompressedAsset* pAsset; + status_t result; + + pAsset = new _CompressedAsset; + result = pAsset->openChunk(fd, offset, compressionMethod, + uncompressedLen, compressedLen); + if (result != NO_ERROR) + return NULL; + + pAsset->mAccessMode = mode; + return pAsset; +} +#endif + +/* + * Create a new Asset from a memory mapping. + */ +/*static*/ Asset* Asset::createFromUncompressedMap(FileMap* dataMap, + AccessMode mode) +{ + _FileAsset* pAsset; + status_t result; + + pAsset = new _FileAsset; + result = pAsset->openChunk(dataMap); + if (result != NO_ERROR) + return NULL; + + pAsset->mAccessMode = mode; + return pAsset; +} + +/* + * Create a new Asset from compressed data in a memory mapping. + */ +/*static*/ Asset* Asset::createFromCompressedMap(FileMap* dataMap, + int method, size_t uncompressedLen, AccessMode mode) +{ + _CompressedAsset* pAsset; + status_t result; + + pAsset = new _CompressedAsset; + result = pAsset->openChunk(dataMap, method, uncompressedLen); + if (result != NO_ERROR) + return NULL; + + pAsset->mAccessMode = mode; + return pAsset; +} + + +/* + * Do generic seek() housekeeping. Pass in the offset/whence values from + * the seek request, along with the current chunk offset and the chunk + * length. + * + * Returns the new chunk offset, or -1 if the seek is illegal. + */ +off_t Asset::handleSeek(off_t offset, int whence, off_t curPosn, off_t maxPosn) +{ + off_t newOffset; + + switch (whence) { + case SEEK_SET: + newOffset = offset; + break; + case SEEK_CUR: + newOffset = curPosn + offset; + break; + case SEEK_END: + newOffset = maxPosn + offset; + break; + default: + LOGW("unexpected whence %d\n", whence); + // this was happening due to an off_t size mismatch + assert(false); + return (off_t) -1; + } + + if (newOffset < 0 || newOffset > maxPosn) { + LOGW("seek out of range: want %ld, end=%ld\n", + (long) newOffset, (long) maxPosn); + return (off_t) -1; + } + + return newOffset; +} + + +/* + * =========================================================================== + * _FileAsset + * =========================================================================== + */ + +/* + * Constructor. + */ +_FileAsset::_FileAsset(void) + : mStart(0), mLength(0), mOffset(0), mFp(NULL), mFileName(NULL), mMap(NULL), mBuf(NULL) +{ +} + +/* + * Destructor. Release resources. + */ +_FileAsset::~_FileAsset(void) +{ + close(); +} + +/* + * Operate on a chunk of an uncompressed file. + * + * Zero-length chunks are allowed. + */ +status_t _FileAsset::openChunk(const char* fileName, int fd, off_t offset, size_t length) +{ + assert(mFp == NULL); // no reopen + assert(mMap == NULL); + assert(fd >= 0); + assert(offset >= 0); + + /* + * Seek to end to get file length. + */ + off_t fileLength; + fileLength = lseek(fd, 0, SEEK_END); + if (fileLength == (off_t) -1) { + // probably a bad file descriptor + LOGD("failed lseek (errno=%d)\n", errno); + return UNKNOWN_ERROR; + } + + if ((off_t) (offset + length) > fileLength) { + LOGD("start (%ld) + len (%ld) > end (%ld)\n", + (long) offset, (long) length, (long) fileLength); + return BAD_INDEX; + } + + /* after fdopen, the fd will be closed on fclose() */ + mFp = fdopen(fd, "rb"); + if (mFp == NULL) + return UNKNOWN_ERROR; + + mStart = offset; + mLength = length; + assert(mOffset == 0); + + /* seek the FILE* to the start of chunk */ + if (fseek(mFp, mStart, SEEK_SET) != 0) { + assert(false); + } + + mFileName = fileName != NULL ? strdup(fileName) : NULL; + + return NO_ERROR; +} + +/* + * Create the chunk from the map. + */ +status_t _FileAsset::openChunk(FileMap* dataMap) +{ + assert(mFp == NULL); // no reopen + assert(mMap == NULL); + assert(dataMap != NULL); + + mMap = dataMap; + mStart = -1; // not used + mLength = dataMap->getDataLength(); + assert(mOffset == 0); + + return NO_ERROR; +} + +/* + * Read a chunk of data. + */ +ssize_t _FileAsset::read(void* buf, size_t count) +{ + size_t maxLen; + size_t actual; + + assert(mOffset >= 0 && mOffset <= mLength); + + if (getAccessMode() == ACCESS_BUFFER) { + /* + * On first access, read or map the entire file. The caller has + * requested buffer access, either because they're going to be + * using the buffer or because what they're doing has appropriate + * performance needs and access patterns. + */ + if (mBuf == NULL) + getBuffer(false); + } + + /* adjust count if we're near EOF */ + maxLen = mLength - mOffset; + if (count > maxLen) + count = maxLen; + + if (!count) + return 0; + + if (mMap != NULL) { + /* copy from mapped area */ + //printf("map read\n"); + memcpy(buf, (char*)mMap->getDataPtr() + mOffset, count); + actual = count; + } else if (mBuf != NULL) { + /* copy from buffer */ + //printf("buf read\n"); + memcpy(buf, (char*)mBuf + mOffset, count); + actual = count; + } else { + /* read from the file */ + //printf("file read\n"); + if (ftell(mFp) != mStart + mOffset) { + LOGE("Hosed: %ld != %ld+%ld\n", + ftell(mFp), (long) mStart, (long) mOffset); + assert(false); + } + + /* + * This returns 0 on error or eof. We need to use ferror() or feof() + * to tell the difference, but we don't currently have those on the + * device. However, we know how much data is *supposed* to be in the + * file, so if we don't read the full amount we know something is + * hosed. + */ + actual = fread(buf, 1, count, mFp); + if (actual == 0) // something failed -- I/O error? + return -1; + + assert(actual == count); + } + + mOffset += actual; + return actual; +} + +/* + * Seek to a new position. + */ +off_t _FileAsset::seek(off_t offset, int whence) +{ + off_t newPosn; + long actualOffset; + + // compute new position within chunk + newPosn = handleSeek(offset, whence, mOffset, mLength); + if (newPosn == (off_t) -1) + return newPosn; + + actualOffset = (long) (mStart + newPosn); + + if (mFp != NULL) { + if (fseek(mFp, (long) actualOffset, SEEK_SET) != 0) + return (off_t) -1; + } + + mOffset = actualOffset - mStart; + return mOffset; +} + +/* + * Close the asset. + */ +void _FileAsset::close(void) +{ + if (mMap != NULL) { + mMap->release(); + mMap = NULL; + } + if (mBuf != NULL) { + delete[] mBuf; + mBuf = NULL; + } + + if (mFileName != NULL) { + free(mFileName); + mFileName = NULL; + } + + if (mFp != NULL) { + // can only be NULL when called from destructor + // (otherwise we would never return this object) + fclose(mFp); + mFp = NULL; + } +} + +/* + * Return a read-only pointer to a buffer. + * + * We can either read the whole thing in or map the relevant piece of + * the source file. Ideally a map would be established at a higher + * level and we'd be using a different object, but we didn't, so we + * deal with it here. + */ +const void* _FileAsset::getBuffer(bool wordAligned) +{ + /* subsequent requests just use what we did previously */ + if (mBuf != NULL) + return mBuf; + if (mMap != NULL) { + if (!wordAligned) { + return mMap->getDataPtr(); + } + return ensureAlignment(mMap); + } + + assert(mFp != NULL); + + if (mLength < kReadVsMapThreshold) { + unsigned char* buf; + long allocLen; + + /* zero-length files are allowed; not sure about zero-len allocs */ + /* (works fine with gcc + x86linux) */ + allocLen = mLength; + if (mLength == 0) + allocLen = 1; + + buf = new unsigned char[allocLen]; + if (buf == NULL) { + LOGE("alloc of %ld bytes failed\n", (long) allocLen); + return NULL; + } + + LOGV("Asset %p allocating buffer size %d (smaller than threshold)", this, (int)allocLen); + if (mLength > 0) { + long oldPosn = ftell(mFp); + fseek(mFp, mStart, SEEK_SET); + if (fread(buf, 1, mLength, mFp) != (size_t) mLength) { + LOGE("failed reading %ld bytes\n", (long) mLength); + delete[] buf; + return NULL; + } + fseek(mFp, oldPosn, SEEK_SET); + } + + LOGV(" getBuffer: loaded into buffer\n"); + + mBuf = buf; + return mBuf; + } else { + FileMap* map; + + map = new FileMap; + if (!map->create(NULL, fileno(mFp), mStart, mLength, true)) { + map->release(); + return NULL; + } + + LOGV(" getBuffer: mapped\n"); + + mMap = map; + if (!wordAligned) { + return mMap->getDataPtr(); + } + return ensureAlignment(mMap); + } +} + +int _FileAsset::openFileDescriptor(off_t* outStart, off_t* outLength) const +{ + if (mMap != NULL) { + const char* fname = mMap->getFileName(); + if (fname == NULL) { + fname = mFileName; + } + if (fname == NULL) { + return -1; + } + *outStart = mMap->getDataOffset(); + *outLength = mMap->getDataLength(); + return open(fname, O_RDONLY | O_BINARY); + } + if (mFileName == NULL) { + return -1; + } + *outStart = mStart; + *outLength = mLength; + return open(mFileName, O_RDONLY | O_BINARY); +} + +const void* _FileAsset::ensureAlignment(FileMap* map) +{ + void* data = map->getDataPtr(); + if ((((size_t)data)&0x3) == 0) { + // We can return this directly if it is aligned on a word + // boundary. + return data; + } + // If not aligned on a word boundary, then we need to copy it into + // our own buffer. + LOGV("Copying FileAsset %p to buffer size %d to make it aligned.", this, (int)mLength); + unsigned char* buf = new unsigned char[mLength]; + if (buf == NULL) { + LOGE("alloc of %ld bytes failed\n", (long) mLength); + return NULL; + } + memcpy(buf, data, mLength); + mBuf = buf; + return buf; +} + +/* + * =========================================================================== + * _CompressedAsset + * =========================================================================== + */ + +/* + * Constructor. + */ +_CompressedAsset::_CompressedAsset(void) + : mStart(0), mCompressedLen(0), mUncompressedLen(0), mOffset(0), + mMap(NULL), mFd(-1), mBuf(NULL) +{ +} + +/* + * Destructor. Release resources. + */ +_CompressedAsset::~_CompressedAsset(void) +{ + close(); +} + +/* + * Open a chunk of compressed data inside a file. + * + * This currently just sets up some values and returns. On the first + * read, we expand the entire file into a buffer and return data from it. + */ +status_t _CompressedAsset::openChunk(int fd, off_t offset, + int compressionMethod, size_t uncompressedLen, size_t compressedLen) +{ + assert(mFd < 0); // no re-open + assert(mMap == NULL); + assert(fd >= 0); + assert(offset >= 0); + assert(compressedLen > 0); + + if (compressionMethod != ZipFileRO::kCompressDeflated) { + assert(false); + return UNKNOWN_ERROR; + } + + mStart = offset; + mCompressedLen = compressedLen; + mUncompressedLen = uncompressedLen; + assert(mOffset == 0); + mFd = fd; + assert(mBuf == NULL); + + return NO_ERROR; +} + +/* + * Open a chunk of compressed data in a mapped region. + * + * Nothing is expanded until the first read call. + */ +status_t _CompressedAsset::openChunk(FileMap* dataMap, int compressionMethod, + size_t uncompressedLen) +{ + assert(mFd < 0); // no re-open + assert(mMap == NULL); + assert(dataMap != NULL); + + if (compressionMethod != ZipFileRO::kCompressDeflated) { + assert(false); + return UNKNOWN_ERROR; + } + + mMap = dataMap; + mStart = -1; // not used + mCompressedLen = dataMap->getDataLength(); + mUncompressedLen = uncompressedLen; + assert(mOffset == 0); + + return NO_ERROR; +} + +/* + * Read data from a chunk of compressed data. + * + * [For now, that's just copying data out of a buffer.] + */ +ssize_t _CompressedAsset::read(void* buf, size_t count) +{ + size_t maxLen; + size_t actual; + + assert(mOffset >= 0 && mOffset <= mUncompressedLen); + + // TODO: if mAccessMode == ACCESS_STREAMING, use zlib more cleverly + + if (mBuf == NULL) { + if (getBuffer(false) == NULL) + return -1; + } + assert(mBuf != NULL); + + /* adjust count if we're near EOF */ + maxLen = mUncompressedLen - mOffset; + if (count > maxLen) + count = maxLen; + + if (!count) + return 0; + + /* copy from buffer */ + //printf("comp buf read\n"); + memcpy(buf, (char*)mBuf + mOffset, count); + actual = count; + + mOffset += actual; + return actual; +} + +/* + * Handle a seek request. + * + * If we're working in a streaming mode, this is going to be fairly + * expensive, because it requires plowing through a bunch of compressed + * data. + */ +off_t _CompressedAsset::seek(off_t offset, int whence) +{ + off_t newPosn; + + // compute new position within chunk + newPosn = handleSeek(offset, whence, mOffset, mUncompressedLen); + if (newPosn == (off_t) -1) + return newPosn; + + mOffset = newPosn; + return mOffset; +} + +/* + * Close the asset. + */ +void _CompressedAsset::close(void) +{ + if (mMap != NULL) { + mMap->release(); + mMap = NULL; + } + if (mBuf != NULL) { + delete[] mBuf; + mBuf = NULL; + } + + if (mFd > 0) { + ::close(mFd); + mFd = -1; + } +} + +/* + * Get a pointer to a read-only buffer of data. + * + * The first time this is called, we expand the compressed data into a + * buffer. + */ +const void* _CompressedAsset::getBuffer(bool wordAligned) +{ + unsigned char* buf = NULL; + + if (mBuf != NULL) + return mBuf; + + if (mUncompressedLen > UNCOMPRESS_DATA_MAX) { + LOGD("Data exceeds UNCOMPRESS_DATA_MAX (%ld vs %d)\n", + (long) mUncompressedLen, UNCOMPRESS_DATA_MAX); + goto bail; + } + + /* + * Allocate a buffer and read the file into it. + */ + buf = new unsigned char[mUncompressedLen]; + if (buf == NULL) { + LOGW("alloc %ld bytes failed\n", (long) mUncompressedLen); + goto bail; + } + + if (mMap != NULL) { + if (!ZipFileRO::inflateBuffer(buf, mMap->getDataPtr(), + mUncompressedLen, mCompressedLen)) + goto bail; + } else { + assert(mFd >= 0); + + /* + * Seek to the start of the compressed data. + */ + if (lseek(mFd, mStart, SEEK_SET) != mStart) + goto bail; + + /* + * Expand the data into it. + */ + if (!ZipUtils::inflateToBuffer(mFd, buf, mUncompressedLen, + mCompressedLen)) + goto bail; + } + + /* success! */ + mBuf = buf; + buf = NULL; + +bail: + delete[] buf; + return mBuf; +} + |