881 lines
21 KiB
C++
881 lines
21 KiB
C++
|
/*
|
||
|
* 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/StreamingZipInflater.h>
|
||
|
#include <utils/ZipUtils.h>
|
||
|
#include <utils/ZipFileRO.h>
|
||
|
#include <utils/Log.h>
|
||
|
#include <utils/threads.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 Mutex gAssetLock;
|
||
|
static int32_t gCount = 0;
|
||
|
static Asset* gHead = NULL;
|
||
|
static Asset* gTail = NULL;
|
||
|
|
||
|
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());
|
||
|
off_t size = (cur->getLength()+512)/1024;
|
||
|
char buf[64];
|
||
|
sprintf(buf, ": %dK\n", (int)size);
|
||
|
res.append(buf);
|
||
|
}
|
||
|
cur = cur->mNext;
|
||
|
}
|
||
|
|
||
|
return res;
|
||
|
}
|
||
|
|
||
|
Asset::Asset(void)
|
||
|
: mAccessMode(ACCESS_UNKNOWN)
|
||
|
{
|
||
|
AutoMutex _l(gAssetLock);
|
||
|
gCount++;
|
||
|
mNext = mPrev = NULL;
|
||
|
if (gTail == NULL) {
|
||
|
gHead = gTail = this;
|
||
|
} else {
|
||
|
mPrev = gTail;
|
||
|
gTail->mNext = this;
|
||
|
gTail = this;
|
||
|
}
|
||
|
//LOGI("Creating Asset %p #%d\n", this, gCount);
|
||
|
}
|
||
|
|
||
|
Asset::~Asset(void)
|
||
|
{
|
||
|
AutoMutex _l(gAssetLock);
|
||
|
gCount--;
|
||
|
if (gHead == this) {
|
||
|
gHead = mNext;
|
||
|
}
|
||
|
if (gTail == this) {
|
||
|
gTail = mPrev;
|
||
|
}
|
||
|
if (mNext != NULL) {
|
||
|
mNext->mPrev = mPrev;
|
||
|
}
|
||
|
if (mPrev != NULL) {
|
||
|
mPrev->mNext = mNext;
|
||
|
}
|
||
|
mNext = mPrev = NULL;
|
||
|
//LOGI("Destroying Asset in %p #%d\n", this, gCount);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* 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.
|
||
|
LOGV("Returning aligned FileAsset %p (%s).", this,
|
||
|
getAssetSource());
|
||
|
return data;
|
||
|
}
|
||
|
// If not aligned on a word boundary, then we need to copy it into
|
||
|
// our own buffer.
|
||
|
LOGV("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) {
|
||
|
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), mZipInflater(NULL), 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);
|
||
|
|
||
|
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(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);
|
||
|
|
||
|
if (uncompressedLen > StreamingZipInflater::OUTPUT_CHUNK_SIZE) {
|
||
|
mZipInflater = new StreamingZipInflater(dataMap, 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.
|
||
|
*/
|
||
|
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;
|
||
|
|
||
|
if (mZipInflater) {
|
||
|
mZipInflater->seekAbsolute(newPosn);
|
||
|
}
|
||
|
mOffset = newPosn;
|
||
|
return mOffset;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Close the asset.
|
||
|
*/
|
||
|
void _CompressedAsset::close(void)
|
||
|
{
|
||
|
if (mMap != NULL) {
|
||
|
mMap->release();
|
||
|
mMap = NULL;
|
||
|
}
|
||
|
|
||
|
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 wordAligned)
|
||
|
{
|
||
|
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) {
|
||
|
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 - 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;
|
||
|
}
|
||
|
|