| /* |
| * 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 <androidfw/Asset.h> |
| #include <androidfw/StreamingZipInflater.h> |
| #include <androidfw/Util.h> |
| #include <androidfw/ZipFileRO.h> |
| #include <androidfw/ZipUtils.h> |
| #include <cutils/atomic.h> |
| #include <utils/FileMap.h> |
| #include <utils/Log.h> |
| #include <utils/threads.h> |
| |
| #include <assert.h> |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <memory.h> |
| #include <string.h> |
| #include <sys/stat.h> |
| #include <sys/types.h> |
| #include <unistd.h> |
| |
| using namespace android; |
| |
| #ifndef O_BINARY |
| # define O_BINARY 0 |
| #endif |
| |
| static const bool kIsDebug = false; |
| |
| static Mutex gAssetLock; |
| static int32_t gCount = 0; |
| static Asset* gHead = NULL; |
| static Asset* gTail = NULL; |
| |
| void Asset::registerAsset(Asset* asset) |
| { |
| AutoMutex _l(gAssetLock); |
| gCount++; |
| asset->mNext = asset->mPrev = NULL; |
| if (gTail == NULL) { |
| gHead = gTail = asset; |
| } else { |
| asset->mPrev = gTail; |
| gTail->mNext = asset; |
| gTail = asset; |
| } |
| |
| if (kIsDebug) { |
| ALOGI("Creating Asset %p #%d\n", asset, gCount); |
| } |
| } |
| |
| void Asset::unregisterAsset(Asset* asset) |
| { |
| AutoMutex _l(gAssetLock); |
| gCount--; |
| if (gHead == asset) { |
| gHead = asset->mNext; |
| } |
| if (gTail == asset) { |
| gTail = asset->mPrev; |
| } |
| if (asset->mNext != NULL) { |
| asset->mNext->mPrev = asset->mPrev; |
| } |
| if (asset->mPrev != NULL) { |
| asset->mPrev->mNext = asset->mNext; |
| } |
| asset->mNext = asset->mPrev = NULL; |
| |
| if (kIsDebug) { |
| ALOGI("Destroying Asset in %p #%d\n", asset, gCount); |
| } |
| } |
| |
| int32_t Asset::getGlobalCount() |
| { |
| AutoMutex _l(gAssetLock); |
| return gCount; |
| } |
| |
| String8 Asset::getAssetAllocations() |
| { |
| AutoMutex _l(gAssetLock); |
| String8 res; |
| Asset* cur = gHead; |
| while (cur != NULL) { |
| if (cur->isAllocated()) { |
| res.append(" "); |
| res.append(cur->getAssetSource()); |
| off64_t size = (cur->getLength()+512)/1024; |
| char buf[64]; |
| snprintf(buf, sizeof(buf), ": %dK\n", (int)size); |
| res.append(buf); |
| } |
| cur = cur->mNext; |
| } |
| |
| return res; |
| } |
| |
| Asset::Asset(void) |
| : mAccessMode(ACCESS_UNKNOWN), mNext(NULL), mPrev(NULL) |
| { |
| } |
| |
| /* |
| * 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) |
| { |
| return createFromFd(open(fileName, O_RDONLY | O_BINARY), fileName, mode); |
| } |
| |
| /* |
| * 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::createFromFd(const int fd, const char* fileName, AccessMode mode) |
| { |
| if (fd < 0) { |
| return NULL; |
| } |
| |
| _FileAsset* pAsset; |
| status_t result; |
| off64_t length; |
| |
| /* |
| * 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. |
| */ |
| // TODO(kroot): replace this with fstat despite the plea above. |
| #if 1 |
| length = lseek64(fd, 0, SEEK_END); |
| if (length < 0) { |
| ::close(fd); |
| return NULL; |
| } |
| (void) lseek64(fd, 0, SEEK_SET); |
| #else |
| struct stat st; |
| if (fstat(fd, &st) < 0) { |
| ::close(fd); |
| return NULL; |
| } |
| |
| if (!S_ISREG(st.st_mode)) { |
| ::close(fd); |
| return NULL; |
| } |
| #endif |
| |
| 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; |
| off64_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) { |
| ALOGD("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, off64_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) { |
| delete pAsset; |
| return NULL; |
| } |
| |
| pAsset->mAccessMode = mode; |
| return pAsset; |
| } |
| |
| /* |
| * Create a new Asset from compressed data in an open file. |
| */ |
| /*static*/ Asset* Asset::createFromCompressedData(int fd, off64_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) { |
| delete pAsset; |
| return NULL; |
| } |
| |
| pAsset->mAccessMode = mode; |
| return pAsset; |
| } |
| #endif |
| |
| /* |
| * Create a new Asset from a memory mapping. |
| */ |
| /*static*/ std::unique_ptr<Asset> Asset::createFromUncompressedMap(incfs::IncFsFileMap&& dataMap, |
| AccessMode mode, |
| base::unique_fd fd) |
| { |
| auto pAsset = util::make_unique<_FileAsset>(); |
| |
| status_t result = pAsset->openChunk(std::move(dataMap), std::move(fd)); |
| if (result != NO_ERROR) { |
| return NULL; |
| } |
| |
| // We succeeded, so relinquish control of dataMap |
| pAsset->mAccessMode = mode; |
| return std::move(pAsset); |
| } |
| |
| /* |
| * Create a new Asset from compressed data in a memory mapping. |
| */ |
| /*static*/ std::unique_ptr<Asset> Asset::createFromCompressedMap(incfs::IncFsFileMap&& dataMap, |
| size_t uncompressedLen, |
| AccessMode mode) |
| { |
| auto pAsset = util::make_unique<_CompressedAsset>(); |
| |
| status_t result = pAsset->openChunk(std::move(dataMap), uncompressedLen); |
| if (result != NO_ERROR) { |
| return NULL; |
| } |
| |
| // We succeeded, so relinquish control of dataMap |
| pAsset->mAccessMode = mode; |
| return std::move(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. |
| */ |
| off64_t Asset::handleSeek(off64_t offset, int whence, off64_t curPosn, off64_t maxPosn) |
| { |
| off64_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: |
| ALOGW("unexpected whence %d\n", whence); |
| // this was happening due to an off64_t size mismatch |
| assert(false); |
| return (off64_t) -1; |
| } |
| |
| if (newOffset < 0 || newOffset > maxPosn) { |
| ALOGW("seek out of range: want %ld, end=%ld\n", |
| (long) newOffset, (long) maxPosn); |
| return (off64_t) -1; |
| } |
| |
| return newOffset; |
| } |
| |
| |
| /* |
| * =========================================================================== |
| * _FileAsset |
| * =========================================================================== |
| */ |
| |
| /* |
| * Constructor. |
| */ |
| _FileAsset::_FileAsset(void) |
| : mStart(0), mLength(0), mOffset(0), mFp(NULL), mFileName(NULL), mFd(-1), mBuf(NULL) |
| { |
| // Register the Asset with the global list here after it is fully constructed and its |
| // vtable pointer points to this concrete type. b/31113965 |
| registerAsset(this); |
| } |
| |
| /* |
| * Destructor. Release resources. |
| */ |
| _FileAsset::~_FileAsset(void) |
| { |
| close(); |
| |
| // Unregister the Asset from the global list here before it is destructed and while its vtable |
| // pointer still points to this concrete type. b/31113965 |
| unregisterAsset(this); |
| } |
| |
| /* |
| * Operate on a chunk of an uncompressed file. |
| * |
| * Zero-length chunks are allowed. |
| */ |
| status_t _FileAsset::openChunk(const char* fileName, int fd, off64_t offset, size_t length) |
| { |
| assert(mFp == NULL); // no reopen |
| assert(!mMap.has_value()); |
| assert(fd >= 0); |
| assert(offset >= 0); |
| |
| /* |
| * Seek to end to get file length. |
| */ |
| off64_t fileLength; |
| fileLength = lseek64(fd, 0, SEEK_END); |
| if (fileLength == (off64_t) -1) { |
| // probably a bad file descriptor |
| ALOGD("failed lseek (errno=%d)\n", errno); |
| return UNKNOWN_ERROR; |
| } |
| |
| if ((off64_t) (offset + length) > fileLength) { |
| ALOGD("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(incfs::IncFsFileMap&& dataMap, base::unique_fd fd) |
| { |
| assert(mFp == NULL); // no reopen |
| assert(!mMap.has_value()); |
| assert(dataMap != NULL); |
| |
| mMap = std::move(dataMap); |
| mStart = -1; // not used |
| mLength = mMap->length(); |
| mFd = std::move(fd); |
| 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.has_value()) { |
| /* copy from mapped area */ |
| //printf("map read\n"); |
| const auto readPos = mMap->data().offset(mOffset).convert<char>(); |
| if (!readPos.verify(count)) { |
| return -1; |
| } |
| |
| memcpy(buf, readPos.unsafe_ptr(), 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) { |
| ALOGE("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. |
| */ |
| off64_t _FileAsset::seek(off64_t offset, int whence) |
| { |
| off64_t newPosn; |
| off64_t actualOffset; |
| |
| // compute new position within chunk |
| newPosn = handleSeek(offset, whence, mOffset, mLength); |
| if (newPosn == (off64_t) -1) |
| return newPosn; |
| |
| actualOffset = mStart + newPosn; |
| |
| if (mFp != NULL) { |
| if (fseek(mFp, (long) actualOffset, SEEK_SET) != 0) |
| return (off64_t) -1; |
| } |
| |
| mOffset = actualOffset - mStart; |
| return mOffset; |
| } |
| |
| /* |
| * Close the asset. |
| */ |
| void _FileAsset::close(void) |
| { |
| 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 aligned) |
| { |
| auto buffer = getIncFsBuffer(aligned); |
| if (mBuf != NULL) |
| return mBuf; |
| if (!buffer.convert<uint8_t>().verify(mLength)) |
| return NULL; |
| return buffer.unsafe_ptr(); |
| } |
| |
| incfs::map_ptr<void> _FileAsset::getIncFsBuffer(bool aligned) |
| { |
| /* subsequent requests just use what we did previously */ |
| if (mBuf != NULL) |
| return mBuf; |
| if (mMap.has_value()) { |
| if (!aligned) { |
| return mMap->data(); |
| } |
| 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) { |
| ALOGE("alloc of %ld bytes failed\n", (long) allocLen); |
| return NULL; |
| } |
| |
| ALOGV("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) { |
| ALOGE("failed reading %ld bytes\n", (long) mLength); |
| delete[] buf; |
| return NULL; |
| } |
| fseek(mFp, oldPosn, SEEK_SET); |
| } |
| |
| ALOGV(" getBuffer: loaded into buffer\n"); |
| |
| mBuf = buf; |
| return mBuf; |
| } else { |
| incfs::IncFsFileMap map; |
| if (!map.Create(fileno(mFp), mStart, mLength, NULL /* file_name */ )) { |
| return NULL; |
| } |
| |
| ALOGV(" getBuffer: mapped\n"); |
| |
| mMap = std::move(map); |
| if (!aligned) { |
| return mMap->data(); |
| } |
| return ensureAlignment(*mMap); |
| } |
| } |
| |
| int _FileAsset::openFileDescriptor(off64_t* outStart, off64_t* outLength) const |
| { |
| if (mMap.has_value()) { |
| if (mFd.ok()) { |
| *outStart = mMap->offset(); |
| *outLength = mMap->length(); |
| const int fd = dup(mFd); |
| if (fd < 0) { |
| ALOGE("Unable to dup fd (%d).", mFd.get()); |
| return -1; |
| } |
| lseek64(fd, 0, SEEK_SET); |
| return fd; |
| } |
| const char* fname = mMap->file_name(); |
| if (fname == NULL) { |
| fname = mFileName; |
| } |
| if (fname == NULL) { |
| return -1; |
| } |
| *outStart = mMap->offset(); |
| *outLength = mMap->length(); |
| return open(fname, O_RDONLY | O_BINARY); |
| } |
| if (mFileName == NULL) { |
| return -1; |
| } |
| *outStart = mStart; |
| *outLength = mLength; |
| return open(mFileName, O_RDONLY | O_BINARY); |
| } |
| |
| incfs::map_ptr<void> _FileAsset::ensureAlignment(const incfs::IncFsFileMap& map) |
| { |
| const auto data = map.data(); |
| if (util::IsFourByteAligned(data)) { |
| // We can return this directly if it is aligned on a word |
| // boundary. |
| ALOGV("Returning aligned FileAsset %p (%s).", this, |
| getAssetSource()); |
| return data; |
| } |
| |
| if (!data.convert<uint8_t>().verify(mLength)) { |
| return NULL; |
| } |
| |
| // If not aligned on a word boundary, then we need to copy it into |
| // our own buffer. |
| ALOGV("Copying FileAsset %p (%s) to buffer size %d to make it aligned.", this, |
| getAssetSource(), (int)mLength); |
| unsigned char* buf = new unsigned char[mLength]; |
| if (buf == NULL) { |
| ALOGE("alloc of %ld bytes failed\n", (long) mLength); |
| return NULL; |
| } |
| |
| memcpy(buf, data.unsafe_ptr(), mLength); |
| mBuf = buf; |
| return buf; |
| } |
| |
| /* |
| * =========================================================================== |
| * _CompressedAsset |
| * =========================================================================== |
| */ |
| |
| /* |
| * Constructor. |
| */ |
| _CompressedAsset::_CompressedAsset(void) |
| : mStart(0), mCompressedLen(0), mUncompressedLen(0), mOffset(0), |
| mFd(-1), mZipInflater(NULL), mBuf(NULL) |
| { |
| // Register the Asset with the global list here after it is fully constructed and its |
| // vtable pointer points to this concrete type. b/31113965 |
| registerAsset(this); |
| } |
| |
| /* |
| * Destructor. Release resources. |
| */ |
| _CompressedAsset::~_CompressedAsset(void) |
| { |
| close(); |
| |
| // Unregister the Asset from the global list here before it is destructed and while its vtable |
| // pointer still points to this concrete type. b/31113965 |
| unregisterAsset(this); |
| } |
| |
| /* |
| * 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, off64_t offset, |
| int compressionMethod, size_t uncompressedLen, size_t compressedLen) |
| { |
| assert(mFd < 0); // no re-open |
| assert(!mMap.has_value()); |
| 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); |
| |
| if (uncompressedLen > StreamingZipInflater::OUTPUT_CHUNK_SIZE) { |
| mZipInflater = new StreamingZipInflater(mFd, offset, uncompressedLen, compressedLen); |
| } |
| |
| 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(incfs::IncFsFileMap&& dataMap, size_t uncompressedLen) |
| { |
| assert(mFd < 0); // no re-open |
| assert(!mMap.has_value()); |
| assert(dataMap != NULL); |
| |
| mMap = std::move(dataMap); |
| mStart = -1; // not used |
| mCompressedLen = mMap->length(); |
| mUncompressedLen = uncompressedLen; |
| assert(mOffset == 0); |
| |
| if (uncompressedLen > StreamingZipInflater::OUTPUT_CHUNK_SIZE) { |
| mZipInflater = new StreamingZipInflater(&(*mMap), uncompressedLen); |
| } |
| 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); |
| |
| /* If we're relying on a streaming inflater, go through that */ |
| if (mZipInflater) { |
| actual = mZipInflater->read(buf, count); |
| } else { |
| 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. |
| */ |
| off64_t _CompressedAsset::seek(off64_t offset, int whence) |
| { |
| off64_t newPosn; |
| |
| // compute new position within chunk |
| newPosn = handleSeek(offset, whence, mOffset, mUncompressedLen); |
| if (newPosn == (off64_t) -1) |
| return newPosn; |
| |
| if (mZipInflater) { |
| mZipInflater->seekAbsolute(newPosn); |
| } |
| mOffset = newPosn; |
| return mOffset; |
| } |
| |
| /* |
| * Close the asset. |
| */ |
| void _CompressedAsset::close(void) |
| { |
| delete[] mBuf; |
| mBuf = NULL; |
| |
| delete mZipInflater; |
| mZipInflater = 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) |
| { |
| unsigned char* buf = NULL; |
| |
| if (mBuf != NULL) |
| return mBuf; |
| |
| /* |
| * Allocate a buffer and read the file into it. |
| */ |
| buf = new unsigned char[mUncompressedLen]; |
| if (buf == NULL) { |
| ALOGW("alloc %ld bytes failed\n", (long) mUncompressedLen); |
| goto bail; |
| } |
| |
| if (mMap.has_value()) { |
| if (!ZipUtils::inflateToBuffer(mMap->data(), buf, |
| 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 - now that we have the full asset in RAM we |
| * no longer need the streaming inflater |
| */ |
| delete mZipInflater; |
| mZipInflater = NULL; |
| |
| mBuf = buf; |
| buf = NULL; |
| |
| bail: |
| delete[] buf; |
| return mBuf; |
| } |
| |
| incfs::map_ptr<void> _CompressedAsset::getIncFsBuffer(bool aligned) { |
| return incfs::map_ptr<void>(getBuffer(aligned)); |
| } |