diff options
Diffstat (limited to 'libs/utils')
54 files changed, 23312 insertions, 0 deletions
diff --git a/libs/utils/Android.mk b/libs/utils/Android.mk new file mode 100644 index 0000000..4a68dc1 --- /dev/null +++ b/libs/utils/Android.mk @@ -0,0 +1,148 @@ +# Copyright (C) 2008 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. + +LOCAL_PATH:= $(call my-dir) + +# libutils is a little unique: It's built twice, once for the host +# and once for the device. + +commonSources:= \ + Asset.cpp \ + AssetDir.cpp \ + AssetManager.cpp \ + BufferedTextOutput.cpp \ + CallStack.cpp \ + Debug.cpp \ + FileMap.cpp \ + RefBase.cpp \ + ResourceTypes.cpp \ + SharedBuffer.cpp \ + Static.cpp \ + StopWatch.cpp \ + String8.cpp \ + String16.cpp \ + SystemClock.cpp \ + TextOutput.cpp \ + Threads.cpp \ + TimerProbe.cpp \ + Timers.cpp \ + VectorImpl.cpp \ + ZipFileCRO.cpp \ + ZipFileRO.cpp \ + ZipUtils.cpp \ + misc.cpp \ + ported.cpp \ + LogSocket.cpp + +# +# The cpp files listed here do not belong in the device +# build. Consult with the swetland before even thinking about +# putting them in commonSources. +# +# They're used by the simulator runtime and by host-side tools like +# aapt and the simulator front-end. +# +hostSources:= \ + InetAddress.cpp \ + Pipe.cpp \ + Socket.cpp \ + ZipEntry.cpp \ + ZipFile.cpp + +# For the host +# ===================================================== + +include $(CLEAR_VARS) + +LOCAL_SRC_FILES:= $(commonSources) $(hostSources) + +ifeq ($(HOST_OS),linux) +# Use the futex based mutex and condition variable +# implementation from android-arm because it's shared mem safe + LOCAL_SRC_FILES += \ + futex_synchro.c \ + executablepath_linux.cpp +endif +ifeq ($(HOST_OS),darwin) + LOCAL_SRC_FILES += \ + executablepath_darwin.cpp +endif + +LOCAL_MODULE:= libutils + +LOCAL_CFLAGS += -DLIBUTILS_NATIVE=1 $(TOOL_CFLAGS) +LOCAL_C_INCLUDES += external/zlib + +ifeq ($(HOST_OS),windows) +ifeq ($(strip $(USE_CYGWIN),),) +# Under MinGW, ctype.h doesn't need multi-byte support +LOCAL_CFLAGS += -DMB_CUR_MAX=1 +endif +endif + +include $(BUILD_HOST_STATIC_LIBRARY) + + + +# For the device +# ===================================================== +include $(CLEAR_VARS) + + +# we have the common sources, plus some device-specific stuff +LOCAL_SRC_FILES:= \ + $(commonSources) \ + Binder.cpp \ + BpBinder.cpp \ + IInterface.cpp \ + IMemory.cpp \ + IPCThreadState.cpp \ + MemoryDealer.cpp \ + MemoryBase.cpp \ + MemoryHeapBase.cpp \ + MemoryHeapPmem.cpp \ + Parcel.cpp \ + ProcessState.cpp \ + IPermissionController.cpp \ + IServiceManager.cpp \ + Unicode.cpp + +ifeq ($(TARGET_SIMULATOR),true) +LOCAL_SRC_FILES += $(hostSources) +endif + +ifeq ($(TARGET_OS),linux) +# Use the futex based mutex and condition variable +# implementation from android-arm because it's shared mem safe +LOCAL_SRC_FILES += futex_synchro.c +LOCAL_LDLIBS += -lrt -ldl +endif + +LOCAL_C_INCLUDES += \ + external/zlib \ + external/icu4c/common +LOCAL_LDLIBS += -lpthread + +LOCAL_SHARED_LIBRARIES := \ + libz \ + liblog \ + libcutils + +LOCAL_MODULE:= libutils + +#LOCAL_CFLAGS+= +#LOCAL_LDFLAGS:= + +include $(BUILD_SHARED_LIBRARY) + 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; +} + diff --git a/libs/utils/AssetDir.cpp b/libs/utils/AssetDir.cpp new file mode 100644 index 0000000..c5f664e --- /dev/null +++ b/libs/utils/AssetDir.cpp @@ -0,0 +1,66 @@ +/* + * 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 virtual directory in "asset space". Most of the +// implementation is in the header file or in friend functions in +// AssetManager. +// +#include <utils/AssetDir.h> + +using namespace android; + + +/* + * Find a matching entry in a vector of FileInfo. Because it's sorted, we + * can use a binary search. + * + * Assumes the vector is sorted in ascending order. + */ +/*static*/ int AssetDir::FileInfo::findEntry(const SortedVector<FileInfo>* pVector, + const String8& fileName) +{ + FileInfo tmpInfo; + + tmpInfo.setFileName(fileName); + return pVector->indexOf(tmpInfo); + +#if 0 // don't need this after all (uses 1/2 compares of SortedVector though) + int lo, hi, cur; + + lo = 0; + hi = pVector->size() -1; + while (lo <= hi) { + int cmp; + + cur = (hi + lo) / 2; + cmp = strcmp(pVector->itemAt(cur).getFileName(), fileName); + if (cmp == 0) { + /* match, bail */ + return cur; + } else if (cmp < 0) { + /* too low */ + lo = cur + 1; + } else { + /* too high */ + hi = cur -1; + } + } + + return -1; +#endif +} + diff --git a/libs/utils/AssetManager.cpp b/libs/utils/AssetManager.cpp new file mode 100644 index 0000000..447b801 --- /dev/null +++ b/libs/utils/AssetManager.cpp @@ -0,0 +1,1637 @@ +/* + * 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 read-only assets. +// + +#define LOG_TAG "asset" +//#define LOG_NDEBUG 0 + +#include <utils/AssetManager.h> +#include <utils/AssetDir.h> +#include <utils/Asset.h> +#include <utils/Atomic.h> +#include <utils/String8.h> +#include <utils/ResourceTypes.h> +#include <utils/String8.h> +#include <utils/ZipFileRO.h> +#include <utils/Log.h> +#include <utils/Timers.h> +#include <utils/threads.h> + +#include <dirent.h> +#include <errno.h> +#include <assert.h> + +using namespace android; + +/* + * Names for default app, locale, and vendor. We might want to change + * these to be an actual locale, e.g. always use en-US as the default. + */ +static const char* kDefaultLocale = "default"; +static const char* kDefaultVendor = "default"; +static const char* kAssetsRoot = "assets"; +static const char* kAppZipName = NULL; //"classes.jar"; +static const char* kSystemAssets = "framework/framework-res.apk"; + +static const char* kExcludeExtension = ".EXCLUDE"; + +static Asset* const kExcludedAsset = (Asset*) 0xd000000d; + +static volatile int32_t gCount = 0; + + +/* + * =========================================================================== + * AssetManager + * =========================================================================== + */ + +int32_t AssetManager::getGlobalCount() +{ + return gCount; +} + +AssetManager::AssetManager(CacheMode cacheMode) + : mLocale(NULL), mVendor(NULL), + mResources(NULL), mConfig(new ResTable_config), + mCacheMode(cacheMode), mCacheValid(false) +{ + int count = android_atomic_inc(&gCount)+1; + //LOGI("Creating AssetManager %p #%d\n", this, count); + memset(mConfig, 0, sizeof(ResTable_config)); +} + +AssetManager::~AssetManager(void) +{ + int count = android_atomic_dec(&gCount); + //LOGI("Destroying AssetManager in %p #%d\n", this, count); + + delete mConfig; + delete mResources; + + // don't have a String class yet, so make sure we clean up + delete[] mLocale; + delete[] mVendor; +} + +bool AssetManager::addAssetPath(const String8& path, void** cookie) +{ + AutoMutex _l(mLock); + + asset_path ap; + + String8 realPath(path); + if (kAppZipName) { + realPath.appendPath(kAppZipName); + } + ap.type = ::getFileType(realPath.string()); + if (ap.type == kFileTypeRegular) { + ap.path = realPath; + } else { + ap.path = path; + ap.type = ::getFileType(path.string()); + if (ap.type != kFileTypeDirectory && ap.type != kFileTypeRegular) { + LOGW("Asset path %s is neither a directory nor file (type=%d).", + path.string(), (int)ap.type); + return false; + } + } + + // Skip if we have it already. + for (size_t i=0; i<mAssetPaths.size(); i++) { + if (mAssetPaths[i].path == ap.path) { + if (cookie) { + *cookie = (void*)(i+1); + } + return true; + } + } + + LOGV("In %p Asset %s path: %s", this, + ap.type == kFileTypeDirectory ? "dir" : "zip", ap.path.string()); + + mAssetPaths.add(ap); + + // new paths are always added at the end + if (cookie) { + *cookie = (void*)mAssetPaths.size(); + } + + return true; +} + +bool AssetManager::addDefaultAssets() +{ + const char* root = getenv("ANDROID_ROOT"); + LOG_ALWAYS_FATAL_IF(root == NULL, "ANDROID_ROOT not set"); + + String8 path(root); + path.appendPath(kSystemAssets); + + return addAssetPath(path, NULL); +} + +void* AssetManager::nextAssetPath(void* cookie) const +{ + AutoMutex _l(mLock); + size_t next = ((size_t)cookie)+1; + return next > mAssetPaths.size() ? NULL : (void*)next; +} + +String8 AssetManager::getAssetPath(void* cookie) const +{ + AutoMutex _l(mLock); + const size_t which = ((size_t)cookie)-1; + if (which < mAssetPaths.size()) { + return mAssetPaths[which].path; + } + return String8(); +} + +/* + * Set the current locale. Use NULL to indicate no locale. + * + * Close and reopen Zip archives as appropriate, and reset cached + * information in the locale-specific sections of the tree. + */ +void AssetManager::setLocale(const char* locale) +{ + AutoMutex _l(mLock); + setLocaleLocked(locale); +} + +void AssetManager::setLocaleLocked(const char* locale) +{ + if (mLocale != NULL) { + /* previously set, purge cached data */ + purgeFileNameCacheLocked(); + //mZipSet.purgeLocale(); + delete[] mLocale; + } + mLocale = strdupNew(locale); + + updateResourceParamsLocked(); +} + +/* + * Set the current vendor. Use NULL to indicate no vendor. + * + * Close and reopen Zip archives as appropriate, and reset cached + * information in the vendor-specific sections of the tree. + */ +void AssetManager::setVendor(const char* vendor) +{ + AutoMutex _l(mLock); + + if (mVendor != NULL) { + /* previously set, purge cached data */ + purgeFileNameCacheLocked(); + //mZipSet.purgeVendor(); + delete[] mVendor; + } + mVendor = strdupNew(vendor); +} + +void AssetManager::setConfiguration(const ResTable_config& config, const char* locale) +{ + AutoMutex _l(mLock); + *mConfig = config; + if (locale) { + setLocaleLocked(locale); + } else if (config.language[0] != 0) { + char spec[9]; + spec[0] = config.language[0]; + spec[1] = config.language[1]; + if (config.country[0] != 0) { + spec[2] = '_'; + spec[3] = config.country[0]; + spec[4] = config.country[1]; + spec[5] = 0; + } else { + spec[3] = 0; + } + setLocaleLocked(spec); + } else { + updateResourceParamsLocked(); + } +} + +/* + * Open an asset. + * + * The data could be; + * - In a file on disk (assetBase + fileName). + * - In a compressed file on disk (assetBase + fileName.gz). + * - In a Zip archive, uncompressed or compressed. + * + * It can be in a number of different directories and Zip archives. + * The search order is: + * - [appname] + * - locale + vendor + * - "default" + vendor + * - locale + "default" + * - "default + "default" + * - "common" + * - (same as above) + * + * To find a particular file, we have to try up to eight paths with + * all three forms of data. + * + * We should probably reject requests for "illegal" filenames, e.g. those + * with illegal characters or "../" backward relative paths. + */ +Asset* AssetManager::open(const char* fileName, AccessMode mode) +{ + AutoMutex _l(mLock); + + LOG_FATAL_IF(mAssetPaths.size() == 0, "No assets added to AssetManager"); + + + if (mCacheMode != CACHE_OFF && !mCacheValid) + loadFileNameCacheLocked(); + + String8 assetName(kAssetsRoot); + assetName.appendPath(fileName); + + /* + * For each top-level asset path, search for the asset. + */ + + size_t i = mAssetPaths.size(); + while (i > 0) { + i--; + LOGV("Looking for asset '%s' in '%s'\n", + assetName.string(), mAssetPaths.itemAt(i).path.string()); + Asset* pAsset = openNonAssetInPathLocked(assetName.string(), mode, mAssetPaths.itemAt(i)); + if (pAsset != NULL) { + return pAsset != kExcludedAsset ? pAsset : NULL; + } + } + + return NULL; +} + +/* + * Open a non-asset file as if it were an asset. + * + * The "fileName" is the partial path starting from the application + * name. + */ +Asset* AssetManager::openNonAsset(const char* fileName, AccessMode mode) +{ + AutoMutex _l(mLock); + + LOG_FATAL_IF(mAssetPaths.size() == 0, "No assets added to AssetManager"); + + + if (mCacheMode != CACHE_OFF && !mCacheValid) + loadFileNameCacheLocked(); + + /* + * For each top-level asset path, search for the asset. + */ + + size_t i = mAssetPaths.size(); + while (i > 0) { + i--; + LOGV("Looking for non-asset '%s' in '%s'\n", fileName, mAssetPaths.itemAt(i).path.string()); + Asset* pAsset = openNonAssetInPathLocked( + fileName, mode, mAssetPaths.itemAt(i)); + if (pAsset != NULL) { + return pAsset != kExcludedAsset ? pAsset : NULL; + } + } + + return NULL; +} + +Asset* AssetManager::openNonAsset(void* cookie, const char* fileName, AccessMode mode) +{ + const size_t which = ((size_t)cookie)-1; + + AutoMutex _l(mLock); + + LOG_FATAL_IF(mAssetPaths.size() == 0, "No assets added to AssetManager"); + + + if (mCacheMode != CACHE_OFF && !mCacheValid) + loadFileNameCacheLocked(); + + if (which < mAssetPaths.size()) { + LOGV("Looking for non-asset '%s' in '%s'\n", fileName, + mAssetPaths.itemAt(which).path.string()); + Asset* pAsset = openNonAssetInPathLocked( + fileName, mode, mAssetPaths.itemAt(which)); + if (pAsset != NULL) { + return pAsset != kExcludedAsset ? pAsset : NULL; + } + } + + return NULL; +} + +/* + * Get the type of a file in the asset namespace. + * + * This currently only works for regular files. All others (including + * directories) will return kFileTypeNonexistent. + */ +FileType AssetManager::getFileType(const char* fileName) +{ + Asset* pAsset = NULL; + + /* + * Open the asset. This is less efficient than simply finding the + * file, but it's not too bad (we don't uncompress or mmap data until + * the first read() call). + */ + pAsset = open(fileName, Asset::ACCESS_STREAMING); + delete pAsset; + + if (pAsset == NULL) + return kFileTypeNonexistent; + else + return kFileTypeRegular; +} + +const ResTable* AssetManager::getResTable(bool required) const +{ + ResTable* rt = mResources; + if (rt) { + return rt; + } + + // Iterate through all asset packages, collecting resources from each. + + AutoMutex _l(mLock); + + if (mResources != NULL) { + return mResources; + } + + if (required) { + LOG_FATAL_IF(mAssetPaths.size() == 0, "No assets added to AssetManager"); + } + + if (mCacheMode != CACHE_OFF && !mCacheValid) + const_cast<AssetManager*>(this)->loadFileNameCacheLocked(); + + const size_t N = mAssetPaths.size(); + for (size_t i=0; i<N; i++) { + Asset* ass = NULL; + bool shared = true; + const asset_path& ap = mAssetPaths.itemAt(i); + LOGV("Looking for resource asset in '%s'\n", ap.path.string()); + if (ap.type != kFileTypeDirectory) { + ass = const_cast<AssetManager*>(this)-> + mZipSet.getZipResourceTable(ap.path); + if (ass == NULL) { + LOGV("loading resource table %s\n", ap.path.string()); + ass = const_cast<AssetManager*>(this)-> + openNonAssetInPathLocked("resources.arsc", + Asset::ACCESS_BUFFER, + ap); + if (ass != NULL && ass != kExcludedAsset) { + ass = const_cast<AssetManager*>(this)-> + mZipSet.setZipResourceTable(ap.path, ass); + } + } + } else { + LOGV("loading resource table %s\n", ap.path.string()); + Asset* ass = const_cast<AssetManager*>(this)-> + openNonAssetInPathLocked("resources.arsc", + Asset::ACCESS_BUFFER, + ap); + shared = false; + } + if (ass != NULL && ass != kExcludedAsset) { + if (rt == NULL) { + mResources = rt = new ResTable(); + updateResourceParamsLocked(); + } + LOGV("Installing resource asset %p in to table %p\n", ass, mResources); + rt->add(ass, (void*)(i+1), !shared); + + if (!shared) { + delete ass; + } + } + } + + if (required && !rt) LOGW("Unable to find resources file resources.arsc"); + if (!rt) { + mResources = rt = new ResTable(); + } + return rt; +} + +void AssetManager::updateResourceParamsLocked() const +{ + ResTable* res = mResources; + if (!res) { + return; + } + + size_t llen = mLocale ? strlen(mLocale) : 0; + mConfig->language[0] = 0; + mConfig->language[1] = 0; + mConfig->country[0] = 0; + mConfig->country[1] = 0; + if (llen >= 2) { + mConfig->language[0] = mLocale[0]; + mConfig->language[1] = mLocale[1]; + } + if (llen >= 5) { + mConfig->country[0] = mLocale[3]; + mConfig->country[1] = mLocale[4]; + } + mConfig->size = sizeof(*mConfig); + + res->setParameters(mConfig); +} + +const ResTable& AssetManager::getResources(bool required) const +{ + const ResTable* rt = getResTable(required); + return *rt; +} + +bool AssetManager::isUpToDate() +{ + AutoMutex _l(mLock); + return mZipSet.isUpToDate(); +} + +void AssetManager::getLocales(Vector<String8>* locales) const +{ + ResTable* res = mResources; + if (res != NULL) { + res->getLocales(locales); + } +} + +/* + * Open a non-asset file as if it were an asset, searching for it in the + * specified app. + * + * Pass in a NULL values for "appName" if the common app directory should + * be used. + */ +Asset* AssetManager::openNonAssetInPathLocked(const char* fileName, AccessMode mode, + const asset_path& ap) +{ + Asset* pAsset = NULL; + + /* look at the filesystem on disk */ + if (ap.type == kFileTypeDirectory) { + String8 path(ap.path); + path.appendPath(fileName); + + pAsset = openAssetFromFileLocked(path, mode); + + if (pAsset == NULL) { + /* try again, this time with ".gz" */ + path.append(".gz"); + pAsset = openAssetFromFileLocked(path, mode); + } + + if (pAsset != NULL) { + //printf("FOUND NA '%s' on disk\n", fileName); + pAsset->setAssetSource(path); + } + + /* look inside the zip file */ + } else { + String8 path(fileName); + + /* check the appropriate Zip file */ + ZipFileRO* pZip; + ZipEntryRO entry; + + pZip = getZipFileLocked(ap); + if (pZip != NULL) { + //printf("GOT zip, checking NA '%s'\n", (const char*) path); + entry = pZip->findEntryByName(path.string()); + if (entry != NULL) { + //printf("FOUND NA in Zip file for %s\n", appName ? appName : kAppCommon); + pAsset = openAssetFromZipLocked(pZip, entry, mode, path); + } + } + + if (pAsset != NULL) { + /* create a "source" name, for debug/display */ + pAsset->setAssetSource( + createZipSourceNameLocked(ZipSet::getPathName(ap.path.string()), String8(""), + String8(fileName))); + } + } + + return pAsset; +} + +/* + * Open an asset, searching for it in the directory hierarchy for the + * specified app. + * + * Pass in a NULL values for "appName" if the common app directory should + * be used. + */ +Asset* AssetManager::openInPathLocked(const char* fileName, AccessMode mode, + const asset_path& ap) +{ + Asset* pAsset = NULL; + + /* + * Try various combinations of locale and vendor. + */ + if (mLocale != NULL && mVendor != NULL) + pAsset = openInLocaleVendorLocked(fileName, mode, ap, mLocale, mVendor); + if (pAsset == NULL && mVendor != NULL) + pAsset = openInLocaleVendorLocked(fileName, mode, ap, NULL, mVendor); + if (pAsset == NULL && mLocale != NULL) + pAsset = openInLocaleVendorLocked(fileName, mode, ap, mLocale, NULL); + if (pAsset == NULL) + pAsset = openInLocaleVendorLocked(fileName, mode, ap, NULL, NULL); + + return pAsset; +} + +/* + * Open an asset, searching for it in the directory hierarchy for the + * specified locale and vendor. + * + * We also search in "app.jar". + * + * Pass in NULL values for "appName", "locale", and "vendor" if the + * defaults should be used. + */ +Asset* AssetManager::openInLocaleVendorLocked(const char* fileName, AccessMode mode, + const asset_path& ap, const char* locale, const char* vendor) +{ + Asset* pAsset = NULL; + + if (ap.type == kFileTypeDirectory) { + if (mCacheMode == CACHE_OFF) { + /* look at the filesystem on disk */ + String8 path(createPathNameLocked(ap, locale, vendor)); + path.appendPath(fileName); + + String8 excludeName(path); + excludeName.append(kExcludeExtension); + if (::getFileType(excludeName.string()) != kFileTypeNonexistent) { + /* say no more */ + //printf("+++ excluding '%s'\n", (const char*) excludeName); + return kExcludedAsset; + } + + pAsset = openAssetFromFileLocked(path, mode); + + if (pAsset == NULL) { + /* try again, this time with ".gz" */ + path.append(".gz"); + pAsset = openAssetFromFileLocked(path, mode); + } + + if (pAsset != NULL) + pAsset->setAssetSource(path); + } else { + /* find in cache */ + String8 path(createPathNameLocked(ap, locale, vendor)); + path.appendPath(fileName); + + AssetDir::FileInfo tmpInfo; + bool found = false; + + String8 excludeName(path); + excludeName.append(kExcludeExtension); + + if (mCache.indexOf(excludeName) != NAME_NOT_FOUND) { + /* go no farther */ + //printf("+++ Excluding '%s'\n", (const char*) excludeName); + return kExcludedAsset; + } + + /* + * File compression extensions (".gz") don't get stored in the + * name cache, so we have to try both here. + */ + if (mCache.indexOf(path) != NAME_NOT_FOUND) { + found = true; + pAsset = openAssetFromFileLocked(path, mode); + if (pAsset == NULL) { + /* try again, this time with ".gz" */ + path.append(".gz"); + pAsset = openAssetFromFileLocked(path, mode); + } + } + + if (pAsset != NULL) + pAsset->setAssetSource(path); + + /* + * Don't continue the search into the Zip files. Our cached info + * said it was a file on disk; to be consistent with openDir() + * we want to return the loose asset. If the cached file gets + * removed, we fail. + * + * The alternative is to update our cache when files get deleted, + * or make some sort of "best effort" promise, but for now I'm + * taking the hard line. + */ + if (found) { + if (pAsset == NULL) + LOGD("Expected file not found: '%s'\n", path.string()); + return pAsset; + } + } + } + + /* + * Either it wasn't found on disk or on the cached view of the disk. + * Dig through the currently-opened set of Zip files. If caching + * is disabled, the Zip file may get reopened. + */ + if (pAsset == NULL && ap.type == kFileTypeRegular) { + String8 path; + + path.appendPath((locale != NULL) ? locale : kDefaultLocale); + path.appendPath((vendor != NULL) ? vendor : kDefaultVendor); + path.appendPath(fileName); + + /* check the appropriate Zip file */ + ZipFileRO* pZip; + ZipEntryRO entry; + + pZip = getZipFileLocked(ap); + if (pZip != NULL) { + //printf("GOT zip, checking '%s'\n", (const char*) path); + entry = pZip->findEntryByName(path.string()); + if (entry != NULL) { + //printf("FOUND in Zip file for %s/%s-%s\n", + // appName, locale, vendor); + pAsset = openAssetFromZipLocked(pZip, entry, mode, path); + } + } + + if (pAsset != NULL) { + /* create a "source" name, for debug/display */ + pAsset->setAssetSource(createZipSourceNameLocked(ZipSet::getPathName(ap.path.string()), + String8(""), String8(fileName))); + } + } + + return pAsset; +} + +/* + * Create a "source name" for a file from a Zip archive. + */ +String8 AssetManager::createZipSourceNameLocked(const String8& zipFileName, + const String8& dirName, const String8& fileName) +{ + String8 sourceName("zip:"); + sourceName.append(zipFileName); + sourceName.append(":"); + if (dirName.length() > 0) { + sourceName.appendPath(dirName); + } + sourceName.appendPath(fileName); + return sourceName; +} + +/* + * Create a path to a loose asset (asset-base/app/locale/vendor). + */ +String8 AssetManager::createPathNameLocked(const asset_path& ap, const char* locale, + const char* vendor) +{ + String8 path(ap.path); + path.appendPath((locale != NULL) ? locale : kDefaultLocale); + path.appendPath((vendor != NULL) ? vendor : kDefaultVendor); + return path; +} + +/* + * Create a path to a loose asset (asset-base/app/rootDir). + */ +String8 AssetManager::createPathNameLocked(const asset_path& ap, const char* rootDir) +{ + String8 path(ap.path); + if (rootDir != NULL) path.appendPath(rootDir); + return path; +} + +/* + * Return a pointer to one of our open Zip archives. Returns NULL if no + * matching Zip file exists. + * + * Right now we have 2 possible Zip files (1 each in app/"common"). + * + * If caching is set to CACHE_OFF, to get the expected behavior we + * need to reopen the Zip file on every request. That would be silly + * and expensive, so instead we just check the file modification date. + * + * Pass in NULL values for "appName", "locale", and "vendor" if the + * generics should be used. + */ +ZipFileRO* AssetManager::getZipFileLocked(const asset_path& ap) +{ + LOGV("getZipFileLocked() in %p\n", this); + + return mZipSet.getZip(ap.path); +} + +/* + * Try to open an asset from a file on disk. + * + * If the file is compressed with gzip, we seek to the start of the + * deflated data and pass that in (just like we would for a Zip archive). + * + * For uncompressed data, we may already have an mmap()ed version sitting + * around. If so, we want to hand that to the Asset instead. + * + * This returns NULL if the file doesn't exist, couldn't be opened, or + * claims to be a ".gz" but isn't. + */ +Asset* AssetManager::openAssetFromFileLocked(const String8& pathName, + AccessMode mode) +{ + Asset* pAsset = NULL; + + if (strcasecmp(pathName.getPathExtension().string(), ".gz") == 0) { + //printf("TRYING '%s'\n", (const char*) pathName); + pAsset = Asset::createFromCompressedFile(pathName.string(), mode); + } else { + //printf("TRYING '%s'\n", (const char*) pathName); + pAsset = Asset::createFromFile(pathName.string(), mode); + } + + return pAsset; +} + +/* + * Given an entry in a Zip archive, create a new Asset object. + * + * If the entry is uncompressed, we may want to create or share a + * slice of shared memory. + */ +Asset* AssetManager::openAssetFromZipLocked(const ZipFileRO* pZipFile, + const ZipEntryRO entry, AccessMode mode, const String8& entryName) +{ + Asset* pAsset = NULL; + + // TODO: look for previously-created shared memory slice? + int method; + long uncompressedLen; + + //printf("USING Zip '%s'\n", pEntry->getFileName()); + + //pZipFile->getEntryInfo(entry, &method, &uncompressedLen, &compressedLen, + // &offset); + if (!pZipFile->getEntryInfo(entry, &method, &uncompressedLen, NULL, NULL, + NULL, NULL)) + { + LOGW("getEntryInfo failed\n"); + return NULL; + } + + FileMap* dataMap = pZipFile->createEntryFileMap(entry); + if (dataMap == NULL) { + LOGW("create map from entry failed\n"); + return NULL; + } + + if (method == ZipFileRO::kCompressStored) { + pAsset = Asset::createFromUncompressedMap(dataMap, mode); + LOGV("Opened uncompressed entry %s in zip %s mode %d: %p", entryName.string(), + dataMap->getFileName(), mode, pAsset); + } else { + pAsset = Asset::createFromCompressedMap(dataMap, method, + uncompressedLen, mode); + LOGV("Opened compressed entry %s in zip %s mode %d: %p", entryName.string(), + dataMap->getFileName(), mode, pAsset); + } + if (pAsset == NULL) { + /* unexpected */ + LOGW("create from segment failed\n"); + } + + return pAsset; +} + + + +/* + * Open a directory in the asset namespace. + * + * An "asset directory" is simply the combination of all files in all + * locations, with ".gz" stripped for loose files. With app, locale, and + * vendor defined, we have 8 directories and 2 Zip archives to scan. + * + * Pass in "" for the root dir. + */ +AssetDir* AssetManager::openDir(const char* dirName) +{ + AutoMutex _l(mLock); + + AssetDir* pDir = NULL; + SortedVector<AssetDir::FileInfo>* pMergedInfo = NULL; + + LOG_FATAL_IF(mAssetPaths.size() == 0, "No assets added to AssetManager"); + assert(dirName != NULL); + + //printf("+++ openDir(%s) in '%s'\n", dirName, (const char*) mAssetBase); + + if (mCacheMode != CACHE_OFF && !mCacheValid) + loadFileNameCacheLocked(); + + pDir = new AssetDir; + + /* + * Scan the various directories, merging what we find into a single + * vector. We want to scan them in reverse priority order so that + * the ".EXCLUDE" processing works correctly. Also, if we decide we + * want to remember where the file is coming from, we'll get the right + * version. + * + * We start with Zip archives, then do loose files. + */ + pMergedInfo = new SortedVector<AssetDir::FileInfo>; + + size_t i = mAssetPaths.size(); + while (i > 0) { + i--; + const asset_path& ap = mAssetPaths.itemAt(i); + if (ap.type == kFileTypeRegular) { + LOGV("Adding directory %s from zip %s", dirName, ap.path.string()); + scanAndMergeZipLocked(pMergedInfo, ap, kAssetsRoot, dirName); + } else { + LOGV("Adding directory %s from dir %s", dirName, ap.path.string()); + scanAndMergeDirLocked(pMergedInfo, ap, kAssetsRoot, dirName); + } + } + +#if 0 + printf("FILE LIST:\n"); + for (i = 0; i < (size_t) pMergedInfo->size(); i++) { + printf(" %d: (%d) '%s'\n", i, + pMergedInfo->itemAt(i).getFileType(), + (const char*) pMergedInfo->itemAt(i).getFileName()); + } +#endif + + pDir->setFileList(pMergedInfo); + return pDir; +} + +/* + * Scan the contents of the specified directory and merge them into the + * "pMergedInfo" vector, removing previous entries if we find "exclude" + * directives. + * + * Returns "false" if we found nothing to contribute. + */ +bool AssetManager::scanAndMergeDirLocked(SortedVector<AssetDir::FileInfo>* pMergedInfo, + const asset_path& ap, const char* rootDir, const char* dirName) +{ + SortedVector<AssetDir::FileInfo>* pContents; + String8 path; + + assert(pMergedInfo != NULL); + + //printf("scanAndMergeDir: %s %s %s %s\n", appName, locale, vendor,dirName); + + if (mCacheValid) { + int i, start, count; + + pContents = new SortedVector<AssetDir::FileInfo>; + + /* + * Get the basic partial path and find it in the cache. That's + * the start point for the search. + */ + path = createPathNameLocked(ap, rootDir); + if (dirName[0] != '\0') + path.appendPath(dirName); + + start = mCache.indexOf(path); + if (start == NAME_NOT_FOUND) { + //printf("+++ not found in cache: dir '%s'\n", (const char*) path); + delete pContents; + return false; + } + + /* + * The match string looks like "common/default/default/foo/bar/". + * The '/' on the end ensures that we don't match on the directory + * itself or on ".../foo/barfy/". + */ + path.append("/"); + + count = mCache.size(); + + /* + * Pick out the stuff in the current dir by examining the pathname. + * It needs to match the partial pathname prefix, and not have a '/' + * (fssep) anywhere after the prefix. + */ + for (i = start+1; i < count; i++) { + if (mCache[i].getFileName().length() > path.length() && + strncmp(mCache[i].getFileName().string(), path.string(), path.length()) == 0) + { + const char* name = mCache[i].getFileName().string(); + // XXX THIS IS BROKEN! Looks like we need to store the full + // path prefix separately from the file path. + if (strchr(name + path.length(), '/') == NULL) { + /* grab it, reducing path to just the filename component */ + AssetDir::FileInfo tmp = mCache[i]; + tmp.setFileName(tmp.getFileName().getPathLeaf()); + pContents->add(tmp); + } + } else { + /* no longer in the dir or its subdirs */ + break; + } + + } + } else { + path = createPathNameLocked(ap, rootDir); + if (dirName[0] != '\0') + path.appendPath(dirName); + pContents = scanDirLocked(path); + if (pContents == NULL) + return false; + } + + // if we wanted to do an incremental cache fill, we would do it here + + /* + * Process "exclude" directives. If we find a filename that ends with + * ".EXCLUDE", we look for a matching entry in the "merged" set, and + * remove it if we find it. We also delete the "exclude" entry. + */ + int i, count, exclExtLen; + + count = pContents->size(); + exclExtLen = strlen(kExcludeExtension); + for (i = 0; i < count; i++) { + const char* name; + int nameLen; + + name = pContents->itemAt(i).getFileName().string(); + nameLen = strlen(name); + if (nameLen > exclExtLen && + strcmp(name + (nameLen - exclExtLen), kExcludeExtension) == 0) + { + String8 match(name, nameLen - exclExtLen); + int matchIdx; + + matchIdx = AssetDir::FileInfo::findEntry(pMergedInfo, match); + if (matchIdx > 0) { + LOGV("Excluding '%s' [%s]\n", + pMergedInfo->itemAt(matchIdx).getFileName().string(), + pMergedInfo->itemAt(matchIdx).getSourceName().string()); + pMergedInfo->removeAt(matchIdx); + } else { + //printf("+++ no match on '%s'\n", (const char*) match); + } + + LOGD("HEY: size=%d removing %d\n", (int)pContents->size(), i); + pContents->removeAt(i); + i--; // adjust "for" loop + count--; // and loop limit + } + } + + mergeInfoLocked(pMergedInfo, pContents); + + delete pContents; + + return true; +} + +/* + * Scan the contents of the specified directory, and stuff what we find + * into a newly-allocated vector. + * + * Files ending in ".gz" will have their extensions removed. + * + * We should probably think about skipping files with "illegal" names, + * e.g. illegal characters (/\:) or excessive length. + * + * Returns NULL if the specified directory doesn't exist. + */ +SortedVector<AssetDir::FileInfo>* AssetManager::scanDirLocked(const String8& path) +{ + SortedVector<AssetDir::FileInfo>* pContents = NULL; + DIR* dir; + struct dirent* entry; + FileType fileType; + + LOGV("Scanning dir '%s'\n", path.string()); + + dir = opendir(path.string()); + if (dir == NULL) + return NULL; + + pContents = new SortedVector<AssetDir::FileInfo>; + + while (1) { + entry = readdir(dir); + if (entry == NULL) + break; + + if (strcmp(entry->d_name, ".") == 0 || + strcmp(entry->d_name, "..") == 0) + continue; + +#ifdef _DIRENT_HAVE_D_TYPE + if (entry->d_type == DT_REG) + fileType = kFileTypeRegular; + else if (entry->d_type == DT_DIR) + fileType = kFileTypeDirectory; + else + fileType = kFileTypeUnknown; +#else + // stat the file + fileType = ::getFileType(path.appendPathCopy(entry->d_name).string()); +#endif + + if (fileType != kFileTypeRegular && fileType != kFileTypeDirectory) + continue; + + AssetDir::FileInfo info; + info.set(String8(entry->d_name), fileType); + if (strcasecmp(info.getFileName().getPathExtension().string(), ".gz") == 0) + info.setFileName(info.getFileName().getBasePath()); + info.setSourceName(path.appendPathCopy(info.getFileName())); + pContents->add(info); + } + + closedir(dir); + return pContents; +} + +/* + * Scan the contents out of the specified Zip archive, and merge what we + * find into "pMergedInfo". If the Zip archive in question doesn't exist, + * we return immediately. + * + * Returns "false" if we found nothing to contribute. + */ +bool AssetManager::scanAndMergeZipLocked(SortedVector<AssetDir::FileInfo>* pMergedInfo, + const asset_path& ap, const char* rootDir, const char* baseDirName) +{ + ZipFileRO* pZip; + Vector<String8> dirs; + AssetDir::FileInfo info; + SortedVector<AssetDir::FileInfo> contents; + String8 sourceName, zipName, dirName; + + pZip = mZipSet.getZip(ap.path); + if (pZip == NULL) { + LOGW("Failure opening zip %s\n", ap.path.string()); + return false; + } + + zipName = ZipSet::getPathName(ap.path.string()); + + /* convert "sounds" to "rootDir/sounds" */ + if (rootDir != NULL) dirName = rootDir; + dirName.appendPath(baseDirName); + + /* + * Scan through the list of files, looking for a match. The files in + * the Zip table of contents are not in sorted order, so we have to + * process the entire list. We're looking for a string that begins + * with the characters in "dirName", is followed by a '/', and has no + * subsequent '/' in the stuff that follows. + * + * What makes this especially fun is that directories are not stored + * explicitly in Zip archives, so we have to infer them from context. + * When we see "sounds/foo.wav" we have to leave a note to ourselves + * to insert a directory called "sounds" into the list. We store + * these in temporary vector so that we only return each one once. + * + * Name comparisons are case-sensitive to match UNIX filesystem + * semantics. + */ + int dirNameLen = dirName.length(); + for (int i = 0; i < pZip->getNumEntries(); i++) { + ZipEntryRO entry; + char nameBuf[256]; + + entry = pZip->findEntryByIndex(i); + if (pZip->getEntryFileName(entry, nameBuf, sizeof(nameBuf)) != 0) { + // TODO: fix this if we expect to have long names + LOGE("ARGH: name too long?\n"); + continue; + } + if (dirNameLen == 0 || + (strncmp(nameBuf, dirName.string(), dirNameLen) == 0 && + nameBuf[dirNameLen] == '/')) + { + const char* cp; + const char* nextSlash; + + cp = nameBuf + dirNameLen; + if (dirNameLen != 0) + cp++; // advance past the '/' + + nextSlash = strchr(cp, '/'); +//xxx this may break if there are bare directory entries + if (nextSlash == NULL) { + /* this is a file in the requested directory */ + + info.set(String8(nameBuf).getPathLeaf(), kFileTypeRegular); + + info.setSourceName( + createZipSourceNameLocked(zipName, dirName, info.getFileName())); + + contents.add(info); + //printf("FOUND: file '%s'\n", (const char*) info.mFileName); + } else { + /* this is a subdir; add it if we don't already have it*/ + String8 subdirName(cp, nextSlash - cp); + size_t j; + size_t N = dirs.size(); + + for (j = 0; j < N; j++) { + if (subdirName == dirs[j]) { + break; + } + } + if (j == N) { + dirs.add(subdirName); + } + + //printf("FOUND: dir '%s'\n", (const char*) subdirName); + } + } + } + + /* + * Add the set of unique directories. + */ + for (int i = 0; i < (int) dirs.size(); i++) { + info.set(dirs[i], kFileTypeDirectory); + info.setSourceName( + createZipSourceNameLocked(zipName, dirName, info.getFileName())); + contents.add(info); + } + + mergeInfoLocked(pMergedInfo, &contents); + + return true; +} + + +/* + * Merge two vectors of FileInfo. + * + * The merged contents will be stuffed into *pMergedInfo. + * + * If an entry for a file exists in both "pMergedInfo" and "pContents", + * we use the newer "pContents" entry. + */ +void AssetManager::mergeInfoLocked(SortedVector<AssetDir::FileInfo>* pMergedInfo, + const SortedVector<AssetDir::FileInfo>* pContents) +{ + /* + * Merge what we found in this directory with what we found in + * other places. + * + * Two basic approaches: + * (1) Create a new array that holds the unique values of the two + * arrays. + * (2) Take the elements from pContents and shove them into pMergedInfo. + * + * Because these are vectors of complex objects, moving elements around + * inside the vector requires constructing new objects and allocating + * storage for members. With approach #1, we're always adding to the + * end, whereas with #2 we could be inserting multiple elements at the + * front of the vector. Approach #1 requires a full copy of the + * contents of pMergedInfo, but approach #2 requires the same copy for + * every insertion at the front of pMergedInfo. + * + * (We should probably use a SortedVector interface that allows us to + * just stuff items in, trusting us to maintain the sort order.) + */ + SortedVector<AssetDir::FileInfo>* pNewSorted; + int mergeMax, contMax; + int mergeIdx, contIdx; + + pNewSorted = new SortedVector<AssetDir::FileInfo>; + mergeMax = pMergedInfo->size(); + contMax = pContents->size(); + mergeIdx = contIdx = 0; + + while (mergeIdx < mergeMax || contIdx < contMax) { + if (mergeIdx == mergeMax) { + /* hit end of "merge" list, copy rest of "contents" */ + pNewSorted->add(pContents->itemAt(contIdx)); + contIdx++; + } else if (contIdx == contMax) { + /* hit end of "cont" list, copy rest of "merge" */ + pNewSorted->add(pMergedInfo->itemAt(mergeIdx)); + mergeIdx++; + } else if (pMergedInfo->itemAt(mergeIdx) == pContents->itemAt(contIdx)) + { + /* items are identical, add newer and advance both indices */ + pNewSorted->add(pContents->itemAt(contIdx)); + mergeIdx++; + contIdx++; + } else if (pMergedInfo->itemAt(mergeIdx) < pContents->itemAt(contIdx)) + { + /* "merge" is lower, add that one */ + pNewSorted->add(pMergedInfo->itemAt(mergeIdx)); + mergeIdx++; + } else { + /* "cont" is lower, add that one */ + assert(pContents->itemAt(contIdx) < pMergedInfo->itemAt(mergeIdx)); + pNewSorted->add(pContents->itemAt(contIdx)); + contIdx++; + } + } + + /* + * Overwrite the "merged" list with the new stuff. + */ + *pMergedInfo = *pNewSorted; + delete pNewSorted; + +#if 0 // for Vector, rather than SortedVector + int i, j; + for (i = pContents->size() -1; i >= 0; i--) { + bool add = true; + + for (j = pMergedInfo->size() -1; j >= 0; j--) { + /* case-sensitive comparisons, to behave like UNIX fs */ + if (strcmp(pContents->itemAt(i).mFileName, + pMergedInfo->itemAt(j).mFileName) == 0) + { + /* match, don't add this entry */ + add = false; + break; + } + } + + if (add) + pMergedInfo->add(pContents->itemAt(i)); + } +#endif +} + + +/* + * Load all files into the file name cache. We want to do this across + * all combinations of { appname, locale, vendor }, performing a recursive + * directory traversal. + * + * This is not the most efficient data structure. Also, gathering the + * information as we needed it (file-by-file or directory-by-directory) + * would be faster. However, on the actual device, 99% of the files will + * live in Zip archives, so this list will be very small. The trouble + * is that we have to check the "loose" files first, so it's important + * that we don't beat the filesystem silly looking for files that aren't + * there. + * + * Note on thread safety: this is the only function that causes updates + * to mCache, and anybody who tries to use it will call here if !mCacheValid, + * so we need to employ a mutex here. + */ +void AssetManager::loadFileNameCacheLocked(void) +{ + assert(!mCacheValid); + assert(mCache.size() == 0); + +#ifdef DO_TIMINGS // need to link against -lrt for this now + DurationTimer timer; + timer.start(); +#endif + + fncScanLocked(&mCache, ""); + +#ifdef DO_TIMINGS + timer.stop(); + LOGD("Cache scan took %.3fms\n", + timer.durationUsecs() / 1000.0); +#endif + +#if 0 + int i; + printf("CACHED FILE LIST (%d entries):\n", mCache.size()); + for (i = 0; i < (int) mCache.size(); i++) { + printf(" %d: (%d) '%s'\n", i, + mCache.itemAt(i).getFileType(), + (const char*) mCache.itemAt(i).getFileName()); + } +#endif + + mCacheValid = true; +} + +/* + * Scan up to 8 versions of the specified directory. + */ +void AssetManager::fncScanLocked(SortedVector<AssetDir::FileInfo>* pMergedInfo, + const char* dirName) +{ + size_t i = mAssetPaths.size(); + while (i > 0) { + i--; + const asset_path& ap = mAssetPaths.itemAt(i); + fncScanAndMergeDirLocked(pMergedInfo, ap, NULL, NULL, dirName); + if (mLocale != NULL) + fncScanAndMergeDirLocked(pMergedInfo, ap, mLocale, NULL, dirName); + if (mVendor != NULL) + fncScanAndMergeDirLocked(pMergedInfo, ap, NULL, mVendor, dirName); + if (mLocale != NULL && mVendor != NULL) + fncScanAndMergeDirLocked(pMergedInfo, ap, mLocale, mVendor, dirName); + } +} + +/* + * Recursively scan this directory and all subdirs. + * + * This is similar to scanAndMergeDir, but we don't remove the .EXCLUDE + * files, and we prepend the extended partial path to the filenames. + */ +bool AssetManager::fncScanAndMergeDirLocked( + SortedVector<AssetDir::FileInfo>* pMergedInfo, + const asset_path& ap, const char* locale, const char* vendor, + const char* dirName) +{ + SortedVector<AssetDir::FileInfo>* pContents; + String8 partialPath; + String8 fullPath; + + // XXX This is broken -- the filename cache needs to hold the base + // asset path separately from its filename. + + partialPath = createPathNameLocked(ap, locale, vendor); + if (dirName[0] != '\0') { + partialPath.appendPath(dirName); + } + + fullPath = partialPath; + pContents = scanDirLocked(fullPath); + if (pContents == NULL) { + return false; // directory did not exist + } + + /* + * Scan all subdirectories of the current dir, merging what we find + * into "pMergedInfo". + */ + for (int i = 0; i < (int) pContents->size(); i++) { + if (pContents->itemAt(i).getFileType() == kFileTypeDirectory) { + String8 subdir(dirName); + subdir.appendPath(pContents->itemAt(i).getFileName()); + + fncScanAndMergeDirLocked(pMergedInfo, ap, locale, vendor, subdir.string()); + } + } + + /* + * To be consistent, we want entries for the root directory. If + * we're the root, add one now. + */ + if (dirName[0] == '\0') { + AssetDir::FileInfo tmpInfo; + + tmpInfo.set(String8(""), kFileTypeDirectory); + tmpInfo.setSourceName(createPathNameLocked(ap, locale, vendor)); + pContents->add(tmpInfo); + } + + /* + * We want to prepend the extended partial path to every entry in + * "pContents". It's the same value for each entry, so this will + * not change the sorting order of the vector contents. + */ + for (int i = 0; i < (int) pContents->size(); i++) { + const AssetDir::FileInfo& info = pContents->itemAt(i); + pContents->editItemAt(i).setFileName(partialPath.appendPathCopy(info.getFileName())); + } + + mergeInfoLocked(pMergedInfo, pContents); + return true; +} + +/* + * Trash the cache. + */ +void AssetManager::purgeFileNameCacheLocked(void) +{ + mCacheValid = false; + mCache.clear(); +} + +/* + * =========================================================================== + * AssetManager::SharedZip + * =========================================================================== + */ + + +Mutex AssetManager::SharedZip::gLock; +DefaultKeyedVector<String8, wp<AssetManager::SharedZip> > AssetManager::SharedZip::gOpen; + +AssetManager::SharedZip::SharedZip(const String8& path, time_t modWhen) + : mPath(path), mZipFile(NULL), mModWhen(modWhen), mResourceTableAsset(NULL) +{ + //LOGI("Creating SharedZip %p %s\n", this, (const char*)mPath); + mZipFile = new ZipFileRO; + LOGV("+++ opening zip '%s'\n", mPath.string()); + if (mZipFile->open(mPath.string()) != NO_ERROR) { + LOGD("failed to open Zip archive '%s'\n", mPath.string()); + delete mZipFile; + mZipFile = NULL; + } +} + +sp<AssetManager::SharedZip> AssetManager::SharedZip::get(const String8& path) +{ + AutoMutex _l(gLock); + time_t modWhen = getFileModDate(path); + sp<SharedZip> zip = gOpen.valueFor(path).promote(); + if (zip != NULL && zip->mModWhen == modWhen) { + return zip; + } + zip = new SharedZip(path, modWhen); + gOpen.add(path, zip); + return zip; + +} + +ZipFileRO* AssetManager::SharedZip::getZip() +{ + return mZipFile; +} + +Asset* AssetManager::SharedZip::getResourceTableAsset() +{ + LOGV("Getting from SharedZip %p resource asset %p\n", this, mResourceTableAsset); + return mResourceTableAsset; +} + +Asset* AssetManager::SharedZip::setResourceTableAsset(Asset* asset) +{ + { + AutoMutex _l(gLock); + if (mResourceTableAsset == NULL) { + mResourceTableAsset = asset; + // This is not thread safe the first time it is called, so + // do it here with the global lock held. + asset->getBuffer(true); + return asset; + } + } + delete asset; + return mResourceTableAsset; +} + +bool AssetManager::SharedZip::isUpToDate() +{ + time_t modWhen = getFileModDate(mPath.string()); + return mModWhen == modWhen; +} + +AssetManager::SharedZip::~SharedZip() +{ + //LOGI("Destroying SharedZip %p %s\n", this, (const char*)mPath); + if (mResourceTableAsset != NULL) { + delete mResourceTableAsset; + } + if (mZipFile != NULL) { + delete mZipFile; + LOGV("Closed '%s'\n", mPath.string()); + } +} + +/* + * =========================================================================== + * AssetManager::ZipSet + * =========================================================================== + */ + +/* + * Constructor. + */ +AssetManager::ZipSet::ZipSet(void) +{ +} + +/* + * Destructor. Close any open archives. + */ +AssetManager::ZipSet::~ZipSet(void) +{ + size_t N = mZipFile.size(); + for (size_t i = 0; i < N; i++) + closeZip(i); +} + +/* + * Close a Zip file and reset the entry. + */ +void AssetManager::ZipSet::closeZip(int idx) +{ + mZipFile.editItemAt(idx) = NULL; +} + + +/* + * Retrieve the appropriate Zip file from the set. + */ +ZipFileRO* AssetManager::ZipSet::getZip(const String8& path) +{ + int idx = getIndex(path); + sp<SharedZip> zip = mZipFile[idx]; + if (zip == NULL) { + zip = SharedZip::get(path); + mZipFile.editItemAt(idx) = zip; + } + return zip->getZip(); +} + +Asset* AssetManager::ZipSet::getZipResourceTable(const String8& path) +{ + int idx = getIndex(path); + sp<SharedZip> zip = mZipFile[idx]; + if (zip == NULL) { + zip = SharedZip::get(path); + mZipFile.editItemAt(idx) = zip; + } + return zip->getResourceTableAsset(); +} + +Asset* AssetManager::ZipSet::setZipResourceTable(const String8& path, + Asset* asset) +{ + int idx = getIndex(path); + sp<SharedZip> zip = mZipFile[idx]; + // doesn't make sense to call before previously accessing. + return zip->setResourceTableAsset(asset); +} + +/* + * Generate the partial pathname for the specified archive. The caller + * gets to prepend the asset root directory. + * + * Returns something like "common/en-US-noogle.jar". + */ +/*static*/ String8 AssetManager::ZipSet::getPathName(const char* zipPath) +{ + return String8(zipPath); +} + +bool AssetManager::ZipSet::isUpToDate() +{ + const size_t N = mZipFile.size(); + for (size_t i=0; i<N; i++) { + if (mZipFile[i] != NULL && !mZipFile[i]->isUpToDate()) { + return false; + } + } + return true; +} + +/* + * Compute the zip file's index. + * + * "appName", "locale", and "vendor" should be set to NULL to indicate the + * default directory. + */ +int AssetManager::ZipSet::getIndex(const String8& zip) const +{ + const size_t N = mZipPath.size(); + for (size_t i=0; i<N; i++) { + if (mZipPath[i] == zip) { + return i; + } + } + + mZipPath.add(zip); + mZipFile.add(NULL); + + return mZipPath.size()-1; +} + diff --git a/libs/utils/Binder.cpp b/libs/utils/Binder.cpp new file mode 100644 index 0000000..37e4685 --- /dev/null +++ b/libs/utils/Binder.cpp @@ -0,0 +1,242 @@ +/* + * Copyright (C) 2005 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. + */ + +#include <utils/Binder.h> + +#include <utils/Atomic.h> +#include <utils/BpBinder.h> +#include <utils/IInterface.h> +#include <utils/Parcel.h> + +#include <stdio.h> + +namespace android { + +// --------------------------------------------------------------------------- + +sp<IInterface> IBinder::queryLocalInterface(const String16& descriptor) +{ + return NULL; +} + +BBinder* IBinder::localBinder() +{ + return NULL; +} + +BpBinder* IBinder::remoteBinder() +{ + return NULL; +} + +bool IBinder::checkSubclass(const void* /*subclassID*/) const +{ + return false; +} + +// --------------------------------------------------------------------------- + +class BBinder::Extras +{ +public: + Mutex mLock; + BpBinder::ObjectManager mObjects; +}; + +// --------------------------------------------------------------------------- + +BBinder::BBinder() + : mExtras(NULL) +{ +} + +bool BBinder::isBinderAlive() const +{ + return true; +} + +status_t BBinder::pingBinder() +{ + return NO_ERROR; +} + +String16 BBinder::getInterfaceDescriptor() const +{ + LOGW("reached BBinder::getInterfaceDescriptor (this=%p)", this); + return String16(); +} + +status_t BBinder::transact( + uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags) +{ + data.setDataPosition(0); + + status_t err = NO_ERROR; + switch (code) { + case PING_TRANSACTION: + reply->writeInt32(pingBinder()); + break; + default: + err = onTransact(code, data, reply, flags); + break; + } + + if (reply != NULL) { + reply->setDataPosition(0); + } + + return err; +} + +status_t BBinder::linkToDeath( + const sp<DeathRecipient>& recipient, void* cookie, uint32_t flags) +{ + return INVALID_OPERATION; +} + +status_t BBinder::unlinkToDeath( + const wp<DeathRecipient>& recipient, void* cookie, uint32_t flags, + wp<DeathRecipient>* outRecipient) +{ + return INVALID_OPERATION; +} + +status_t BBinder::dump(int fd, const Vector<String16>& args) +{ + return NO_ERROR; +} + +void BBinder::attachObject( + const void* objectID, void* object, void* cleanupCookie, + object_cleanup_func func) +{ + Extras* e = mExtras; + + if (!e) { + e = new Extras; + if (android_atomic_cmpxchg(0, reinterpret_cast<int32_t>(e), + reinterpret_cast<volatile int32_t*>(&mExtras)) != 0) { + delete e; + e = mExtras; + } + if (e == 0) return; // out of memory + } + + AutoMutex _l(e->mLock); + e->mObjects.attach(objectID, object, cleanupCookie, func); +} + +void* BBinder::findObject(const void* objectID) const +{ + Extras* e = mExtras; + if (!e) return NULL; + + AutoMutex _l(e->mLock); + return e->mObjects.find(objectID); +} + +void BBinder::detachObject(const void* objectID) +{ + Extras* e = mExtras; + if (!e) return; + + AutoMutex _l(e->mLock); + e->mObjects.detach(objectID); +} + +BBinder* BBinder::localBinder() +{ + return this; +} + +BBinder::~BBinder() +{ + if (mExtras) delete mExtras; +} + + +status_t BBinder::onTransact( + uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags) +{ + switch (code) { + case INTERFACE_TRANSACTION: + reply->writeString16(getInterfaceDescriptor()); + return NO_ERROR; + + case DUMP_TRANSACTION: { + int fd = data.readFileDescriptor(); + int argc = data.readInt32(); + Vector<String16> args; + for (int i = 0; i < argc && data.dataAvail() > 0; i++) { + args.add(data.readString16()); + } + return dump(fd, args); + } + default: + return UNKNOWN_TRANSACTION; + } +} + +// --------------------------------------------------------------------------- + +enum { + // This is used to transfer ownership of the remote binder from + // the BpRefBase object holding it (when it is constructed), to the + // owner of the BpRefBase object when it first acquires that BpRefBase. + kRemoteAcquired = 0x00000001 +}; + +BpRefBase::BpRefBase(const sp<IBinder>& o) + : mRemote(o.get()), mRefs(NULL), mState(0) +{ + extendObjectLifetime(OBJECT_LIFETIME_WEAK); + + if (mRemote) { + mRemote->incStrong(this); // Removed on first IncStrong(). + mRefs = mRemote->createWeak(this); // Held for our entire lifetime. + } +} + +BpRefBase::~BpRefBase() +{ + if (mRemote) { + if (!(mState&kRemoteAcquired)) { + mRemote->decStrong(this); + } + mRefs->decWeak(this); + } +} + +void BpRefBase::onFirstRef() +{ + android_atomic_or(kRemoteAcquired, &mState); +} + +void BpRefBase::onLastStrongRef(const void* id) +{ + if (mRemote) { + mRemote->decStrong(this); + } +} + +bool BpRefBase::onIncStrongAttempted(uint32_t flags, const void* id) +{ + return mRemote ? mRefs->attemptIncStrong(this) : false; +} + +// --------------------------------------------------------------------------- + +}; // namespace android diff --git a/libs/utils/BpBinder.cpp b/libs/utils/BpBinder.cpp new file mode 100644 index 0000000..69ab195 --- /dev/null +++ b/libs/utils/BpBinder.cpp @@ -0,0 +1,348 @@ +/* + * Copyright (C) 2005 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. + */ + +#define LOG_TAG "BpBinder" +//#define LOG_NDEBUG 0 + +#include <utils/BpBinder.h> + +#include <utils/IPCThreadState.h> +#include <utils/Log.h> + +#include <stdio.h> + +//#undef LOGV +//#define LOGV(...) fprintf(stderr, __VA_ARGS__) + +namespace android { + +// --------------------------------------------------------------------------- + +BpBinder::ObjectManager::ObjectManager() +{ +} + +BpBinder::ObjectManager::~ObjectManager() +{ + kill(); +} + +void BpBinder::ObjectManager::attach( + const void* objectID, void* object, void* cleanupCookie, + IBinder::object_cleanup_func func) +{ + entry_t e; + e.object = object; + e.cleanupCookie = cleanupCookie; + e.func = func; + + if (mObjects.indexOfKey(objectID) >= 0) { + LOGE("Trying to attach object ID %p to binder ObjectManager %p with object %p, but object ID already in use", + objectID, this, object); + return; + } + + mObjects.add(objectID, e); +} + +void* BpBinder::ObjectManager::find(const void* objectID) const +{ + const ssize_t i = mObjects.indexOfKey(objectID); + if (i < 0) return NULL; + return mObjects.valueAt(i).object; +} + +void BpBinder::ObjectManager::detach(const void* objectID) +{ + mObjects.removeItem(objectID); +} + +void BpBinder::ObjectManager::kill() +{ + const size_t N = mObjects.size(); + LOGV("Killing %d objects in manager %p", N, this); + for (size_t i=0; i<N; i++) { + const entry_t& e = mObjects.valueAt(i); + if (e.func != NULL) { + e.func(mObjects.keyAt(i), e.object, e.cleanupCookie); + } + } + + mObjects.clear(); +} + +// --------------------------------------------------------------------------- + +BpBinder::BpBinder(int32_t handle) + : mHandle(handle) + , mAlive(1) + , mObitsSent(0) + , mObituaries(NULL) +{ + LOGV("Creating BpBinder %p handle %d\n", this, mHandle); + + extendObjectLifetime(OBJECT_LIFETIME_WEAK); + IPCThreadState::self()->incWeakHandle(handle); +} + +String16 BpBinder::getInterfaceDescriptor() const +{ + String16 res; + Parcel send, reply; + status_t err = const_cast<BpBinder*>(this)->transact( + INTERFACE_TRANSACTION, send, &reply); + if (err == NO_ERROR) { + res = reply.readString16(); + } + return res; +} + +bool BpBinder::isBinderAlive() const +{ + return mAlive != 0; +} + +status_t BpBinder::pingBinder() +{ + Parcel send; + Parcel reply; + status_t err = transact(PING_TRANSACTION, send, &reply); + if (err != NO_ERROR) return err; + if (reply.dataSize() < sizeof(status_t)) return NOT_ENOUGH_DATA; + return (status_t)reply.readInt32(); +} + +status_t BpBinder::dump(int fd, const Vector<String16>& args) +{ + Parcel send; + Parcel reply; + send.writeFileDescriptor(fd); + const size_t numArgs = args.size(); + send.writeInt32(numArgs); + for (size_t i = 0; i < numArgs; i++) { + send.writeString16(args[i]); + } + status_t err = transact(DUMP_TRANSACTION, send, &reply); + return err; +} + +status_t BpBinder::transact( + uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags) +{ + // Once a binder has died, it will never come back to life. + if (mAlive) { + status_t status = IPCThreadState::self()->transact( + mHandle, code, data, reply, flags); + if (status == DEAD_OBJECT) mAlive = 0; + return status; + } + + return DEAD_OBJECT; +} + +status_t BpBinder::linkToDeath( + const sp<DeathRecipient>& recipient, void* cookie, uint32_t flags) +{ + Obituary ob; + ob.recipient = recipient; + ob.cookie = cookie; + ob.flags = flags; + + LOG_ALWAYS_FATAL_IF(recipient == NULL, + "linkToDeath(): recipient must be non-NULL"); + + { + AutoMutex _l(mLock); + + if (!mObitsSent) { + if (!mObituaries) { + mObituaries = new Vector<Obituary>; + if (!mObituaries) { + return NO_MEMORY; + } + LOGV("Requesting death notification: %p handle %d\n", this, mHandle); + getWeakRefs()->incWeak(this); + IPCThreadState* self = IPCThreadState::self(); + self->requestDeathNotification(mHandle, this); + self->flushCommands(); + } + ssize_t res = mObituaries->add(ob); + return res >= (ssize_t)NO_ERROR ? (status_t)NO_ERROR : res; + } + } + + return DEAD_OBJECT; +} + +status_t BpBinder::unlinkToDeath( + const wp<DeathRecipient>& recipient, void* cookie, uint32_t flags, + wp<DeathRecipient>* outRecipient) +{ + AutoMutex _l(mLock); + + if (mObitsSent) { + return DEAD_OBJECT; + } + + const size_t N = mObituaries ? mObituaries->size() : 0; + for (size_t i=0; i<N; i++) { + const Obituary& obit = mObituaries->itemAt(i); + if ((obit.recipient == recipient + || (recipient == NULL && obit.cookie == cookie)) + && obit.flags == flags) { + const uint32_t allFlags = obit.flags|flags; + if (outRecipient != NULL) { + *outRecipient = mObituaries->itemAt(i).recipient; + } + mObituaries->removeAt(i); + if (mObituaries->size() == 0) { + LOGV("Clearing death notification: %p handle %d\n", this, mHandle); + IPCThreadState* self = IPCThreadState::self(); + self->clearDeathNotification(mHandle, this); + self->flushCommands(); + delete mObituaries; + mObituaries = NULL; + } + return NO_ERROR; + } + } + + return NAME_NOT_FOUND; +} + +void BpBinder::sendObituary() +{ + LOGV("Sending obituary for proxy %p handle %d, mObitsSent=%s\n", + this, mHandle, mObitsSent ? "true" : "false"); + + mAlive = 0; + if (mObitsSent) return; + + mLock.lock(); + Vector<Obituary>* obits = mObituaries; + if(obits != NULL) { + LOGV("Clearing sent death notification: %p handle %d\n", this, mHandle); + IPCThreadState* self = IPCThreadState::self(); + self->clearDeathNotification(mHandle, this); + self->flushCommands(); + mObituaries = NULL; + } + mObitsSent = 1; + mLock.unlock(); + + LOGV("Reporting death of proxy %p for %d recipients\n", + this, obits ? obits->size() : 0); + + if (obits != NULL) { + const size_t N = obits->size(); + for (size_t i=0; i<N; i++) { + reportOneDeath(obits->itemAt(i)); + } + + delete obits; + } +} + +void BpBinder::reportOneDeath(const Obituary& obit) +{ + sp<DeathRecipient> recipient = obit.recipient.promote(); + LOGV("Reporting death to recipient: %p\n", recipient.get()); + if (recipient == NULL) return; + + recipient->binderDied(this); +} + + +void BpBinder::attachObject( + const void* objectID, void* object, void* cleanupCookie, + object_cleanup_func func) +{ + AutoMutex _l(mLock); + LOGV("Attaching object %p to binder %p (manager=%p)", object, this, &mObjects); + mObjects.attach(objectID, object, cleanupCookie, func); +} + +void* BpBinder::findObject(const void* objectID) const +{ + AutoMutex _l(mLock); + return mObjects.find(objectID); +} + +void BpBinder::detachObject(const void* objectID) +{ + AutoMutex _l(mLock); + mObjects.detach(objectID); +} + +BpBinder* BpBinder::remoteBinder() +{ + return this; +} + +BpBinder::~BpBinder() +{ + LOGV("Destroying BpBinder %p handle %d\n", this, mHandle); + + IPCThreadState* ipc = IPCThreadState::self(); + + mLock.lock(); + Vector<Obituary>* obits = mObituaries; + if(obits != NULL) { + if (ipc) ipc->clearDeathNotification(mHandle, this); + mObituaries = NULL; + } + mLock.unlock(); + + if (obits != NULL) { + // XXX Should we tell any remaining DeathRecipient + // objects that the last strong ref has gone away, so they + // are no longer linked? + delete obits; + } + + if (ipc) { + ipc->expungeHandle(mHandle, this); + ipc->decWeakHandle(mHandle); + } +} + +void BpBinder::onFirstRef() +{ + LOGV("onFirstRef BpBinder %p handle %d\n", this, mHandle); + IPCThreadState* ipc = IPCThreadState::self(); + if (ipc) ipc->incStrongHandle(mHandle); +} + +void BpBinder::onLastStrongRef(const void* id) +{ + LOGV("onLastStrongRef BpBinder %p handle %d\n", this, mHandle); + IF_LOGV() { + printRefs(); + } + IPCThreadState* ipc = IPCThreadState::self(); + if (ipc) ipc->decStrongHandle(mHandle); +} + +bool BpBinder::onIncStrongAttempted(uint32_t flags, const void* id) +{ + LOGV("onIncStrongAttempted BpBinder %p handle %d\n", this, mHandle); + IPCThreadState* ipc = IPCThreadState::self(); + return ipc ? ipc->attemptIncStrongHandle(mHandle) == NO_ERROR : false; +} + +// --------------------------------------------------------------------------- + +}; // namespace android diff --git a/libs/utils/BufferedTextOutput.cpp b/libs/utils/BufferedTextOutput.cpp new file mode 100644 index 0000000..989662e --- /dev/null +++ b/libs/utils/BufferedTextOutput.cpp @@ -0,0 +1,279 @@ +/* + * 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. + */ + +#include <utils/BufferedTextOutput.h> + +#include <utils/Atomic.h> +#include <utils/Debug.h> +#include <utils/Log.h> +#include <utils/RefBase.h> +#include <utils/Vector.h> +#include <cutils/threads.h> + +#include <private/utils/Static.h> + +#include <stdio.h> +#include <stdlib.h> + +// --------------------------------------------------------------------------- + +namespace android { + +struct BufferedTextOutput::BufferState : public RefBase +{ + BufferState(int32_t _seq) + : seq(_seq) + , buffer(NULL) + , bufferPos(0) + , bufferSize(0) + , atFront(true) + , indent(0) + , bundle(0) { + } + ~BufferState() { + free(buffer); + } + + status_t append(const char* txt, size_t len) { + if ((len+bufferPos) > bufferSize) { + void* b = realloc(buffer, ((len+bufferPos)*3)/2); + if (!b) return NO_MEMORY; + buffer = (char*)b; + } + memcpy(buffer+bufferPos, txt, len); + bufferPos += len; + return NO_ERROR; + } + + void restart() { + bufferPos = 0; + atFront = true; + if (bufferSize > 256) { + void* b = realloc(buffer, 256); + if (b) { + buffer = (char*)b; + bufferSize = 256; + } + } + } + + const int32_t seq; + char* buffer; + size_t bufferPos; + size_t bufferSize; + bool atFront; + int32_t indent; + int32_t bundle; +}; + +struct BufferedTextOutput::ThreadState +{ + Vector<sp<BufferedTextOutput::BufferState> > states; +}; + +static mutex_t gMutex; + +static thread_store_t tls; + +BufferedTextOutput::ThreadState* BufferedTextOutput::getThreadState() +{ + ThreadState* ts = (ThreadState*) thread_store_get( &tls ); + if (ts) return ts; + ts = new ThreadState; + thread_store_set( &tls, ts, threadDestructor ); + return ts; +} + +void BufferedTextOutput::threadDestructor(void *st) +{ + delete ((ThreadState*)st); +} + +static volatile int32_t gSequence = 0; + +static volatile int32_t gFreeBufferIndex = -1; + +static int32_t allocBufferIndex() +{ + int32_t res = -1; + + mutex_lock(&gMutex); + + if (gFreeBufferIndex >= 0) { + res = gFreeBufferIndex; + gFreeBufferIndex = gTextBuffers[res]; + gTextBuffers.editItemAt(res) = -1; + + } else { + res = gTextBuffers.size(); + gTextBuffers.add(-1); + } + + mutex_unlock(&gMutex); + + return res; +} + +static void freeBufferIndex(int32_t idx) +{ + mutex_lock(&gMutex); + gTextBuffers.editItemAt(idx) = gFreeBufferIndex; + gFreeBufferIndex = idx; + mutex_unlock(&gMutex); +} + +// --------------------------------------------------------------------------- + +BufferedTextOutput::BufferedTextOutput(uint32_t flags) + : mFlags(flags) + , mSeq(android_atomic_inc(&gSequence)) + , mIndex(allocBufferIndex()) +{ + mGlobalState = new BufferState(mSeq); + if (mGlobalState) mGlobalState->incStrong(this); +} + +BufferedTextOutput::~BufferedTextOutput() +{ + if (mGlobalState) mGlobalState->decStrong(this); + freeBufferIndex(mIndex); +} + +status_t BufferedTextOutput::print(const char* txt, size_t len) +{ + //printf("BufferedTextOutput: printing %d\n", len); + + AutoMutex _l(mLock); + BufferState* b = getBuffer(); + + const char* const end = txt+len; + + status_t err; + + while (txt < end) { + // Find the next line. + const char* first = txt; + while (txt < end && *txt != '\n') txt++; + + // Include this and all following empty lines. + while (txt < end && *txt == '\n') txt++; + + // Special cases for first data on a line. + if (b->atFront) { + if (b->indent > 0) { + // If this is the start of a line, add the indent. + const char* prefix = stringForIndent(b->indent); + err = b->append(prefix, strlen(prefix)); + if (err != NO_ERROR) return err; + + } else if (*(txt-1) == '\n' && !b->bundle) { + // Fast path: if we are not indenting or bundling, and + // have been given one or more complete lines, just write + // them out without going through the buffer. + + // Slurp up all of the lines. + const char* lastLine = txt+1; + while (txt < end) { + if (*txt++ == '\n') lastLine = txt; + } + struct iovec vec; + vec.iov_base = (void*)first; + vec.iov_len = lastLine-first; + //printf("Writing %d bytes of data!\n", vec.iov_len); + writeLines(vec, 1); + txt = lastLine; + continue; + } + } + + // Append the new text to the buffer. + err = b->append(first, txt-first); + if (err != NO_ERROR) return err; + b->atFront = *(txt-1) == '\n'; + + // If we have finished a line and are not bundling, write + // it out. + //printf("Buffer is now %d bytes\n", b->bufferPos); + if (b->atFront && !b->bundle) { + struct iovec vec; + vec.iov_base = b->buffer; + vec.iov_len = b->bufferPos; + //printf("Writing %d bytes of data!\n", vec.iov_len); + writeLines(vec, 1); + b->restart(); + } + } + + return NO_ERROR; +} + +void BufferedTextOutput::moveIndent(int delta) +{ + AutoMutex _l(mLock); + BufferState* b = getBuffer(); + b->indent += delta; + if (b->indent < 0) b->indent = 0; +} + +void BufferedTextOutput::pushBundle() +{ + AutoMutex _l(mLock); + BufferState* b = getBuffer(); + b->bundle++; +} + +void BufferedTextOutput::popBundle() +{ + AutoMutex _l(mLock); + BufferState* b = getBuffer(); + b->bundle--; + LOG_FATAL_IF(b->bundle < 0, + "TextOutput::popBundle() called more times than pushBundle()"); + if (b->bundle < 0) b->bundle = 0; + + if (b->bundle == 0) { + // Last bundle, write out data if it is complete. If it is not + // complete, don't write until the last line is done... this may + // or may not be the write thing to do, but it's the easiest. + if (b->bufferPos > 0 && b->atFront) { + struct iovec vec; + vec.iov_base = b->buffer; + vec.iov_len = b->bufferPos; + writeLines(vec, 1); + b->restart(); + } + } +} + +BufferedTextOutput::BufferState* BufferedTextOutput::getBuffer() const +{ + if ((mFlags&MULTITHREADED) != 0) { + ThreadState* ts = getThreadState(); + if (ts) { + while (ts->states.size() <= (size_t)mIndex) ts->states.add(NULL); + BufferState* bs = ts->states[mIndex].get(); + if (bs != NULL && bs->seq == mSeq) return bs; + + ts->states.editItemAt(mIndex) = new BufferState(mIndex); + bs = ts->states[mIndex].get(); + if (bs != NULL) return bs; + } + } + + return mGlobalState; +} + +}; // namespace android diff --git a/libs/utils/CallStack.cpp b/libs/utils/CallStack.cpp new file mode 100644 index 0000000..4968666 --- /dev/null +++ b/libs/utils/CallStack.cpp @@ -0,0 +1,335 @@ +/* + * Copyright (C) 2007 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. + */ + +#define LOG_TAG "CallStack" + +#include <string.h> +#include <stdlib.h> +#include <stdio.h> + +#if HAVE_DLADDR +#include <dlfcn.h> +#endif + +#if HAVE_CXXABI +#include <cxxabi.h> +#endif + +#include <unwind.h> + +#include <utils/Log.h> +#include <utils/Errors.h> +#include <utils/CallStack.h> +#include <utils/threads.h> + + +/*****************************************************************************/ +namespace android { + + +typedef struct { + size_t count; + size_t ignore; + const void** addrs; +} stack_crawl_state_t; + +static +_Unwind_Reason_Code trace_function(_Unwind_Context *context, void *arg) +{ + stack_crawl_state_t* state = (stack_crawl_state_t*)arg; + if (state->count) { + void* ip = (void*)_Unwind_GetIP(context); + if (ip) { + if (state->ignore) { + state->ignore--; + } else { + state->addrs[0] = ip; + state->addrs++; + state->count--; + } + } + } + return _URC_NO_REASON; +} + +static +int backtrace(const void** addrs, size_t ignore, size_t size) +{ + stack_crawl_state_t state; + state.count = size; + state.ignore = ignore; + state.addrs = addrs; + _Unwind_Backtrace(trace_function, (void*)&state); + return size - state.count; +} + +/*****************************************************************************/ + +static +const char *lookup_symbol(const void* addr, uint32_t *offset, char* name, size_t bufSize) +{ +#if HAVE_DLADDR + Dl_info info; + if (dladdr(addr, &info)) { + *offset = (uint32_t)info.dli_saddr; + return info.dli_sname; + } +#endif + return NULL; +} + +static +int32_t linux_gcc_demangler(const char *mangled_name, char *unmangled_name, size_t buffersize) +{ + size_t out_len = 0; +#if HAVE_CXXABI + int status = 0; + char *demangled = abi::__cxa_demangle(mangled_name, 0, &out_len, &status); + if (status == 0) { + // OK + if (out_len < buffersize) memcpy(unmangled_name, demangled, out_len); + else out_len = 0; + free(demangled); + } else { + out_len = 0; + } +#endif + return out_len; +} + +/*****************************************************************************/ + +class MapInfo { + struct mapinfo { + struct mapinfo *next; + unsigned start; + unsigned end; + char name[]; + }; + + const char *map_to_name(unsigned pc, const char* def) { + mapinfo* mi = getMapInfoList(); + while(mi) { + if ((pc >= mi->start) && (pc < mi->end)) + return mi->name; + mi = mi->next; + } + return def; + } + + mapinfo *parse_maps_line(char *line) { + mapinfo *mi; + int len = strlen(line); + if (len < 1) return 0; + line[--len] = 0; + if (len < 50) return 0; + if (line[20] != 'x') return 0; + mi = (mapinfo*)malloc(sizeof(mapinfo) + (len - 47)); + if (mi == 0) return 0; + mi->start = strtoul(line, 0, 16); + mi->end = strtoul(line + 9, 0, 16); + mi->next = 0; + strcpy(mi->name, line + 49); + return mi; + } + + mapinfo* getMapInfoList() { + Mutex::Autolock _l(mLock); + if (milist == 0) { + char data[1024]; + FILE *fp; + sprintf(data, "/proc/%d/maps", getpid()); + fp = fopen(data, "r"); + if (fp) { + while(fgets(data, 1024, fp)) { + mapinfo *mi = parse_maps_line(data); + if(mi) { + mi->next = milist; + milist = mi; + } + } + fclose(fp); + } + } + return milist; + } + mapinfo* milist; + Mutex mLock; + static MapInfo sMapInfo; + +public: + MapInfo() + : milist(0) { + } + + ~MapInfo() { + while (milist) { + mapinfo *next = milist->next; + free(milist); + milist = next; + } + } + + static const char *mapAddressToName(const void* pc, const char* def) { + return sMapInfo.map_to_name((unsigned)pc, def); + } + +}; + +/*****************************************************************************/ + +MapInfo MapInfo::sMapInfo; + +/*****************************************************************************/ + +CallStack::CallStack() + : mCount(0) +{ +} + +CallStack::CallStack(const CallStack& rhs) + : mCount(rhs.mCount) +{ + if (mCount) { + memcpy(mStack, rhs.mStack, mCount*sizeof(void*)); + } +} + +CallStack::~CallStack() +{ +} + +CallStack& CallStack::operator = (const CallStack& rhs) +{ + mCount = rhs.mCount; + if (mCount) { + memcpy(mStack, rhs.mStack, mCount*sizeof(void*)); + } + return *this; +} + +bool CallStack::operator == (const CallStack& rhs) const { + if (mCount != rhs.mCount) + return false; + return !mCount || (memcmp(mStack, rhs.mStack, mCount*sizeof(void*)) == 0); +} + +bool CallStack::operator != (const CallStack& rhs) const { + return !operator == (rhs); +} + +bool CallStack::operator < (const CallStack& rhs) const { + if (mCount != rhs.mCount) + return mCount < rhs.mCount; + return memcmp(mStack, rhs.mStack, mCount*sizeof(void*)) < 0; +} + +bool CallStack::operator >= (const CallStack& rhs) const { + return !operator < (rhs); +} + +bool CallStack::operator > (const CallStack& rhs) const { + if (mCount != rhs.mCount) + return mCount > rhs.mCount; + return memcmp(mStack, rhs.mStack, mCount*sizeof(void*)) > 0; +} + +bool CallStack::operator <= (const CallStack& rhs) const { + return !operator > (rhs); +} + +const void* CallStack::operator [] (int index) const { + if (index >= int(mCount)) + return 0; + return mStack[index]; +} + + +void CallStack::clear() +{ + mCount = 0; +} + +void CallStack::update(int32_t ignoreDepth, int32_t maxDepth) +{ + if (maxDepth > MAX_DEPTH) + maxDepth = MAX_DEPTH; + mCount = backtrace(mStack, ignoreDepth, maxDepth); +} + +// Return the stack frame name on the designated level +String8 CallStack::toStringSingleLevel(const char* prefix, int32_t level) const +{ + String8 res; + char namebuf[1024]; + char tmp[256]; + char tmp1[32]; + char tmp2[32]; + uint32_t offs; + + const void* ip = mStack[level]; + if (!ip) return res; + + if (prefix) res.append(prefix); + snprintf(tmp1, 32, "#%02d ", level); + res.append(tmp1); + + const char* name = lookup_symbol(ip, &offs, namebuf, sizeof(namebuf)); + if (name) { + if (linux_gcc_demangler(name, tmp, 256) != 0) + name = tmp; + snprintf(tmp1, 32, "0x%08x: <", (size_t)ip); + snprintf(tmp2, 32, ">+0x%08x", offs); + res.append(tmp1); + res.append(name); + res.append(tmp2); + } else { + name = MapInfo::mapAddressToName(ip, "<unknown>"); + snprintf(tmp, 256, "pc %08x %s", (size_t)ip, name); + res.append(tmp); + } + res.append("\n"); + + return res; +} + +// Dump a stack trace to the log +void CallStack::dump(const char* prefix) const +{ + /* + * Sending a single long log may be truncated since the stack levels can + * get very deep. So we request function names of each frame individually. + */ + for (int i=0; i<int(mCount); i++) { + LOGD("%s", toStringSingleLevel(prefix, i).string()); + } +} + +// Return a string (possibly very long) containing the complete stack trace +String8 CallStack::toString(const char* prefix) const +{ + String8 res; + + for (int i=0; i<int(mCount); i++) { + res.append(toStringSingleLevel(prefix, i).string()); + } + + return res; +} + +/*****************************************************************************/ + +}; // namespace android diff --git a/libs/utils/Debug.cpp b/libs/utils/Debug.cpp new file mode 100644 index 0000000..f7988ec --- /dev/null +++ b/libs/utils/Debug.cpp @@ -0,0 +1,318 @@ +/* + * Copyright (C) 2005 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. + */ + +#include <utils/Debug.h> + +#include <utils/misc.h> + +#include <stdio.h> +#include <stdlib.h> +#include <ctype.h> + +namespace android { + +// --------------------------------------------------------------------- + +static const char indentStr[] = +" " +" "; + +const char* stringForIndent(int32_t indentLevel) +{ + ssize_t off = sizeof(indentStr)-1-(indentLevel*2); + return indentStr + (off < 0 ? 0 : off); +} + +// --------------------------------------------------------------------- + +static void defaultPrintFunc(void* cookie, const char* txt) +{ + printf("%s", txt); +} + +// --------------------------------------------------------------------- + +static inline int isident(int c) +{ + return isalnum(c) || c == '_'; +} + +static inline bool isasciitype(char c) +{ + if( c >= ' ' && c < 127 && c != '\'' && c != '\\' ) return true; + return false; +} + +static inline char makehexdigit(uint32_t val) +{ + return "0123456789abcdef"[val&0xF]; +} + +static char* appendhexnum(uint32_t val, char* out) +{ + for( int32_t i=28; i>=0; i-=4 ) { + *out++ = makehexdigit( val>>i ); + } + *out = 0; + return out; +} + +static inline char makeupperhexdigit(uint32_t val) +{ + return "0123456789ABCDEF"[val&0xF]; +} + +static char* appendupperhexnum(uint32_t val, char* out) +{ + for( int32_t i=28; i>=0; i-=4 ) { + *out++ = makeupperhexdigit( val>>i ); + } + *out = 0; + return out; +} + +static char* appendcharornum(char c, char* out, bool skipzero = true) +{ + if (skipzero && c == 0) return out; + + if (isasciitype(c)) { + *out++ = c; + return out; + } + + *out++ = '\\'; + *out++ = 'x'; + *out++ = makehexdigit(c>>4); + *out++ = makehexdigit(c); + return out; +} + +static char* typetostring(uint32_t type, char* out, + bool fullContext = true, + bool strict = false) +{ + char* pos = out; + char c[4]; + c[0] = (char)((type>>24)&0xFF); + c[1] = (char)((type>>16)&0xFF); + c[2] = (char)((type>>8)&0xFF); + c[3] = (char)(type&0xFF); + bool valid; + if( !strict ) { + // now even less strict! + // valid = isasciitype(c[3]); + valid = true; + int32_t i = 0; + bool zero = true; + while (valid && i<3) { + if (c[i] == 0) { + if (!zero) valid = false; + } else { + zero = false; + //if (!isasciitype(c[i])) valid = false; + } + i++; + } + // if all zeros, not a valid type code. + if (zero) valid = false; + } else { + valid = isident(c[3]) ? true : false; + int32_t i = 0; + bool zero = true; + while (valid && i<3) { + if (c[i] == 0) { + if (!zero) valid = false; + } else { + zero = false; + if (!isident(c[i])) valid = false; + } + i++; + } + } + if( valid && (!fullContext || c[0] != '0' || c[1] != 'x') ) { + if( fullContext ) *pos++ = '\''; + pos = appendcharornum(c[0], pos); + pos = appendcharornum(c[1], pos); + pos = appendcharornum(c[2], pos); + pos = appendcharornum(c[3], pos); + if( fullContext ) *pos++ = '\''; + *pos = 0; + return pos; + } + + if( fullContext ) { + *pos++ = '0'; + *pos++ = 'x'; + } + return appendhexnum(type, pos); +} + +void printTypeCode(uint32_t typeCode, debugPrintFunc func, void* cookie) +{ + char buffer[32]; + char* end = typetostring(typeCode, buffer); + *end = 0; + func ? (*func)(cookie, buffer) : defaultPrintFunc(cookie, buffer); +} + +void printHexData(int32_t indent, const void *buf, size_t length, + size_t bytesPerLine, int32_t singleLineBytesCutoff, + size_t alignment, bool cStyle, + debugPrintFunc func, void* cookie) +{ + if (alignment == 0) { + if (bytesPerLine >= 16) alignment = 4; + else if (bytesPerLine >= 8) alignment = 2; + else alignment = 1; + } + if (func == NULL) func = defaultPrintFunc; + + size_t offset; + + unsigned char *pos = (unsigned char *)buf; + + if (pos == NULL) { + if (singleLineBytesCutoff < 0) func(cookie, "\n"); + func(cookie, "(NULL)"); + return; + } + + if (length == 0) { + if (singleLineBytesCutoff < 0) func(cookie, "\n"); + func(cookie, "(empty)"); + return; + } + + if ((int32_t)length < 0) { + if (singleLineBytesCutoff < 0) func(cookie, "\n"); + char buf[64]; + sprintf(buf, "(bad length: %d)", length); + func(cookie, buf); + return; + } + + char buffer[256]; + static const size_t maxBytesPerLine = (sizeof(buffer)-1-11-4)/(3+1); + + if (bytesPerLine > maxBytesPerLine) bytesPerLine = maxBytesPerLine; + + const bool oneLine = (int32_t)length <= singleLineBytesCutoff; + bool newLine = false; + if (cStyle) { + indent++; + func(cookie, "{\n"); + newLine = true; + } else if (!oneLine) { + func(cookie, "\n"); + newLine = true; + } + + for (offset = 0; ; offset += bytesPerLine, pos += bytesPerLine) { + long remain = length; + + char* c = buffer; + if (!oneLine && !cStyle) { + sprintf(c, "0x%08x: ", (int)offset); + c += 12; + } + + size_t index; + size_t word; + + for (word = 0; word < bytesPerLine; ) { + +#ifdef HAVE_LITTLE_ENDIAN + const size_t startIndex = word+(alignment-(alignment?1:0)); + const ssize_t dir = -1; +#else + const size_t startIndex = word; + const ssize_t dir = 1; +#endif + + for (index = 0; index < alignment || (alignment == 0 && index < bytesPerLine); index++) { + + if (!cStyle) { + if (index == 0 && word > 0 && alignment > 0) { + *c++ = ' '; + } + + if (remain-- > 0) { + const unsigned char val = *(pos+startIndex+(index*dir)); + *c++ = makehexdigit(val>>4); + *c++ = makehexdigit(val); + } else if (!oneLine) { + *c++ = ' '; + *c++ = ' '; + } + } else { + if (remain > 0) { + if (index == 0 && word > 0) { + *c++ = ','; + *c++ = ' '; + } + if (index == 0) { + *c++ = '0'; + *c++ = 'x'; + } + const unsigned char val = *(pos+startIndex+(index*dir)); + *c++ = makehexdigit(val>>4); + *c++ = makehexdigit(val); + remain--; + } + } + } + + word += index; + } + + if (!cStyle) { + remain = length; + *c++ = ' '; + *c++ = '\''; + for (index = 0; index < bytesPerLine; index++) { + + if (remain-- > 0) { + const unsigned char val = pos[index]; + *c++ = (val >= ' ' && val < 127) ? val : '.'; + } else if (!oneLine) { + *c++ = ' '; + } + } + + *c++ = '\''; + if (length > bytesPerLine) *c++ = '\n'; + } else { + if (remain > 0) *c++ = ','; + *c++ = '\n'; + } + + if (newLine && indent) func(cookie, stringForIndent(indent)); + *c = 0; + func(cookie, buffer); + newLine = true; + + if (length <= bytesPerLine) break; + length -= bytesPerLine; + } + + if (cStyle) { + if (indent > 0) func(cookie, stringForIndent(indent-1)); + func(cookie, "};"); + } +} + +}; // namespace android + diff --git a/libs/utils/FileMap.cpp b/libs/utils/FileMap.cpp new file mode 100644 index 0000000..e1ba9b2 --- /dev/null +++ b/libs/utils/FileMap.cpp @@ -0,0 +1,222 @@ +/* + * 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. + */ + +// +// Shared file mapping class. +// + +#define LOG_TAG "filemap" + +#include <utils/FileMap.h> +#include <utils/Log.h> + +#include <stdio.h> +#include <stdlib.h> + +#ifdef HAVE_POSIX_FILEMAP +#include <sys/mman.h> +#endif + +#include <string.h> +#include <memory.h> +#include <errno.h> +#include <assert.h> + +using namespace android; + +/*static*/ long FileMap::mPageSize = -1; + + +/* + * Constructor. Create an empty object. + */ +FileMap::FileMap(void) + : mRefCount(1), mFileName(NULL), mBasePtr(NULL), mBaseLength(0), + mDataPtr(NULL), mDataLength(0) +{ +} + +/* + * Destructor. + */ +FileMap::~FileMap(void) +{ + assert(mRefCount == 0); + + //printf("+++ removing FileMap %p %u\n", mDataPtr, mDataLength); + + mRefCount = -100; // help catch double-free + if (mFileName != NULL) { + free(mFileName); + } +#ifdef HAVE_POSIX_FILEMAP + if (munmap(mBasePtr, mBaseLength) != 0) { + LOGD("munmap(%p, %d) failed\n", mBasePtr, (int) mBaseLength); + } +#endif +#ifdef HAVE_WIN32_FILEMAP + if ( UnmapViewOfFile(mBasePtr) == 0) { + LOGD("UnmapViewOfFile(%p) failed, error = %ld\n", mBasePtr, + GetLastError() ); + } + CloseHandle(mFileMapping); + CloseHandle(mFileHandle); +#endif +} + + +/* + * Create a new mapping on an open file. + * + * Closing the file descriptor does not unmap the pages, so we don't + * claim ownership of the fd. + * + * Returns "false" on failure. + */ +bool FileMap::create(const char* origFileName, int fd, off_t offset, size_t length, bool readOnly) +{ +#ifdef HAVE_WIN32_FILEMAP + int adjust; + off_t adjOffset; + size_t adjLength; + + if (mPageSize == -1) { + SYSTEM_INFO si; + + GetSystemInfo( &si ); + mPageSize = si.dwAllocationGranularity; + } + + DWORD protect = readOnly ? PAGE_READONLY : PAGE_READWRITE; + + mFileHandle = (HANDLE) _get_osfhandle(fd); + mFileMapping = CreateFileMapping( mFileHandle, NULL, protect, 0, 0, NULL); + if (mFileMapping == NULL) { + LOGE("CreateFileMapping(%p, %lx) failed with error %ld\n", + mFileHandle, protect, GetLastError() ); + return false; + } + + adjust = offset % mPageSize; + adjOffset = offset - adjust; + adjLength = length + adjust; + + mBasePtr = MapViewOfFile( mFileMapping, + readOnly ? FILE_MAP_READ : FILE_MAP_ALL_ACCESS, + 0, + (DWORD)(adjOffset), + adjLength ); + if (mBasePtr == NULL) { + LOGE("MapViewOfFile(%ld, %ld) failed with error %ld\n", + adjOffset, adjLength, GetLastError() ); + CloseHandle(mFileMapping); + mFileMapping = INVALID_HANDLE_VALUE; + return false; + } +#endif +#ifdef HAVE_POSIX_FILEMAP + int prot, flags, adjust; + off_t adjOffset; + size_t adjLength; + + void* ptr; + + assert(mRefCount == 1); + assert(fd >= 0); + assert(offset >= 0); + assert(length > 0); + + /* init on first use */ + if (mPageSize == -1) { +#if NOT_USING_KLIBC + mPageSize = sysconf(_SC_PAGESIZE); + if (mPageSize == -1) { + LOGE("could not get _SC_PAGESIZE\n"); + return false; + } +#else + /* this holds for Linux, Darwin, Cygwin, and doesn't pain the ARM */ + mPageSize = 4096; +#endif + } + + adjust = offset % mPageSize; +try_again: + adjOffset = offset - adjust; + adjLength = length + adjust; + + flags = MAP_SHARED; + prot = PROT_READ; + if (!readOnly) + prot |= PROT_WRITE; + + ptr = mmap(NULL, adjLength, prot, flags, fd, adjOffset); + if (ptr == MAP_FAILED) { + // Cygwin does not seem to like file mapping files from an offset. + // So if we fail, try again with offset zero + if (adjOffset > 0) { + adjust = offset; + goto try_again; + } + + LOGE("mmap(%ld,%ld) failed: %s\n", + (long) adjOffset, (long) adjLength, strerror(errno)); + return false; + } + mBasePtr = ptr; +#endif /* HAVE_POSIX_FILEMAP */ + + mFileName = origFileName != NULL ? strdup(origFileName) : NULL; + mBaseLength = adjLength; + mDataOffset = offset; + mDataPtr = (char*) mBasePtr + adjust; + mDataLength = length; + + assert(mBasePtr != NULL); + + LOGV("MAP: base %p/%d data %p/%d\n", + mBasePtr, (int) mBaseLength, mDataPtr, (int) mDataLength); + + return true; +} + +/* + * Provide guidance to the system. + */ +int FileMap::advise(MapAdvice advice) +{ +#if HAVE_MADVISE + int cc, sysAdvice; + + switch (advice) { + case NORMAL: sysAdvice = MADV_NORMAL; break; + case RANDOM: sysAdvice = MADV_RANDOM; break; + case SEQUENTIAL: sysAdvice = MADV_SEQUENTIAL; break; + case WILLNEED: sysAdvice = MADV_WILLNEED; break; + case DONTNEED: sysAdvice = MADV_DONTNEED; break; + default: + assert(false); + return -1; + } + + cc = madvise(mBasePtr, mBaseLength, sysAdvice); + if (cc != 0) + LOGW("madvise(%d) failed: %s\n", sysAdvice, strerror(errno)); + return cc; +#else + return -1; +#endif // HAVE_MADVISE +} diff --git a/libs/utils/IDataConnection.cpp b/libs/utils/IDataConnection.cpp new file mode 100644 index 0000000..c6d49aa --- /dev/null +++ b/libs/utils/IDataConnection.cpp @@ -0,0 +1,89 @@ +/* + * 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. + */ + +#include <stdint.h> +#include <sys/types.h> + +#include <utils/Parcel.h> + +#include <utils/IDataConnection.h> + +namespace android { + +// --------------------------------------------------------------------------- + +enum +{ + CONNECT_TRANSACTION = IBinder::FIRST_CALL_TRANSACTION, + DISCONNECT_TRANSACTION = IBinder::FIRST_CALL_TRANSACTION + 1 +}; + +class BpDataConnection : public BpInterface<IDataConnection> +{ +public: + BpDataConnection::BpDataConnection(const sp<IBinder>& impl) + : BpInterface<IDataConnection>(impl) + { + } + + virtual void connect() + { + Parcel data, reply; + data.writeInterfaceToken(IDataConnection::descriptor()); + remote()->transact(CONNECT_TRANSACTION, data, &reply); + } + + virtual void disconnect() + { + Parcel data, reply; + remote()->transact(DISCONNECT_TRANSACTION, data, &reply); + } +}; + +IMPLEMENT_META_INTERFACE(DataConnection, "android.utils.IDataConnection"); + +#define CHECK_INTERFACE(interface, data, reply) \ + do { if (!data.enforceInterface(interface::getInterfaceDescriptor())) { \ + LOGW("Call incorrectly routed to " #interface); \ + return PERMISSION_DENIED; \ + } } while (0) + +status_t BnDataConnection::onTransact(uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags) +{ + switch(code) + { + case CONNECT_TRANSACTION: + { + CHECK_INTERFACE(IDataConnection, data, reply); + connect(); + return NO_ERROR; + } + + case DISCONNECT_TRANSACTION: + { + CHECK_INTERFACE(IDataConnection, data, reply); + disconnect(); + return NO_ERROR; + } + + default: + return BBinder::onTransact(code, data, reply, flags); + } +} + +// ---------------------------------------------------------------------------- + +}; // namespace android diff --git a/libs/utils/IInterface.cpp b/libs/utils/IInterface.cpp new file mode 100644 index 0000000..6ea8178 --- /dev/null +++ b/libs/utils/IInterface.cpp @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2005 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. + */ + +#include <utils/IInterface.h> + +namespace android { + +// --------------------------------------------------------------------------- + +sp<IBinder> IInterface::asBinder() +{ + return this ? onAsBinder() : NULL; +} + +sp<const IBinder> IInterface::asBinder() const +{ + return this ? const_cast<IInterface*>(this)->onAsBinder() : NULL; +} + +// --------------------------------------------------------------------------- + +}; // namespace android diff --git a/libs/utils/IMemory.cpp b/libs/utils/IMemory.cpp new file mode 100644 index 0000000..429bc2b --- /dev/null +++ b/libs/utils/IMemory.cpp @@ -0,0 +1,486 @@ +/* + * Copyright (C) 2008 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. + */ + +#define LOG_TAG "IMemory" + +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <fcntl.h> +#include <unistd.h> + +#include <sys/types.h> +#include <sys/mman.h> + +#include <utils/IMemory.h> +#include <utils/KeyedVector.h> +#include <utils/threads.h> +#include <utils/Atomic.h> +#include <utils/Parcel.h> +#include <utils/CallStack.h> + +#define VERBOSE 0 + +namespace android { +// --------------------------------------------------------------------------- + +class HeapCache : public IBinder::DeathRecipient +{ +public: + HeapCache(); + virtual ~HeapCache(); + + virtual void binderDied(const wp<IBinder>& who); + + sp<IMemoryHeap> find_heap(const sp<IBinder>& binder); + void pin_heap(const sp<IBinder>& binder); + void free_heap(const sp<IBinder>& binder); + sp<IMemoryHeap> get_heap(const sp<IBinder>& binder); + void dump_heaps(); + +private: + // For IMemory.cpp + struct heap_info_t { + sp<IMemoryHeap> heap; + int32_t count; + }; + + void free_heap(const wp<IBinder>& binder); + + Mutex mHeapCacheLock; + KeyedVector< wp<IBinder>, heap_info_t > mHeapCache; +}; + +static sp<HeapCache> gHeapCache = new HeapCache(); + +/******************************************************************************/ + +enum { + HEAP_ID = IBinder::FIRST_CALL_TRANSACTION +}; + +class BpMemoryHeap : public BpInterface<IMemoryHeap> +{ +public: + BpMemoryHeap(const sp<IBinder>& impl); + virtual ~BpMemoryHeap(); + + virtual int getHeapID() const; + virtual void* getBase() const; + virtual size_t getSize() const; + virtual uint32_t getFlags() const; + +private: + friend class IMemory; + friend class HeapCache; + + // for debugging in this module + static inline sp<IMemoryHeap> find_heap(const sp<IBinder>& binder) { + return gHeapCache->find_heap(binder); + } + static inline void free_heap(const sp<IBinder>& binder) { + gHeapCache->free_heap(binder); + } + static inline sp<IMemoryHeap> get_heap(const sp<IBinder>& binder) { + return gHeapCache->get_heap(binder); + } + static inline void dump_heaps() { + gHeapCache->dump_heaps(); + } + void inline pin_heap() const { + gHeapCache->pin_heap(const_cast<BpMemoryHeap*>(this)->asBinder()); + } + + void assertMapped() const; + void assertReallyMapped() const; + void pinHeap() const; + + mutable volatile int32_t mHeapId; + mutable void* mBase; + mutable size_t mSize; + mutable uint32_t mFlags; + mutable bool mRealHeap; + mutable Mutex mLock; +}; + +// ---------------------------------------------------------------------------- + +enum { + GET_MEMORY = IBinder::FIRST_CALL_TRANSACTION +}; + +class BpMemory : public BpInterface<IMemory> +{ +public: + BpMemory(const sp<IBinder>& impl); + virtual ~BpMemory(); + virtual sp<IMemoryHeap> getMemory(ssize_t* offset=0, size_t* size=0) const; + +private: + mutable sp<IMemoryHeap> mHeap; + mutable ssize_t mOffset; + mutable size_t mSize; +}; + +/******************************************************************************/ + +void* IMemory::fastPointer(const sp<IBinder>& binder, ssize_t offset) const +{ + sp<IMemoryHeap> realHeap = BpMemoryHeap::get_heap(binder); + void* const base = realHeap->base(); + if (base == MAP_FAILED) + return 0; + return static_cast<char*>(base) + offset; +} + +void* IMemory::pointer() const { + ssize_t offset; + sp<IMemoryHeap> heap = getMemory(&offset); + void* const base = heap!=0 ? heap->base() : MAP_FAILED; + if (base == MAP_FAILED) + return 0; + return static_cast<char*>(base) + offset; +} + +size_t IMemory::size() const { + size_t size; + getMemory(NULL, &size); + return size; +} + +ssize_t IMemory::offset() const { + ssize_t offset; + getMemory(&offset); + return offset; +} + +/******************************************************************************/ + +BpMemory::BpMemory(const sp<IBinder>& impl) + : BpInterface<IMemory>(impl), mOffset(0), mSize(0) +{ +} + +BpMemory::~BpMemory() +{ +} + +sp<IMemoryHeap> BpMemory::getMemory(ssize_t* offset, size_t* size) const +{ + if (mHeap == 0) { + Parcel data, reply; + data.writeInterfaceToken(IMemory::getInterfaceDescriptor()); + if (remote()->transact(GET_MEMORY, data, &reply) == NO_ERROR) { + sp<IBinder> heap = reply.readStrongBinder(); + ssize_t o = reply.readInt32(); + size_t s = reply.readInt32(); + if (heap != 0) { + mHeap = interface_cast<IMemoryHeap>(heap); + if (mHeap != 0) { + mOffset = o; + mSize = s; + } + } + } + } + if (offset) *offset = mOffset; + if (size) *size = mSize; + return mHeap; +} + +// --------------------------------------------------------------------------- + +IMPLEMENT_META_INTERFACE(Memory, "android.utils.IMemory"); + +#define CHECK_INTERFACE(interface, data, reply) \ + do { if (!data.enforceInterface(interface::getInterfaceDescriptor())) { \ + LOGW("Call incorrectly routed to " #interface); \ + return PERMISSION_DENIED; \ + } } while (0) + +status_t BnMemory::onTransact( + uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags) +{ + switch(code) { + case GET_MEMORY: { + CHECK_INTERFACE(IMemory, data, reply); + ssize_t offset; + size_t size; + reply->writeStrongBinder( getMemory(&offset, &size)->asBinder() ); + reply->writeInt32(offset); + reply->writeInt32(size); + return NO_ERROR; + } break; + default: + return BBinder::onTransact(code, data, reply, flags); + } +} + + +/******************************************************************************/ + +BpMemoryHeap::BpMemoryHeap(const sp<IBinder>& impl) + : BpInterface<IMemoryHeap>(impl), + mHeapId(-1), mBase(MAP_FAILED), mSize(0), mFlags(0), mRealHeap(false) +{ +} + +BpMemoryHeap::~BpMemoryHeap() { + if (mHeapId != -1) { + close(mHeapId); + if (mRealHeap) { + // by construction we're the last one + if (mBase != MAP_FAILED) { + sp<IBinder> binder = const_cast<BpMemoryHeap*>(this)->asBinder(); + + if (VERBOSE) { + LOGD("UNMAPPING binder=%p, heap=%p, size=%d, fd=%d", + binder.get(), this, mSize, mHeapId); + CallStack stack; + stack.update(); + stack.dump("callstack"); + } + + munmap(mBase, mSize); + } + } else { + // remove from list only if it was mapped before + sp<IBinder> binder = const_cast<BpMemoryHeap*>(this)->asBinder(); + free_heap(binder); + } + } +} + +void BpMemoryHeap::assertMapped() const +{ + if (mHeapId == -1) { + sp<IBinder> binder(const_cast<BpMemoryHeap*>(this)->asBinder()); + sp<BpMemoryHeap> heap(static_cast<BpMemoryHeap*>(find_heap(binder).get())); + heap->assertReallyMapped(); + if (heap->mBase != MAP_FAILED) { + Mutex::Autolock _l(mLock); + if (mHeapId == -1) { + mBase = heap->mBase; + mSize = heap->mSize; + android_atomic_write( dup( heap->mHeapId ), &mHeapId ); + } + } else { + // something went wrong + free_heap(binder); + } + } +} + +void BpMemoryHeap::assertReallyMapped() const +{ + if (mHeapId == -1) { + + // remote call without mLock held, worse case scenario, we end up + // calling transact() from multiple threads, but that's not a problem, + // only mmap below must be in the critical section. + + Parcel data, reply; + data.writeInterfaceToken(IMemoryHeap::getInterfaceDescriptor()); + status_t err = remote()->transact(HEAP_ID, data, &reply); + int parcel_fd = reply.readFileDescriptor(); + ssize_t size = reply.readInt32(); + uint32_t flags = reply.readInt32(); + + LOGE_IF(err, "binder=%p transaction failed fd=%d, size=%d, err=%d (%s)", + asBinder().get(), parcel_fd, size, err, strerror(-err)); + + int fd = dup( parcel_fd ); + LOGE_IF(fd==-1, "cannot dup fd=%d, size=%d, err=%d (%s)", + parcel_fd, size, err, strerror(errno)); + + int access = PROT_READ; + if (!(flags & READ_ONLY)) { + access |= PROT_WRITE; + } + + Mutex::Autolock _l(mLock); + if (mHeapId == -1) { + mRealHeap = true; + mBase = mmap(0, size, access, MAP_SHARED, fd, 0); + if (mBase == MAP_FAILED) { + LOGE("cannot map BpMemoryHeap (binder=%p), size=%d, fd=%d (%s)", + asBinder().get(), size, fd, strerror(errno)); + close(fd); + } else { + if (flags & MAP_ONCE) { + //LOGD("pinning heap (binder=%p, size=%d, fd=%d", + // asBinder().get(), size, fd); + pin_heap(); + } + mSize = size; + mFlags = flags; + android_atomic_write(fd, &mHeapId); + } + } + } +} + +int BpMemoryHeap::getHeapID() const { + assertMapped(); + return mHeapId; +} + +void* BpMemoryHeap::getBase() const { + assertMapped(); + return mBase; +} + +size_t BpMemoryHeap::getSize() const { + assertMapped(); + return mSize; +} + +uint32_t BpMemoryHeap::getFlags() const { + assertMapped(); + return mFlags; +} + +// --------------------------------------------------------------------------- + +IMPLEMENT_META_INTERFACE(MemoryHeap, "android.utils.IMemoryHeap"); + +status_t BnMemoryHeap::onTransact( + uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags) +{ + switch(code) { + case HEAP_ID: { + CHECK_INTERFACE(IMemoryHeap, data, reply); + reply->writeFileDescriptor(getHeapID()); + reply->writeInt32(getSize()); + reply->writeInt32(getFlags()); + return NO_ERROR; + } break; + default: + return BBinder::onTransact(code, data, reply, flags); + } +} + +/*****************************************************************************/ + +HeapCache::HeapCache() + : DeathRecipient() +{ +} + +HeapCache::~HeapCache() +{ +} + +void HeapCache::binderDied(const wp<IBinder>& binder) +{ + //LOGD("binderDied binder=%p", binder.unsafe_get()); + free_heap(binder); +} + +sp<IMemoryHeap> HeapCache::find_heap(const sp<IBinder>& binder) +{ + Mutex::Autolock _l(mHeapCacheLock); + ssize_t i = mHeapCache.indexOfKey(binder); + if (i>=0) { + heap_info_t& info = mHeapCache.editValueAt(i); + LOGD_IF(VERBOSE, + "found binder=%p, heap=%p, size=%d, fd=%d, count=%d", + binder.get(), info.heap.get(), + static_cast<BpMemoryHeap*>(info.heap.get())->mSize, + static_cast<BpMemoryHeap*>(info.heap.get())->mHeapId, + info.count); + android_atomic_inc(&info.count); + return info.heap; + } else { + heap_info_t info; + info.heap = interface_cast<IMemoryHeap>(binder); + info.count = 1; + //LOGD("adding binder=%p, heap=%p, count=%d", + // binder.get(), info.heap.get(), info.count); + mHeapCache.add(binder, info); + return info.heap; + } +} + +void HeapCache::pin_heap(const sp<IBinder>& binder) +{ + Mutex::Autolock _l(mHeapCacheLock); + ssize_t i = mHeapCache.indexOfKey(binder); + if (i>=0) { + heap_info_t& info(mHeapCache.editValueAt(i)); + android_atomic_inc(&info.count); + binder->linkToDeath(this); + } else { + LOGE("pin_heap binder=%p not found!!!", binder.get()); + } +} + +void HeapCache::free_heap(const sp<IBinder>& binder) { + free_heap( wp<IBinder>(binder) ); +} + +void HeapCache::free_heap(const wp<IBinder>& binder) +{ + sp<IMemoryHeap> rel; + { + Mutex::Autolock _l(mHeapCacheLock); + ssize_t i = mHeapCache.indexOfKey(binder); + if (i>=0) { + heap_info_t& info(mHeapCache.editValueAt(i)); + int32_t c = android_atomic_dec(&info.count); + if (c == 1) { + LOGD_IF(VERBOSE, + "removing binder=%p, heap=%p, size=%d, fd=%d, count=%d", + binder.unsafe_get(), info.heap.get(), + static_cast<BpMemoryHeap*>(info.heap.get())->mSize, + static_cast<BpMemoryHeap*>(info.heap.get())->mHeapId, + info.count); + rel = mHeapCache.valueAt(i).heap; + mHeapCache.removeItemsAt(i); + } + } else { + LOGE("free_heap binder=%p not found!!!", binder.unsafe_get()); + } + } +} + +sp<IMemoryHeap> HeapCache::get_heap(const sp<IBinder>& binder) +{ + sp<IMemoryHeap> realHeap; + Mutex::Autolock _l(mHeapCacheLock); + ssize_t i = mHeapCache.indexOfKey(binder); + if (i>=0) realHeap = mHeapCache.valueAt(i).heap; + else realHeap = interface_cast<IMemoryHeap>(binder); + return realHeap; +} + +void HeapCache::dump_heaps() +{ + Mutex::Autolock _l(mHeapCacheLock); + int c = mHeapCache.size(); + for (int i=0 ; i<c ; i++) { + const heap_info_t& info = mHeapCache.valueAt(i); + BpMemoryHeap const* h(static_cast<BpMemoryHeap const *>(info.heap.get())); + LOGD("hey=%p, heap=%p, count=%d, (fd=%d, base=%p, size=%d)", + mHeapCache.keyAt(i).unsafe_get(), + info.heap.get(), info.count, + h->mHeapId, h->mBase, h->mSize); + } +} + + +// --------------------------------------------------------------------------- +}; // namespace android diff --git a/libs/utils/IPCThreadState.cpp b/libs/utils/IPCThreadState.cpp new file mode 100644 index 0000000..ca49d9a --- /dev/null +++ b/libs/utils/IPCThreadState.cpp @@ -0,0 +1,1007 @@ +/* + * Copyright (C) 2005 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. + */ + +#include <utils/IPCThreadState.h> + +#include <utils/Binder.h> +#include <utils/BpBinder.h> +#include <utils/Debug.h> +#include <utils/Log.h> +#include <utils/TextOutput.h> +#include <utils/threads.h> + +#include <private/utils/binder_module.h> +#include <private/utils/Static.h> + +#include <sys/ioctl.h> +#include <signal.h> +#include <errno.h> +#include <stdio.h> +#include <unistd.h> + +#ifdef HAVE_PTHREADS +#include <pthread.h> +#include <sched.h> +#include <sys/resource.h> +#endif +#ifdef HAVE_WIN32_THREADS +#include <windows.h> +#endif + + +#if LOG_NDEBUG + +#define IF_LOG_TRANSACTIONS() if (false) +#define IF_LOG_COMMANDS() if (false) +#define LOG_REMOTEREFS(...) +#define IF_LOG_REMOTEREFS() if (false) +#define LOG_THREADPOOL(...) +#define LOG_ONEWAY(...) + +#else + +#define IF_LOG_TRANSACTIONS() IF_LOG(LOG_VERBOSE, "transact") +#define IF_LOG_COMMANDS() IF_LOG(LOG_VERBOSE, "ipc") +#define LOG_REMOTEREFS(...) LOG(LOG_DEBUG, "remoterefs", __VA_ARGS__) +#define IF_LOG_REMOTEREFS() IF_LOG(LOG_DEBUG, "remoterefs") +#define LOG_THREADPOOL(...) LOG(LOG_DEBUG, "threadpool", __VA_ARGS__) +#define LOG_ONEWAY(...) LOG(LOG_DEBUG, "ipc", __VA_ARGS__) + +#endif + +// --------------------------------------------------------------------------- + +namespace android { + +static const char* getReturnString(size_t idx); +static const char* getCommandString(size_t idx); +static const void* printReturnCommand(TextOutput& out, const void* _cmd); +static const void* printCommand(TextOutput& out, const void* _cmd); + +// This will result in a missing symbol failure if the IF_LOG_COMMANDS() +// conditionals don't get stripped... but that is probably what we want. +#if !LOG_NDEBUG +static const char *kReturnStrings[] = { +#if 1 /* TODO: error update strings */ + "unknown", +#else + "BR_OK", + "BR_TIMEOUT", + "BR_WAKEUP", + "BR_TRANSACTION", + "BR_REPLY", + "BR_ACQUIRE_RESULT", + "BR_DEAD_REPLY", + "BR_TRANSACTION_COMPLETE", + "BR_INCREFS", + "BR_ACQUIRE", + "BR_RELEASE", + "BR_DECREFS", + "BR_ATTEMPT_ACQUIRE", + "BR_EVENT_OCCURRED", + "BR_NOOP", + "BR_SPAWN_LOOPER", + "BR_FINISHED", + "BR_DEAD_BINDER", + "BR_CLEAR_DEATH_NOTIFICATION_DONE" +#endif +}; + +static const char *kCommandStrings[] = { +#if 1 /* TODO: error update strings */ + "unknown", +#else + "BC_NOOP", + "BC_TRANSACTION", + "BC_REPLY", + "BC_ACQUIRE_RESULT", + "BC_FREE_BUFFER", + "BC_TRANSACTION_COMPLETE", + "BC_INCREFS", + "BC_ACQUIRE", + "BC_RELEASE", + "BC_DECREFS", + "BC_INCREFS_DONE", + "BC_ACQUIRE_DONE", + "BC_ATTEMPT_ACQUIRE", + "BC_RETRIEVE_ROOT_OBJECT", + "BC_SET_THREAD_ENTRY", + "BC_REGISTER_LOOPER", + "BC_ENTER_LOOPER", + "BC_EXIT_LOOPER", + "BC_SYNC", + "BC_STOP_PROCESS", + "BC_STOP_SELF", + "BC_REQUEST_DEATH_NOTIFICATION", + "BC_CLEAR_DEATH_NOTIFICATION", + "BC_DEAD_BINDER_DONE" +#endif +}; + +static const char* getReturnString(size_t idx) +{ + if (idx < sizeof(kReturnStrings) / sizeof(kReturnStrings[0])) + return kReturnStrings[idx]; + else + return "unknown"; +} + +static const char* getCommandString(size_t idx) +{ + if (idx < sizeof(kCommandStrings) / sizeof(kCommandStrings[0])) + return kCommandStrings[idx]; + else + return "unknown"; +} + +static const void* printBinderTransactionData(TextOutput& out, const void* data) +{ + const binder_transaction_data* btd = + (const binder_transaction_data*)data; + out << "target=" << btd->target.ptr << " (cookie " << btd->cookie << ")" << endl + << "code=" << TypeCode(btd->code) << ", flags=" << (void*)btd->flags << endl + << "data=" << btd->data.ptr.buffer << " (" << (void*)btd->data_size + << " bytes)" << endl + << "offsets=" << btd->data.ptr.offsets << " (" << (void*)btd->offsets_size + << " bytes)" << endl; + return btd+1; +} + +static const void* printReturnCommand(TextOutput& out, const void* _cmd) +{ + static const int32_t N = sizeof(kReturnStrings)/sizeof(kReturnStrings[0]); + + const int32_t* cmd = (const int32_t*)_cmd; + int32_t code = *cmd++; + if (code == BR_ERROR) { + out << "BR_ERROR: " << (void*)(*cmd++) << endl; + return cmd; + } else if (code < 0 || code >= N) { + out << "Unknown reply: " << code << endl; + return cmd; + } + + out << kReturnStrings[code]; + switch (code) { + case BR_TRANSACTION: + case BR_REPLY: { + out << ": " << indent; + cmd = (const int32_t *)printBinderTransactionData(out, cmd); + out << dedent; + } break; + + case BR_ACQUIRE_RESULT: { + const int32_t res = *cmd++; + out << ": " << res << (res ? " (SUCCESS)" : " (FAILURE)"); + } break; + + case BR_INCREFS: + case BR_ACQUIRE: + case BR_RELEASE: + case BR_DECREFS: { + const int32_t b = *cmd++; + const int32_t c = *cmd++; + out << ": target=" << (void*)b << " (cookie " << (void*)c << ")"; + } break; + + case BR_ATTEMPT_ACQUIRE: { + const int32_t p = *cmd++; + const int32_t b = *cmd++; + const int32_t c = *cmd++; + out << ": target=" << (void*)b << " (cookie " << (void*)c + << "), pri=" << p; + } break; + + case BR_DEAD_BINDER: + case BR_CLEAR_DEATH_NOTIFICATION_DONE: { + const int32_t c = *cmd++; + out << ": death cookie " << (void*)c; + } break; + } + + out << endl; + return cmd; +} + +static const void* printCommand(TextOutput& out, const void* _cmd) +{ + static const int32_t N = sizeof(kCommandStrings)/sizeof(kCommandStrings[0]); + + const int32_t* cmd = (const int32_t*)_cmd; + int32_t code = *cmd++; + if (code < 0 || code >= N) { + out << "Unknown command: " << code << endl; + return cmd; + } + + out << kCommandStrings[code]; + switch (code) { + case BC_TRANSACTION: + case BC_REPLY: { + out << ": " << indent; + cmd = (const int32_t *)printBinderTransactionData(out, cmd); + out << dedent; + } break; + + case BC_ACQUIRE_RESULT: { + const int32_t res = *cmd++; + out << ": " << res << (res ? " (SUCCESS)" : " (FAILURE)"); + } break; + + case BC_FREE_BUFFER: { + const int32_t buf = *cmd++; + out << ": buffer=" << (void*)buf; + } break; + + case BC_INCREFS: + case BC_ACQUIRE: + case BC_RELEASE: + case BC_DECREFS: { + const int32_t d = *cmd++; + out << ": descriptor=" << (void*)d; + } break; + + case BC_INCREFS_DONE: + case BC_ACQUIRE_DONE: { + const int32_t b = *cmd++; + const int32_t c = *cmd++; + out << ": target=" << (void*)b << " (cookie " << (void*)c << ")"; + } break; + + case BC_ATTEMPT_ACQUIRE: { + const int32_t p = *cmd++; + const int32_t d = *cmd++; + out << ": decriptor=" << (void*)d << ", pri=" << p; + } break; + + case BC_REQUEST_DEATH_NOTIFICATION: + case BC_CLEAR_DEATH_NOTIFICATION: { + const int32_t h = *cmd++; + const int32_t c = *cmd++; + out << ": handle=" << h << " (death cookie " << (void*)c << ")"; + } break; + + case BC_DEAD_BINDER_DONE: { + const int32_t c = *cmd++; + out << ": death cookie " << (void*)c; + } break; + } + + out << endl; + return cmd; +} +#endif + +static pthread_mutex_t gTLSMutex = PTHREAD_MUTEX_INITIALIZER; +static bool gHaveTLS = false; +static pthread_key_t gTLS = 0; +static bool gShutdown = false; + +IPCThreadState* IPCThreadState::self() +{ + if (gHaveTLS) { +restart: + const pthread_key_t k = gTLS; + IPCThreadState* st = (IPCThreadState*)pthread_getspecific(k); + if (st) return st; + return new IPCThreadState; + } + + if (gShutdown) return NULL; + + pthread_mutex_lock(&gTLSMutex); + if (!gHaveTLS) { + if (pthread_key_create(&gTLS, threadDestructor) != 0) { + pthread_mutex_unlock(&gTLSMutex); + return NULL; + } + gHaveTLS = true; + } + pthread_mutex_unlock(&gTLSMutex); + goto restart; +} + +void IPCThreadState::shutdown() +{ + gShutdown = true; + + if (gHaveTLS) { + // XXX Need to wait for all thread pool threads to exit! + IPCThreadState* st = (IPCThreadState*)pthread_getspecific(gTLS); + if (st) { + delete st; + pthread_setspecific(gTLS, NULL); + } + gHaveTLS = false; + } +} + +sp<ProcessState> IPCThreadState::process() +{ + return mProcess; +} + +status_t IPCThreadState::clearLastError() +{ + const status_t err = mLastError; + mLastError = NO_ERROR; + return err; +} + +int IPCThreadState::getCallingPid() +{ + return mCallingPid; +} + +int IPCThreadState::getCallingUid() +{ + return mCallingUid; +} + +int64_t IPCThreadState::clearCallingIdentity() +{ + int64_t token = ((int64_t)mCallingUid<<32) | mCallingPid; + clearCaller(); + return token; +} + +void IPCThreadState::restoreCallingIdentity(int64_t token) +{ + mCallingUid = (int)(token>>32); + mCallingPid = (int)token; +} + +void IPCThreadState::clearCaller() +{ + if (mProcess->supportsProcesses()) { + mCallingPid = getpid(); + mCallingUid = getuid(); + } else { + mCallingPid = -1; + mCallingUid = -1; + } +} + +void IPCThreadState::flushCommands() +{ + if (mProcess->mDriverFD <= 0) + return; + talkWithDriver(false); +} + +void IPCThreadState::joinThreadPool(bool isMain) +{ + LOG_THREADPOOL("**** THREAD %p (PID %d) IS JOINING THE THREAD POOL\n", (void*)pthread_self(), getpid()); + + mOut.writeInt32(isMain ? BC_ENTER_LOOPER : BC_REGISTER_LOOPER); + + status_t result; + do { + int32_t cmd; + result = talkWithDriver(); + if (result >= NO_ERROR) { + size_t IN = mIn.dataAvail(); + if (IN < sizeof(int32_t)) continue; + cmd = mIn.readInt32(); + IF_LOG_COMMANDS() { + alog << "Processing top-level Command: " + << getReturnString(cmd) << endl; + } + result = executeCommand(cmd); + } + + // Let this thread exit the thread pool if it is no longer + // needed and it is not the main process thread. + if(result == TIMED_OUT && !isMain) { + break; + } + } while (result != -ECONNREFUSED && result != -EBADF); + + LOG_THREADPOOL("**** THREAD %p (PID %d) IS LEAVING THE THREAD POOL err=%p\n", + (void*)pthread_self(), getpid(), (void*)result); + + mOut.writeInt32(BC_EXIT_LOOPER); + talkWithDriver(false); +} + +void IPCThreadState::stopProcess(bool immediate) +{ + //LOGI("**** STOPPING PROCESS"); + flushCommands(); + int fd = mProcess->mDriverFD; + mProcess->mDriverFD = -1; + close(fd); + //kill(getpid(), SIGKILL); +} + +status_t IPCThreadState::transact(int32_t handle, + uint32_t code, const Parcel& data, + Parcel* reply, uint32_t flags) +{ + status_t err = data.errorCheck(); + + flags |= TF_ACCEPT_FDS; + + IF_LOG_TRANSACTIONS() { + TextOutput::Bundle _b(alog); + alog << "BC_TRANSACTION thr " << (void*)pthread_self() << " / hand " + << handle << " / code " << TypeCode(code) << ": " + << indent << data << dedent << endl; + } + + if (err == NO_ERROR) { + LOG_ONEWAY(">>>> SEND from pid %d uid %d %s", getpid(), getuid(), + (flags & TF_ONE_WAY) == 0 ? "READ REPLY" : "ONE WAY"); + err = writeTransactionData(BC_TRANSACTION, flags, handle, code, data, NULL); + } + + if (err != NO_ERROR) { + if (reply) reply->setError(err); + return (mLastError = err); + } + + if ((flags & TF_ONE_WAY) == 0) { + if (reply) { + err = waitForResponse(reply); + } else { + Parcel fakeReply; + err = waitForResponse(&fakeReply); + } + + IF_LOG_TRANSACTIONS() { + TextOutput::Bundle _b(alog); + alog << "BR_REPLY thr " << (void*)pthread_self() << " / hand " + << handle << ": "; + if (reply) alog << indent << *reply << dedent << endl; + else alog << "(none requested)" << endl; + } + } else { + err = waitForResponse(NULL, NULL); + } + + return err; +} + +void IPCThreadState::incStrongHandle(int32_t handle) +{ + LOG_REMOTEREFS("IPCThreadState::incStrongHandle(%d)\n", handle); + mOut.writeInt32(BC_ACQUIRE); + mOut.writeInt32(handle); +} + +void IPCThreadState::decStrongHandle(int32_t handle) +{ + LOG_REMOTEREFS("IPCThreadState::decStrongHandle(%d)\n", handle); + mOut.writeInt32(BC_RELEASE); + mOut.writeInt32(handle); +} + +void IPCThreadState::incWeakHandle(int32_t handle) +{ + LOG_REMOTEREFS("IPCThreadState::incWeakHandle(%d)\n", handle); + mOut.writeInt32(BC_INCREFS); + mOut.writeInt32(handle); +} + +void IPCThreadState::decWeakHandle(int32_t handle) +{ + LOG_REMOTEREFS("IPCThreadState::decWeakHandle(%d)\n", handle); + mOut.writeInt32(BC_DECREFS); + mOut.writeInt32(handle); +} + +status_t IPCThreadState::attemptIncStrongHandle(int32_t handle) +{ + mOut.writeInt32(BC_ATTEMPT_ACQUIRE); + mOut.writeInt32(0); // xxx was thread priority + mOut.writeInt32(handle); + status_t result = UNKNOWN_ERROR; + + waitForResponse(NULL, &result); + +#if LOG_REFCOUNTS + printf("IPCThreadState::attemptIncStrongHandle(%ld) = %s\n", + handle, result == NO_ERROR ? "SUCCESS" : "FAILURE"); +#endif + + return result; +} + +void IPCThreadState::expungeHandle(int32_t handle, IBinder* binder) +{ +#if LOG_REFCOUNTS + printf("IPCThreadState::expungeHandle(%ld)\n", handle); +#endif + self()->mProcess->expungeHandle(handle, binder); +} + +status_t IPCThreadState::requestDeathNotification(int32_t handle, BpBinder* proxy) +{ + mOut.writeInt32(BC_REQUEST_DEATH_NOTIFICATION); + mOut.writeInt32((int32_t)handle); + mOut.writeInt32((int32_t)proxy); + return NO_ERROR; +} + +status_t IPCThreadState::clearDeathNotification(int32_t handle, BpBinder* proxy) +{ + mOut.writeInt32(BC_CLEAR_DEATH_NOTIFICATION); + mOut.writeInt32((int32_t)handle); + mOut.writeInt32((int32_t)proxy); + return NO_ERROR; +} + +IPCThreadState::IPCThreadState() + : mProcess(ProcessState::self()) +{ + pthread_setspecific(gTLS, this); + clearCaller(); + mIn.setDataCapacity(256); + mOut.setDataCapacity(256); +} + +IPCThreadState::~IPCThreadState() +{ +} + +status_t IPCThreadState::sendReply(const Parcel& reply, uint32_t flags) +{ + status_t err; + status_t statusBuffer; + err = writeTransactionData(BC_REPLY, flags, -1, 0, reply, &statusBuffer); + if (err < NO_ERROR) return err; + + return waitForResponse(NULL, NULL); +} + +status_t IPCThreadState::waitForResponse(Parcel *reply, status_t *acquireResult) +{ + int32_t cmd; + int32_t err; + + while (1) { + if ((err=talkWithDriver()) < NO_ERROR) break; + err = mIn.errorCheck(); + if (err < NO_ERROR) break; + if (mIn.dataAvail() == 0) continue; + + cmd = mIn.readInt32(); + + IF_LOG_COMMANDS() { + alog << "Processing waitForResponse Command: " + << getReturnString(cmd) << endl; + } + + switch (cmd) { + case BR_TRANSACTION_COMPLETE: + if (!reply && !acquireResult) goto finish; + break; + + case BR_DEAD_REPLY: + err = DEAD_OBJECT; + goto finish; + + case BR_FAILED_REPLY: + err = FAILED_TRANSACTION; + goto finish; + + case BR_ACQUIRE_RESULT: + { + LOG_ASSERT(acquireResult != NULL, "Unexpected brACQUIRE_RESULT"); + const int32_t result = mIn.readInt32(); + if (!acquireResult) continue; + *acquireResult = result ? NO_ERROR : INVALID_OPERATION; + } + goto finish; + + case BR_REPLY: + { + binder_transaction_data tr; + err = mIn.read(&tr, sizeof(tr)); + LOG_ASSERT(err == NO_ERROR, "Not enough command data for brREPLY"); + if (err != NO_ERROR) goto finish; + + if (reply) { + if ((tr.flags & TF_STATUS_CODE) == 0) { + reply->ipcSetDataReference( + reinterpret_cast<const uint8_t*>(tr.data.ptr.buffer), + tr.data_size, + reinterpret_cast<const size_t*>(tr.data.ptr.offsets), + tr.offsets_size/sizeof(size_t), + freeBuffer, this); + } else { + err = *static_cast<const status_t*>(tr.data.ptr.buffer); + freeBuffer(NULL, + reinterpret_cast<const uint8_t*>(tr.data.ptr.buffer), + tr.data_size, + reinterpret_cast<const size_t*>(tr.data.ptr.offsets), + tr.offsets_size/sizeof(size_t), this); + } + } else { + freeBuffer(NULL, + reinterpret_cast<const uint8_t*>(tr.data.ptr.buffer), + tr.data_size, + reinterpret_cast<const size_t*>(tr.data.ptr.offsets), + tr.offsets_size/sizeof(size_t), this); + continue; + } + } + goto finish; + + default: + err = executeCommand(cmd); + if (err != NO_ERROR) goto finish; + break; + } + } + +finish: + if (err != NO_ERROR) { + if (acquireResult) *acquireResult = err; + if (reply) reply->setError(err); + mLastError = err; + } + + return err; +} + +status_t IPCThreadState::talkWithDriver(bool doReceive) +{ + LOG_ASSERT(mProcess->mDriverFD >= 0, "Binder driver is not opened"); + + binder_write_read bwr; + + // Is the read buffer empty? + const bool needRead = mIn.dataPosition() >= mIn.dataSize(); + + // We don't want to write anything if we are still reading + // from data left in the input buffer and the caller + // has requested to read the next data. + const size_t outAvail = (!doReceive || needRead) ? mOut.dataSize() : 0; + + bwr.write_size = outAvail; + bwr.write_buffer = (long unsigned int)mOut.data(); + + // This is what we'll read. + if (doReceive && needRead) { + bwr.read_size = mIn.dataCapacity(); + bwr.read_buffer = (long unsigned int)mIn.data(); + } else { + bwr.read_size = 0; + } + + IF_LOG_COMMANDS() { + TextOutput::Bundle _b(alog); + if (outAvail != 0) { + alog << "Sending commands to driver: " << indent; + const void* cmds = (const void*)bwr.write_buffer; + const void* end = ((const uint8_t*)cmds)+bwr.write_size; + alog << HexDump(cmds, bwr.write_size) << endl; + while (cmds < end) cmds = printCommand(alog, cmds); + alog << dedent; + } + alog << "Size of receive buffer: " << bwr.read_size + << ", needRead: " << needRead << ", doReceive: " << doReceive << endl; + } + + // Return immediately if there is nothing to do. + if ((bwr.write_size == 0) && (bwr.read_size == 0)) return NO_ERROR; + + bwr.write_consumed = 0; + bwr.read_consumed = 0; + status_t err; + do { + IF_LOG_COMMANDS() { + alog << "About to read/write, write size = " << mOut.dataSize() << endl; + } +#if defined(HAVE_ANDROID_OS) + if (ioctl(mProcess->mDriverFD, BINDER_WRITE_READ, &bwr) >= 0) + err = NO_ERROR; + else + err = -errno; +#else + err = INVALID_OPERATION; +#endif + IF_LOG_COMMANDS() { + alog << "Finished read/write, write size = " << mOut.dataSize() << endl; + } + } while (err == -EINTR); + + IF_LOG_COMMANDS() { + alog << "Our err: " << (void*)err << ", write consumed: " + << bwr.write_consumed << " (of " << mOut.dataSize() + << "), read consumed: " << bwr.read_consumed << endl; + } + + if (err >= NO_ERROR) { + if (bwr.write_consumed > 0) { + if (bwr.write_consumed < (ssize_t)mOut.dataSize()) + mOut.remove(0, bwr.write_consumed); + else + mOut.setDataSize(0); + } + if (bwr.read_consumed > 0) { + mIn.setDataSize(bwr.read_consumed); + mIn.setDataPosition(0); + } + IF_LOG_COMMANDS() { + TextOutput::Bundle _b(alog); + alog << "Remaining data size: " << mOut.dataSize() << endl; + alog << "Received commands from driver: " << indent; + const void* cmds = mIn.data(); + const void* end = mIn.data() + mIn.dataSize(); + alog << HexDump(cmds, mIn.dataSize()) << endl; + while (cmds < end) cmds = printReturnCommand(alog, cmds); + alog << dedent; + } + return NO_ERROR; + } + + return err; +} + +status_t IPCThreadState::writeTransactionData(int32_t cmd, uint32_t binderFlags, + int32_t handle, uint32_t code, const Parcel& data, status_t* statusBuffer) +{ + binder_transaction_data tr; + + tr.target.handle = handle; + tr.code = code; + tr.flags = binderFlags; + + const status_t err = data.errorCheck(); + if (err == NO_ERROR) { + tr.data_size = data.ipcDataSize(); + tr.data.ptr.buffer = data.ipcData(); + tr.offsets_size = data.ipcObjectsCount()*sizeof(size_t); + tr.data.ptr.offsets = data.ipcObjects(); + } else if (statusBuffer) { + tr.flags |= TF_STATUS_CODE; + *statusBuffer = err; + tr.data_size = sizeof(status_t); + tr.data.ptr.buffer = statusBuffer; + tr.offsets_size = 0; + tr.data.ptr.offsets = NULL; + } else { + return (mLastError = err); + } + + mOut.writeInt32(cmd); + mOut.write(&tr, sizeof(tr)); + + return NO_ERROR; +} + +sp<BBinder> the_context_object; + +void setTheContextObject(sp<BBinder> obj) +{ + the_context_object = obj; +} + +status_t IPCThreadState::executeCommand(int32_t cmd) +{ + BBinder* obj; + RefBase::weakref_type* refs; + status_t result = NO_ERROR; + + switch (cmd) { + case BR_ERROR: + result = mIn.readInt32(); + break; + + case BR_OK: + break; + + case BR_ACQUIRE: + refs = (RefBase::weakref_type*)mIn.readInt32(); + obj = (BBinder*)mIn.readInt32(); + LOG_ASSERT(refs->refBase() == obj, + "BR_ACQUIRE: object %p does not match cookie %p (expected %p)", + refs, obj, refs->refBase()); + obj->incStrong(mProcess.get()); + IF_LOG_REMOTEREFS() { + LOG_REMOTEREFS("BR_ACQUIRE from driver on %p", obj); + obj->printRefs(); + } + mOut.writeInt32(BC_ACQUIRE_DONE); + mOut.writeInt32((int32_t)refs); + mOut.writeInt32((int32_t)obj); + break; + + case BR_RELEASE: + refs = (RefBase::weakref_type*)mIn.readInt32(); + obj = (BBinder*)mIn.readInt32(); + LOG_ASSERT(refs->refBase() == obj, + "BR_RELEASE: object %p does not match cookie %p (expected %p)", + refs, obj, refs->refBase()); + IF_LOG_REMOTEREFS() { + LOG_REMOTEREFS("BR_RELEASE from driver on %p", obj); + obj->printRefs(); + } + obj->decStrong(mProcess.get()); + break; + + case BR_INCREFS: + refs = (RefBase::weakref_type*)mIn.readInt32(); + obj = (BBinder*)mIn.readInt32(); + refs->incWeak(mProcess.get()); + mOut.writeInt32(BC_INCREFS_DONE); + mOut.writeInt32((int32_t)refs); + mOut.writeInt32((int32_t)obj); + break; + + case BR_DECREFS: + refs = (RefBase::weakref_type*)mIn.readInt32(); + obj = (BBinder*)mIn.readInt32(); + // NOTE: This assertion is not valid, because the object may no + // longer exist (thus the (BBinder*)cast above resulting in a different + // memory address). + //LOG_ASSERT(refs->refBase() == obj, + // "BR_DECREFS: object %p does not match cookie %p (expected %p)", + // refs, obj, refs->refBase()); + refs->decWeak(mProcess.get()); + break; + + case BR_ATTEMPT_ACQUIRE: + refs = (RefBase::weakref_type*)mIn.readInt32(); + obj = (BBinder*)mIn.readInt32(); + + { + const bool success = refs->attemptIncStrong(mProcess.get()); + LOG_ASSERT(success && refs->refBase() == obj, + "BR_ATTEMPT_ACQUIRE: object %p does not match cookie %p (expected %p)", + refs, obj, refs->refBase()); + + mOut.writeInt32(BC_ACQUIRE_RESULT); + mOut.writeInt32((int32_t)success); + } + break; + + case BR_TRANSACTION: + { + binder_transaction_data tr; + result = mIn.read(&tr, sizeof(tr)); + LOG_ASSERT(result == NO_ERROR, + "Not enough command data for brTRANSACTION"); + if (result != NO_ERROR) break; + + Parcel buffer; + buffer.ipcSetDataReference( + reinterpret_cast<const uint8_t*>(tr.data.ptr.buffer), + tr.data_size, + reinterpret_cast<const size_t*>(tr.data.ptr.offsets), + tr.offsets_size/sizeof(size_t), freeBuffer, this); + + const pid_t origPid = mCallingPid; + const uid_t origUid = mCallingUid; + + mCallingPid = tr.sender_pid; + mCallingUid = tr.sender_euid; + + //LOGI(">>>> TRANSACT from pid %d uid %d\n", mCallingPid, mCallingUid); + + Parcel reply; + IF_LOG_TRANSACTIONS() { + TextOutput::Bundle _b(alog); + alog << "BR_TRANSACTION thr " << (void*)pthread_self() + << " / obj " << tr.target.ptr << " / code " + << TypeCode(tr.code) << ": " << indent << buffer + << dedent << endl + << "Data addr = " + << reinterpret_cast<const uint8_t*>(tr.data.ptr.buffer) + << ", offsets addr=" + << reinterpret_cast<const size_t*>(tr.data.ptr.offsets) << endl; + } + if (tr.target.ptr) { + sp<BBinder> b((BBinder*)tr.cookie); + const status_t error = b->transact(tr.code, buffer, &reply, 0); + if (error < NO_ERROR) reply.setError(error); + + } else { + const status_t error = the_context_object->transact(tr.code, buffer, &reply, 0); + if (error < NO_ERROR) reply.setError(error); + } + + //LOGI("<<<< TRANSACT from pid %d restore pid %d uid %d\n", + // mCallingPid, origPid, origUid); + + if ((tr.flags & TF_ONE_WAY) == 0) { + LOG_ONEWAY("Sending reply to %d!", mCallingPid); + sendReply(reply, 0); + } else { + LOG_ONEWAY("NOT sending reply to %d!", mCallingPid); + } + + mCallingPid = origPid; + mCallingUid = origUid; + + IF_LOG_TRANSACTIONS() { + TextOutput::Bundle _b(alog); + alog << "BC_REPLY thr " << (void*)pthread_self() << " / obj " + << tr.target.ptr << ": " << indent << reply << dedent << endl; + } + + } + break; + + case BR_DEAD_BINDER: + { + BpBinder *proxy = (BpBinder*)mIn.readInt32(); + proxy->sendObituary(); + mOut.writeInt32(BC_DEAD_BINDER_DONE); + mOut.writeInt32((int32_t)proxy); + } break; + + case BR_CLEAR_DEATH_NOTIFICATION_DONE: + { + BpBinder *proxy = (BpBinder*)mIn.readInt32(); + proxy->getWeakRefs()->decWeak(proxy); + } break; + + case BR_FINISHED: + result = TIMED_OUT; + break; + + case BR_NOOP: + break; + + case BR_SPAWN_LOOPER: + mProcess->spawnPooledThread(false); + break; + + default: + printf("*** BAD COMMAND %d received from Binder driver\n", cmd); + result = UNKNOWN_ERROR; + break; + } + + if (result != NO_ERROR) { + mLastError = result; + } + + return result; +} + +void IPCThreadState::threadDestructor(void *st) +{ + IPCThreadState* const self = static_cast<IPCThreadState*>(st); + if (self) { + self->flushCommands(); +#if defined(HAVE_ANDROID_OS) + ioctl(self->mProcess->mDriverFD, BINDER_THREAD_EXIT, 0); +#endif + delete self; + } +} + + +void IPCThreadState::freeBuffer(Parcel* parcel, const uint8_t* data, size_t dataSize, + const size_t* objects, size_t objectsSize, + void* cookie) +{ + //LOGI("Freeing parcel %p", &parcel); + IF_LOG_COMMANDS() { + alog << "Writing BC_FREE_BUFFER for " << data << endl; + } + LOG_ASSERT(data != NULL, "Called with NULL data"); + if (parcel != NULL) parcel->closeFileDescriptors(); + IPCThreadState* state = self(); + state->mOut.writeInt32(BC_FREE_BUFFER); + state->mOut.writeInt32((int32_t)data); +} + +}; // namespace android diff --git a/libs/utils/IPermissionController.cpp b/libs/utils/IPermissionController.cpp new file mode 100644 index 0000000..f01d38f --- /dev/null +++ b/libs/utils/IPermissionController.cpp @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2005 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. + */ + +#define LOG_TAG "PermissionController" + +#include <utils/IPermissionController.h> + +#include <utils/Debug.h> +#include <utils/Log.h> +#include <utils/Parcel.h> +#include <utils/String8.h> + +#include <private/utils/Static.h> + +namespace android { + +// ---------------------------------------------------------------------- + +class BpPermissionController : public BpInterface<IPermissionController> +{ +public: + BpPermissionController(const sp<IBinder>& impl) + : BpInterface<IPermissionController>(impl) + { + } + + virtual bool checkPermission(const String16& permission, int32_t pid, int32_t uid) + { + Parcel data, reply; + data.writeInterfaceToken(IPermissionController::getInterfaceDescriptor()); + data.writeString16(permission); + data.writeInt32(pid); + data.writeInt32(uid); + remote()->transact(CHECK_PERMISSION_TRANSACTION, data, &reply); + // fail on exception + if (reply.readInt32() != 0) return 0; + return reply.readInt32() != 0; + } +}; + +IMPLEMENT_META_INTERFACE(PermissionController, "android.os.IPermissionController"); + +// ---------------------------------------------------------------------- + +#define CHECK_INTERFACE(interface, data, reply) \ + do { if (!data.enforceInterface(interface::getInterfaceDescriptor())) { \ + LOGW("Call incorrectly routed to " #interface); \ + return PERMISSION_DENIED; \ + } } while (0) + +status_t BnPermissionController::onTransact( + uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags) +{ + //printf("PermissionController received: "); data.print(); + switch(code) { + case CHECK_PERMISSION_TRANSACTION: { + CHECK_INTERFACE(IPermissionController, data, reply); + String16 permission = data.readString16(); + int32_t pid = data.readInt32(); + int32_t uid = data.readInt32(); + bool res = checkPermission(permission, pid, uid); + // write exception + reply->writeInt32(0); + reply->writeInt32(res ? 1 : 0); + return NO_ERROR; + } break; + default: + return BBinder::onTransact(code, data, reply, flags); + } +} + +}; // namespace android + diff --git a/libs/utils/IServiceManager.cpp b/libs/utils/IServiceManager.cpp new file mode 100644 index 0000000..9beeadd --- /dev/null +++ b/libs/utils/IServiceManager.cpp @@ -0,0 +1,230 @@ +/* + * Copyright (C) 2005 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. + */ + +#define LOG_TAG "ServiceManager" + +#include <utils/IServiceManager.h> + +#include <utils/Debug.h> +#include <utils/IPCThreadState.h> +#include <utils/Log.h> +#include <utils/Parcel.h> +#include <utils/String8.h> +#include <utils/SystemClock.h> + +#include <private/utils/Static.h> + +#include <unistd.h> + +namespace android { + +sp<IServiceManager> defaultServiceManager() +{ + if (gDefaultServiceManager != NULL) return gDefaultServiceManager; + + { + AutoMutex _l(gDefaultServiceManagerLock); + if (gDefaultServiceManager == NULL) { + gDefaultServiceManager = interface_cast<IServiceManager>( + ProcessState::self()->getContextObject(NULL)); + } + } + + return gDefaultServiceManager; +} + +bool checkCallingPermission(const String16& permission) +{ + return checkCallingPermission(permission, NULL, NULL); +} + +static String16 _permission("permission"); + +bool checkCallingPermission(const String16& permission, int32_t* outPid, int32_t* outUid) +{ + IPCThreadState* ipcState = IPCThreadState::self(); + int32_t pid = ipcState->getCallingPid(); + int32_t uid = ipcState->getCallingUid(); + if (outPid) *outPid = pid; + if (outUid) *outUid= uid; + + sp<IPermissionController> pc; + gDefaultServiceManagerLock.lock(); + pc = gPermissionController; + gDefaultServiceManagerLock.unlock(); + + int64_t startTime = 0; + + while (true) { + if (pc != NULL) { + bool res = pc->checkPermission(permission, pid, uid); + if (res) { + if (startTime != 0) { + LOGI("Check passed after %d seconds for %s from uid=%d pid=%d", + (int)((uptimeMillis()-startTime)/1000), + String8(permission).string(), uid, pid); + } + return res; + } + + // Is this a permission failure, or did the controller go away? + if (pc->asBinder()->isBinderAlive()) { + LOGW("Permission failure: %s from uid=%d pid=%d", + String8(permission).string(), uid, pid); + return false; + } + + // Object is dead! + gDefaultServiceManagerLock.lock(); + if (gPermissionController == pc) { + gPermissionController = NULL; + } + gDefaultServiceManagerLock.unlock(); + } + + // Need to retrieve the permission controller. + sp<IBinder> binder = defaultServiceManager()->checkService(_permission); + if (binder == NULL) { + // Wait for the permission controller to come back... + if (startTime == 0) { + startTime = uptimeMillis(); + LOGI("Waiting to check permission %s from uid=%d pid=%d", + String8(permission).string(), uid, pid); + } + sleep(1); + } else { + pc = interface_cast<IPermissionController>(binder); + // Install the new permission controller, and try again. + gDefaultServiceManagerLock.lock(); + gPermissionController = pc; + gDefaultServiceManagerLock.unlock(); + } + } +} + +// ---------------------------------------------------------------------- + +class BpServiceManager : public BpInterface<IServiceManager> +{ +public: + BpServiceManager(const sp<IBinder>& impl) + : BpInterface<IServiceManager>(impl) + { + } + + virtual sp<IBinder> getService(const String16& name) const + { + unsigned n; + for (n = 0; n < 5; n++){ + sp<IBinder> svc = checkService(name); + if (svc != NULL) return svc; + LOGI("Waiting for sevice %s...\n", String8(name).string()); + sleep(1); + } + return NULL; + } + + virtual sp<IBinder> checkService( const String16& name) const + { + Parcel data, reply; + data.writeInterfaceToken(IServiceManager::getInterfaceDescriptor()); + data.writeString16(name); + remote()->transact(CHECK_SERVICE_TRANSACTION, data, &reply); + return reply.readStrongBinder(); + } + + virtual status_t addService(const String16& name, const sp<IBinder>& service) + { + Parcel data, reply; + data.writeInterfaceToken(IServiceManager::getInterfaceDescriptor()); + data.writeString16(name); + data.writeStrongBinder(service); + status_t err = remote()->transact(ADD_SERVICE_TRANSACTION, data, &reply); + return err == NO_ERROR ? reply.readInt32() : err; + } + + virtual Vector<String16> listServices() + { + Vector<String16> res; + int n = 0; + + for (;;) { + Parcel data, reply; + data.writeInterfaceToken(IServiceManager::getInterfaceDescriptor()); + data.writeInt32(n++); + status_t err = remote()->transact(LIST_SERVICES_TRANSACTION, data, &reply); + if (err != NO_ERROR) + break; + res.add(reply.readString16()); + } + return res; + } +}; + +IMPLEMENT_META_INTERFACE(ServiceManager, "android.os.IServiceManager"); + +// ---------------------------------------------------------------------- + +#define CHECK_INTERFACE(interface, data, reply) \ + do { if (!data.enforceInterface(interface::getInterfaceDescriptor())) { \ + LOGW("Call incorrectly routed to " #interface); \ + return PERMISSION_DENIED; \ + } } while (0) + +status_t BnServiceManager::onTransact( + uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags) +{ + //printf("ServiceManager received: "); data.print(); + switch(code) { + case GET_SERVICE_TRANSACTION: { + CHECK_INTERFACE(IServiceManager, data, reply); + String16 which = data.readString16(); + sp<IBinder> b = const_cast<BnServiceManager*>(this)->getService(which); + reply->writeStrongBinder(b); + return NO_ERROR; + } break; + case CHECK_SERVICE_TRANSACTION: { + CHECK_INTERFACE(IServiceManager, data, reply); + String16 which = data.readString16(); + sp<IBinder> b = const_cast<BnServiceManager*>(this)->checkService(which); + reply->writeStrongBinder(b); + return NO_ERROR; + } break; + case ADD_SERVICE_TRANSACTION: { + CHECK_INTERFACE(IServiceManager, data, reply); + String16 which = data.readString16(); + sp<IBinder> b = data.readStrongBinder(); + status_t err = addService(which, b); + reply->writeInt32(err); + return NO_ERROR; + } break; + case LIST_SERVICES_TRANSACTION: { + CHECK_INTERFACE(IServiceManager, data, reply); + Vector<String16> list = listServices(); + const size_t N = list.size(); + reply->writeInt32(N); + for (size_t i=0; i<N; i++) { + reply->writeString16(list[i]); + } + return NO_ERROR; + } break; + default: + return BBinder::onTransact(code, data, reply, flags); + } +} + +}; // namespace android + diff --git a/libs/utils/InetAddress.cpp b/libs/utils/InetAddress.cpp new file mode 100644 index 0000000..39a0a68 --- /dev/null +++ b/libs/utils/InetAddress.cpp @@ -0,0 +1,236 @@ +/* + * Copyright (C) 2005 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. + */ + +// +// Internet address class. +// +#ifdef HAVE_WINSOCK +# include <winsock2.h> +#else +# include <sys/types.h> +# include <sys/socket.h> +# include <netinet/in.h> +//# include <arpa/inet.h> +# include <netdb.h> +#endif + +#include <utils/inet_address.h> +#include <utils/threads.h> +#include <utils/Log.h> + +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <assert.h> + +using namespace android; + + +/* + * =========================================================================== + * InetAddress + * =========================================================================== + */ + +// lock for the next couple of functions; could tuck into InetAddress +static Mutex* gGHBNLock; + +/* + * Lock/unlock access to the hostent struct returned by gethostbyname(). + */ +static inline void lock_gethostbyname(void) +{ + if (gGHBNLock == NULL) + gGHBNLock = new Mutex; + gGHBNLock->lock(); +} +static inline void unlock_gethostbyname(void) +{ + assert(gGHBNLock != NULL); + gGHBNLock->unlock(); +} + + +/* + * Constructor -- just init members. This is private so that callers + * are required to use getByName(). + */ +InetAddress::InetAddress(void) + : mAddress(NULL), mLength(-1), mName(NULL) +{ +} + +/* + * Destructor -- free address storage. + */ +InetAddress::~InetAddress(void) +{ + delete[] (char*) mAddress; + delete[] mName; +} + +/* + * Copy constructor. + */ +InetAddress::InetAddress(const InetAddress& orig) +{ + *this = orig; // use assignment code +} + +/* + * Assignment operator. + */ +InetAddress& InetAddress::operator=(const InetAddress& addr) +{ + // handle self-assignment + if (this == &addr) + return *this; + // copy mLength and mAddress + mLength = addr.mLength; + if (mLength > 0) { + mAddress = new char[mLength]; + memcpy(mAddress, addr.mAddress, mLength); + LOG(LOG_DEBUG, "socket", + "HEY: copied %d bytes in assignment operator\n", mLength); + } else { + mAddress = NULL; + } + // copy mName + mName = new char[strlen(addr.mName)+1]; + strcpy(mName, addr.mName); + + return *this; +} + +/* + * Create a new object from a name or a dotted-number IP notation. + * + * Returns NULL on failure. + */ +InetAddress* +InetAddress::getByName(const char* host) +{ + InetAddress* newAddr = NULL; + struct sockaddr_in addr; + struct hostent* he; + DurationTimer hostTimer, lockTimer; + + // gethostbyname() isn't reentrant, so we need to lock things until + // we can copy the data out. + lockTimer.start(); + lock_gethostbyname(); + hostTimer.start(); + + he = gethostbyname(host); + if (he == NULL) { + LOG(LOG_WARN, "socket", "WARNING: cannot resolve host %s\n", host); + unlock_gethostbyname(); + return NULL; + } + + memcpy(&addr.sin_addr, he->h_addr, he->h_length); + addr.sin_family = he->h_addrtype; + addr.sin_port = 0; + + // got it, unlock us + hostTimer.stop(); + he = NULL; + unlock_gethostbyname(); + + lockTimer.stop(); + if ((long) lockTimer.durationUsecs() > 100000) { + long lockTime = (long) lockTimer.durationUsecs(); + long hostTime = (long) hostTimer.durationUsecs(); + LOG(LOG_DEBUG, "socket", + "Lookup of %s took %.3fs (gethostbyname=%.3fs lock=%.3fs)\n", + host, lockTime / 1000000.0, hostTime / 1000000.0, + (lockTime - hostTime) / 1000000.0); + } + + // Alloc storage and copy it over. + newAddr = new InetAddress(); + if (newAddr == NULL) + return NULL; + + newAddr->mLength = sizeof(struct sockaddr_in); + newAddr->mAddress = new char[sizeof(struct sockaddr_in)]; + if (newAddr->mAddress == NULL) { + delete newAddr; + return NULL; + } + memcpy(newAddr->mAddress, &addr, newAddr->mLength); + + // Keep this for debug messages. + newAddr->mName = new char[strlen(host)+1]; + if (newAddr->mName == NULL) { + delete newAddr; + return NULL; + } + strcpy(newAddr->mName, host); + + return newAddr; +} + + +/* + * =========================================================================== + * InetSocketAddress + * =========================================================================== + */ + +/* + * Create an address with the host wildcard (INADDR_ANY). + */ +bool InetSocketAddress::create(int port) +{ + assert(mAddress == NULL); + + mAddress = InetAddress::getByName("0.0.0.0"); + if (mAddress == NULL) + return false; + mPort = port; + return true; +} + +/* + * Create address with host and port specified. + */ +bool InetSocketAddress::create(const InetAddress* addr, int port) +{ + assert(mAddress == NULL); + + mAddress = new InetAddress(*addr); // make a copy + if (mAddress == NULL) + return false; + mPort = port; + return true; +} + +/* + * Create address with host and port specified. + */ +bool InetSocketAddress::create(const char* host, int port) +{ + assert(mAddress == NULL); + + mAddress = InetAddress::getByName(host); + if (mAddress == NULL) + return false; + mPort = port; + return true; +} + diff --git a/libs/utils/LogSocket.cpp b/libs/utils/LogSocket.cpp new file mode 100644 index 0000000..e64f794 --- /dev/null +++ b/libs/utils/LogSocket.cpp @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2008 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. + */ + + +#ifndef HAVE_WINSOCK +#define SOCKETLOG +#endif + +#ifdef SOCKETLOG + +#define LOG_TAG "SOCKETLOG" + +#include <string.h> +#include <cutils/log.h> +#include "utils/LogSocket.h" +#include "utils/logger.h" +#include "cutils/hashmap.h" + +// defined in //device/data/etc/event-log-tags +#define SOCKET_CLOSE_LOG 51000 + +static Hashmap* statsMap = NULL; + +#define LOG_LIST_NUMBER 5 + +typedef struct SocketStats { + int fd; + unsigned int send; + unsigned int recv; + unsigned int ip; + unsigned short port; + short reason; +}SocketStats; + +SocketStats *get_socket_stats(int fd) { + if (statsMap == NULL) { + statsMap = hashmapCreate(8, &hashmapIntHash, &hashmapIntEquals); + } + + SocketStats *s = (SocketStats*) hashmapGet(statsMap, &fd); + if (s == NULL) { + // LOGD("create SocketStats for fd %d", fd); + s = (SocketStats*) malloc(sizeof(SocketStats)); + memset(s, 0, sizeof(SocketStats)); + s->fd = fd; + hashmapPut(statsMap, &s->fd, s); + } + return s; +} + +void log_socket_connect(int fd, unsigned int ip, unsigned short port) { + // LOGD("log_socket_connect for fd %d ip %d port%d", fd, ip, port); + SocketStats *s = get_socket_stats(fd); + s->ip = ip; + s->port = port; +} + +void add_send_stats(int fd, int send) { + if (send <=0) { + LOGE("add_send_stats send %d", send); + return; + } + SocketStats *s = get_socket_stats(fd); + s->send += send; + // LOGD("add_send_stats for fd %d ip %d port%d", fd, s->ip, s->port); +} + +void add_recv_stats(int fd, int recv) { + if (recv <=0) { + LOGE("add_recv_stats recv %d", recv); + return; + } + SocketStats *s = get_socket_stats(fd); + s->recv += recv; + // LOGD("add_recv_stats for fd %d ip %d port%d", fd, s->ip, s->port); +} + +char* put_int(char* buf, int value) { + *buf = EVENT_TYPE_INT; + buf++; + memcpy(buf, &value, sizeof(int)); + return buf + sizeof(int); +} + +void log_socket_close(int fd, short reason) { + if (statsMap) { + SocketStats *s = (SocketStats*) hashmapGet(statsMap, &fd); + if (s != NULL) { + if (s->send != 0 || s->recv != 0) { + s->reason = reason; + // 5 int + list type need 2 bytes + char buf[LOG_LIST_NUMBER * 5 + 2]; + buf[0] = EVENT_TYPE_LIST; + buf[1] = LOG_LIST_NUMBER; + char* writePos = buf + 2; + writePos = put_int(writePos, s->send); + writePos = put_int(writePos, s->recv); + writePos = put_int(writePos, s->ip); + writePos = put_int(writePos, s->port); + writePos = put_int(writePos, s->reason); + + android_bWriteLog(SOCKET_CLOSE_LOG, buf, sizeof(buf)); + // LOGD("send %d recv %d reason %d", s->send, s->recv, s->reason); + } + hashmapRemove(statsMap, &s->fd); + free(s); + } + } +} + +#else +void add_send_stats(int fd, int send) {} +void add_recv_stats(int fd, int recv) {} +void log_socket_close(int fd, short reason) {} +void log_socket_connect(int fd, unsigned int ip, unsigned short port) {} +#endif diff --git a/libs/utils/MODULE_LICENSE_APACHE2 b/libs/utils/MODULE_LICENSE_APACHE2 new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/libs/utils/MODULE_LICENSE_APACHE2 diff --git a/libs/utils/MemoryBase.cpp b/libs/utils/MemoryBase.cpp new file mode 100644 index 0000000..f25e11c --- /dev/null +++ b/libs/utils/MemoryBase.cpp @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2008 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. + */ + + +#include <stdlib.h> +#include <stdint.h> + +#include <utils/MemoryBase.h> + + +namespace android { + +// --------------------------------------------------------------------------- + +MemoryBase::MemoryBase(const sp<IMemoryHeap>& heap, + ssize_t offset, size_t size) + : mSize(size), mOffset(offset), mHeap(heap) +{ +} + +sp<IMemoryHeap> MemoryBase::getMemory(ssize_t* offset, size_t* size) const +{ + if (offset) *offset = mOffset; + if (size) *size = mSize; + return mHeap; +} + +MemoryBase::~MemoryBase() +{ +} + +// --------------------------------------------------------------------------- +}; // namespace android diff --git a/libs/utils/MemoryDealer.cpp b/libs/utils/MemoryDealer.cpp new file mode 100644 index 0000000..e6d1d18 --- /dev/null +++ b/libs/utils/MemoryDealer.cpp @@ -0,0 +1,407 @@ +/* + * Copyright (C) 2007 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. + */ + +#define LOG_TAG "MemoryDealer" + +#include <utils/MemoryDealer.h> + +#include <utils/Log.h> +#include <utils/IPCThreadState.h> +#include <utils/SortedVector.h> +#include <utils/String8.h> +#include <utils/MemoryBase.h> + +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <fcntl.h> +#include <unistd.h> +#include <errno.h> +#include <string.h> + +#include <sys/stat.h> +#include <sys/types.h> +#include <sys/mman.h> +#include <sys/file.h> + +namespace android { + + +// ---------------------------------------------------------------------------- + +class SimpleMemory : public MemoryBase { +public: + SimpleMemory(const sp<IMemoryHeap>& heap, ssize_t offset, size_t size); + virtual ~SimpleMemory(); +}; + + +// ---------------------------------------------------------------------------- + +MemoryDealer::Allocation::Allocation( + const sp<MemoryDealer>& dealer, ssize_t offset, size_t size, + const sp<IMemory>& memory) + : mDealer(dealer), mOffset(offset), mSize(size), mMemory(memory) +{ +} + +MemoryDealer::Allocation::~Allocation() +{ + if (mSize) { + /* NOTE: it's VERY important to not free allocations of size 0 because + * they're special as they don't have any record in the allocator + * and could alias some real allocation (their offset is zero). */ + mDealer->deallocate(mOffset); + } +} + +sp<IMemoryHeap> MemoryDealer::Allocation::getMemory( + ssize_t* offset, size_t* size) const +{ + return mMemory->getMemory(offset, size); +} + +// ---------------------------------------------------------------------------- + +MemoryDealer::MemoryDealer(size_t size, uint32_t flags, const char* name) + : mHeap(new SharedHeap(size, flags, name)), + mAllocator(new SimpleBestFitAllocator(size)) +{ +} + +MemoryDealer::MemoryDealer(const sp<HeapInterface>& heap) + : mHeap(heap), + mAllocator(new SimpleBestFitAllocator(heap->virtualSize())) +{ +} + +MemoryDealer::MemoryDealer( const sp<HeapInterface>& heap, + const sp<AllocatorInterface>& allocator) + : mHeap(heap), mAllocator(allocator) +{ +} + +MemoryDealer::~MemoryDealer() +{ +} + +sp<IMemory> MemoryDealer::allocate(size_t size, uint32_t flags) +{ + sp<IMemory> memory; + const ssize_t offset = allocator()->allocate(size, flags); + if (offset >= 0) { + sp<IMemory> new_memory = heap()->mapMemory(offset, size); + if (new_memory != 0) { + memory = new Allocation(this, offset, size, new_memory); + } else { + LOGE("couldn't map [%8x, %d]", offset, size); + if (size) { + /* NOTE: it's VERY important to not free allocations of size 0 + * because they're special as they don't have any record in the + * allocator and could alias some real allocation + * (their offset is zero). */ + allocator()->deallocate(offset); + } + } + } + return memory; +} + +void MemoryDealer::deallocate(size_t offset) +{ + allocator()->deallocate(offset); +} + +void MemoryDealer::dump(const char* what, uint32_t flags) const +{ + allocator()->dump(what, flags); +} + +const sp<HeapInterface>& MemoryDealer::heap() const { + return mHeap; +} + +const sp<AllocatorInterface>& MemoryDealer::allocator() const { + return mAllocator; +} + +// ---------------------------------------------------------------------------- + +// align all the memory blocks on a cache-line boundary +const int SimpleBestFitAllocator::kMemoryAlign = 32; + +SimpleBestFitAllocator::SimpleBestFitAllocator(size_t size) +{ + size_t pagesize = getpagesize(); + mHeapSize = ((size + pagesize-1) & ~(pagesize-1)); + + chunk_t* node = new chunk_t(0, mHeapSize / kMemoryAlign); + mList.insertHead(node); +} + +SimpleBestFitAllocator::~SimpleBestFitAllocator() +{ + while(!mList.isEmpty()) { + delete mList.remove(mList.head()); + } +} + +size_t SimpleBestFitAllocator::size() const +{ + return mHeapSize; +} + +size_t SimpleBestFitAllocator::allocate(size_t size, uint32_t flags) +{ + Mutex::Autolock _l(mLock); + ssize_t offset = alloc(size, flags); + return offset; +} + +status_t SimpleBestFitAllocator::deallocate(size_t offset) +{ + Mutex::Autolock _l(mLock); + chunk_t const * const freed = dealloc(offset); + if (freed) { + return NO_ERROR; + } + return NAME_NOT_FOUND; +} + +ssize_t SimpleBestFitAllocator::alloc(size_t size, uint32_t flags) +{ + if (size == 0) { + return 0; + } + size = (size + kMemoryAlign-1) / kMemoryAlign; + chunk_t* free_chunk = 0; + chunk_t* cur = mList.head(); + + size_t pagesize = getpagesize(); + while (cur) { + int extra = 0; + if (flags & PAGE_ALIGNED) + extra = ( -cur->start & ((pagesize/kMemoryAlign)-1) ) ; + + // best fit + if (cur->free && (cur->size >= (size+extra))) { + if ((!free_chunk) || (cur->size < free_chunk->size)) { + free_chunk = cur; + } + if (cur->size == size) { + break; + } + } + cur = cur->next; + } + + if (free_chunk) { + const size_t free_size = free_chunk->size; + free_chunk->free = 0; + free_chunk->size = size; + if (free_size > size) { + int extra = 0; + if (flags & PAGE_ALIGNED) + extra = ( -free_chunk->start & ((pagesize/kMemoryAlign)-1) ) ; + if (extra) { + chunk_t* split = new chunk_t(free_chunk->start, extra); + free_chunk->start += extra; + mList.insertBefore(free_chunk, split); + } + + LOGE_IF((flags&PAGE_ALIGNED) && + ((free_chunk->start*kMemoryAlign)&(pagesize-1)), + "PAGE_ALIGNED requested, but page is not aligned!!!"); + + const ssize_t tail_free = free_size - (size+extra); + if (tail_free > 0) { + chunk_t* split = new chunk_t( + free_chunk->start + free_chunk->size, tail_free); + mList.insertAfter(free_chunk, split); + } + } + return (free_chunk->start)*kMemoryAlign; + } + return NO_MEMORY; +} + +SimpleBestFitAllocator::chunk_t* SimpleBestFitAllocator::dealloc(size_t start) +{ + start = start / kMemoryAlign; + chunk_t* cur = mList.head(); + while (cur) { + if (cur->start == start) { + LOG_FATAL_IF(cur->free, + "block at offset 0x%08lX of size 0x%08lX already freed", + cur->start*kMemoryAlign, cur->size*kMemoryAlign); + + // merge freed blocks together + chunk_t* freed = cur; + cur->free = 1; + do { + chunk_t* const p = cur->prev; + chunk_t* const n = cur->next; + if (p && (p->free || !cur->size)) { + freed = p; + p->size += cur->size; + mList.remove(cur); + delete cur; + } + cur = n; + } while (cur && cur->free); + + #ifndef NDEBUG + if (!freed->free) { + dump_l("dealloc (!freed->free)"); + } + #endif + LOG_FATAL_IF(!freed->free, + "freed block at offset 0x%08lX of size 0x%08lX is not free!", + freed->start * kMemoryAlign, freed->size * kMemoryAlign); + + return freed; + } + cur = cur->next; + } + return 0; +} + +void SimpleBestFitAllocator::dump(const char* what, uint32_t flags) const +{ + Mutex::Autolock _l(mLock); + dump_l(what, flags); +} + +void SimpleBestFitAllocator::dump_l(const char* what, uint32_t flags) const +{ + String8 result; + dump_l(result, what, flags); + LOGD("%s", result.string()); +} + +void SimpleBestFitAllocator::dump(String8& result, + const char* what, uint32_t flags) const +{ + Mutex::Autolock _l(mLock); + dump_l(result, what, flags); +} + +void SimpleBestFitAllocator::dump_l(String8& result, + const char* what, uint32_t flags) const +{ + size_t size = 0; + int32_t i = 0; + chunk_t const* cur = mList.head(); + + const size_t SIZE = 256; + char buffer[SIZE]; + snprintf(buffer, SIZE, " %s (%p, size=%u)\n", + what, this, (unsigned int)mHeapSize); + + result.append(buffer); + + while (cur) { + const char* errs[] = {"", "| link bogus NP", + "| link bogus PN", "| link bogus NP+PN" }; + int np = ((cur->next) && cur->next->prev != cur) ? 1 : 0; + int pn = ((cur->prev) && cur->prev->next != cur) ? 2 : 0; + + snprintf(buffer, SIZE, " %3u: %08x | 0x%08X | 0x%08X | %s %s\n", + i, int(cur), int(cur->start*kMemoryAlign), + int(cur->size*kMemoryAlign), + int(cur->free) ? "F" : "A", + errs[np|pn]); + + result.append(buffer); + + if (!cur->free) + size += cur->size*kMemoryAlign; + + i++; + cur = cur->next; + } + snprintf(buffer, SIZE, " size allocated: %u (%u KB)\n", int(size), int(size/1024)); + result.append(buffer); +} + +// ---------------------------------------------------------------------------- + + +SharedHeap::SharedHeap(size_t size, uint32_t flags, char const * name) + : MemoryHeapBase(size, flags, name) +{ +} + +SharedHeap::~SharedHeap() +{ +} + +sp<IMemory> SharedHeap::mapMemory(size_t offset, size_t size) +{ + return new SimpleMemory(this, offset, size); +} + + +SimpleMemory::SimpleMemory(const sp<IMemoryHeap>& heap, + ssize_t offset, size_t size) + : MemoryBase(heap, offset, size) +{ +#ifndef NDEBUG + void* const start_ptr = (void*)(intptr_t(heap->base()) + offset); + memset(start_ptr, 0xda, size); +#endif +} + +SimpleMemory::~SimpleMemory() +{ + size_t freedOffset = getOffset(); + size_t freedSize = getSize(); + + // keep the size to unmap in excess + size_t pagesize = getpagesize(); + size_t start = freedOffset; + size_t end = start + freedSize; + start &= ~(pagesize-1); + end = (end + pagesize-1) & ~(pagesize-1); + + // give back to the kernel the pages we don't need + size_t free_start = freedOffset; + size_t free_end = free_start + freedSize; + if (start < free_start) + start = free_start; + if (end > free_end) + end = free_end; + start = (start + pagesize-1) & ~(pagesize-1); + end &= ~(pagesize-1); + + void* const start_ptr = (void*)(intptr_t(getHeap()->base()) + start); + size_t size = end-start; + +#ifndef NDEBUG + memset(start_ptr, 0xdf, size); +#endif + +// MADV_REMOVE is not defined on Dapper based Goobuntu +#ifdef MADV_REMOVE + if (size) { + int err = madvise(start_ptr, size, MADV_REMOVE); + LOGW_IF(err, "madvise(%p, %u, MADV_REMOVE) returned %s", + start_ptr, size, err<0 ? strerror(errno) : "Ok"); + } +#endif +} + +}; // namespace android diff --git a/libs/utils/MemoryHeapBase.cpp b/libs/utils/MemoryHeapBase.cpp new file mode 100644 index 0000000..59963c9 --- /dev/null +++ b/libs/utils/MemoryHeapBase.cpp @@ -0,0 +1,178 @@ +/* + * Copyright (C) 2008 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. + */ + +#define LOG_TAG "MemoryHeapBase" + +#include <stdlib.h> +#include <stdint.h> +#include <unistd.h> +#include <fcntl.h> +#include <errno.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/ioctl.h> + +#include <cutils/log.h> +#include <cutils/ashmem.h> +#include <cutils/atomic.h> + +#include <utils/MemoryHeapBase.h> + +#if HAVE_ANDROID_OS +#include <linux/android_pmem.h> +#endif + + +namespace android { + +// --------------------------------------------------------------------------- + +MemoryHeapBase::MemoryHeapBase() + : mFD(-1), mSize(0), mBase(MAP_FAILED), + mDevice(NULL), mNeedUnmap(false) +{ +} + +MemoryHeapBase::MemoryHeapBase(size_t size, uint32_t flags, char const * name) + : mFD(-1), mSize(0), mBase(MAP_FAILED), mFlags(flags), + mDevice(0), mNeedUnmap(false) +{ + const size_t pagesize = getpagesize(); + size = ((size + pagesize-1) & ~(pagesize-1)); + int fd = ashmem_create_region(name == NULL ? "MemoryHeapBase" : name, size); + LOGE_IF(fd<0, "error creating ashmem region: %s", strerror(errno)); + if (fd >= 0) { + if (mapfd(fd, size) == NO_ERROR) { + if (flags & READ_ONLY) { + ashmem_set_prot_region(fd, PROT_READ); + } + } + } +} + +MemoryHeapBase::MemoryHeapBase(const char* device, size_t size, uint32_t flags) + : mFD(-1), mSize(0), mBase(MAP_FAILED), mFlags(flags), + mDevice(0), mNeedUnmap(false) +{ + int fd = open(device, O_RDWR); + LOGE_IF(fd<0, "error opening %s: %s", device, strerror(errno)); + if (fd >= 0) { + const size_t pagesize = getpagesize(); + size = ((size + pagesize-1) & ~(pagesize-1)); + if (mapfd(fd, size) == NO_ERROR) { + mDevice = device; + } + } +} + +MemoryHeapBase::MemoryHeapBase(int fd, size_t size, uint32_t flags) + : mFD(-1), mSize(0), mBase(MAP_FAILED), mFlags(flags), + mDevice(0), mNeedUnmap(false) +{ + const size_t pagesize = getpagesize(); + size = ((size + pagesize-1) & ~(pagesize-1)); + mapfd(dup(fd), size); +} + +status_t MemoryHeapBase::init(int fd, void *base, int size, int flags, const char* device) +{ + if (mFD != -1) { + return INVALID_OPERATION; + } + mFD = fd; + mBase = base; + mSize = size; + mFlags = flags; + mDevice = device; + return NO_ERROR; +} + +status_t MemoryHeapBase::mapfd(int fd, size_t size) +{ + if (size == 0) { + // try to figure out the size automatically +#if HAVE_ANDROID_OS + // first try the PMEM ioctl + pmem_region reg; + int err = ioctl(fd, PMEM_GET_TOTAL_SIZE, ®); + if (err == 0) + size = reg.len; +#endif + if (size == 0) { // try fstat + struct stat sb; + if (fstat(fd, &sb) == 0) + size = sb.st_size; + } + // if it didn't work, let mmap() fail. + } + + void* base = (uint8_t*)mmap(0, size, + PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0); + if (base == MAP_FAILED) { + LOGE("mmap(fd=%d, size=%u) failed (%s)", + fd, uint32_t(size), strerror(errno)); + close(fd); + return -errno; + } + //LOGD("mmap(fd=%d, base=%p, size=%lu)", fd, base, size); + mFD = fd; + mBase = base; + mSize = size; + mNeedUnmap = true; + return NO_ERROR; +} + +MemoryHeapBase::~MemoryHeapBase() +{ + dispose(); +} + +void MemoryHeapBase::dispose() +{ + int fd = android_atomic_or(-1, &mFD); + if (fd >= 0) { + if (mNeedUnmap) { + //LOGD("munmap(fd=%d, base=%p, size=%lu)", fd, mBase, mSize); + munmap(mBase, mSize); + } + mBase = 0; + mSize = 0; + close(fd); + } +} + +int MemoryHeapBase::getHeapID() const { + return mFD; +} + +void* MemoryHeapBase::getBase() const { + return mBase; +} + +size_t MemoryHeapBase::getSize() const { + return mSize; +} + +uint32_t MemoryHeapBase::getFlags() const { + return mFlags; +} + +const char* MemoryHeapBase::getDevice() const { + return mDevice; +} + +// --------------------------------------------------------------------------- +}; // namespace android diff --git a/libs/utils/MemoryHeapPmem.cpp b/libs/utils/MemoryHeapPmem.cpp new file mode 100644 index 0000000..1e5a1cc --- /dev/null +++ b/libs/utils/MemoryHeapPmem.cpp @@ -0,0 +1,226 @@ +/* + * Copyright (C) 2008 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. + */ + +#define LOG_TAG "MemoryHeapPmem" + +#include <stdlib.h> +#include <stdint.h> +#include <unistd.h> +#include <fcntl.h> +#include <errno.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/ioctl.h> + +#include <cutils/log.h> + +#include <utils/MemoryHeapPmem.h> +#include <utils/MemoryHeapBase.h> + +#if HAVE_ANDROID_OS +#include <linux/android_pmem.h> +#endif + +namespace android { + +// --------------------------------------------------------------------------- + +class MemoryHeapPmem; + +class SubRegionMemory : public BnMemory { +public: + SubRegionMemory(const sp<MemoryHeapPmem>& heap, ssize_t offset, size_t size); + virtual ~SubRegionMemory(); + virtual sp<IMemoryHeap> getMemory(ssize_t* offset, size_t* size) const; +private: + friend class MemoryHeapPmem; + void revoke(); + size_t mSize; + ssize_t mOffset; + sp<MemoryHeapPmem> mClientHeap; +}; + +SubRegionMemory::SubRegionMemory(const sp<MemoryHeapPmem>& heap, + ssize_t offset, size_t size) + : mSize(size), mOffset(offset), mClientHeap(heap) +{ +#ifndef NDEBUG + void* const start_ptr = (void*)(intptr_t(mClientHeap->base()) + offset); + memset(start_ptr, 0xda, size); +#endif + +#if HAVE_ANDROID_OS + if (size > 0) { + const size_t pagesize = getpagesize(); + size = (size + pagesize-1) & ~(pagesize-1); + int our_fd = heap->heapID(); + struct pmem_region sub = { offset, size }; + int err = ioctl(our_fd, PMEM_MAP, &sub); + LOGE_IF(err<0, "PMEM_MAP failed (%s), " + "mFD=%d, sub.offset=%lu, sub.size=%lu", + strerror(errno), our_fd, sub.offset, sub.len); +} +#endif +} + +sp<IMemoryHeap> SubRegionMemory::getMemory(ssize_t* offset, size_t* size) const +{ + if (offset) *offset = mOffset; + if (size) *size = mSize; + return mClientHeap; +} + +SubRegionMemory::~SubRegionMemory() +{ + revoke(); +} + + +void SubRegionMemory::revoke() +{ + // NOTE: revoke() doesn't need to be protected by a lock because it + // can only be called from MemoryHeapPmem::revoke(), which means + // that we can't be in ~SubRegionMemory(), or in ~SubRegionMemory(), + // which means MemoryHeapPmem::revoke() wouldn't have been able to + // promote() it. + +#if HAVE_ANDROID_OS + if (mClientHeap != NULL) { + int our_fd = mClientHeap->heapID(); + struct pmem_region sub; + sub.offset = mOffset; + sub.len = mSize; + int err = ioctl(our_fd, PMEM_UNMAP, &sub); + LOGE_IF(err<0, "PMEM_UNMAP failed (%s), " + "mFD=%d, sub.offset=%lu, sub.size=%lu", + strerror(errno), our_fd, sub.offset, sub.len); + mClientHeap.clear(); + } +#endif +} + +// --------------------------------------------------------------------------- + +MemoryHeapPmem::MemoryHeapPmem(const sp<MemoryHeapBase>& pmemHeap, + uint32_t flags) + : HeapInterface(), MemoryHeapBase() +{ + char const * const device = pmemHeap->getDevice(); +#if HAVE_ANDROID_OS + if (device) { + int fd = open(device, O_RDWR); + LOGE_IF(fd<0, "couldn't open %s (%s)", device, strerror(errno)); + if (fd >= 0) { + int err = ioctl(fd, PMEM_CONNECT, pmemHeap->heapID()); + if (err < 0) { + LOGE("PMEM_CONNECT failed (%s), mFD=%d, sub-fd=%d", + strerror(errno), fd, pmemHeap->heapID()); + close(fd); + } else { + // everything went well... + mParentHeap = pmemHeap; + MemoryHeapBase::init(fd, + pmemHeap->getBase(), + pmemHeap->getSize(), + pmemHeap->getFlags() | flags, + device); + } + } + } +#else + mParentHeap = pmemHeap; + MemoryHeapBase::init( + dup(pmemHeap->heapID()), + pmemHeap->getBase(), + pmemHeap->getSize(), + pmemHeap->getFlags() | flags, + device); +#endif +} + +MemoryHeapPmem::~MemoryHeapPmem() +{ +} + +sp<IMemory> MemoryHeapPmem::mapMemory(size_t offset, size_t size) +{ + sp<SubRegionMemory> memory; + if (heapID() > 0) + memory = new SubRegionMemory(this, offset, size); + + if (memory != 0) { + Mutex::Autolock _l(mLock); + mAllocations.add(memory); + } + return memory; +} + +status_t MemoryHeapPmem::slap() +{ +#if HAVE_ANDROID_OS + size_t size = getSize(); + const size_t pagesize = getpagesize(); + size = (size + pagesize-1) & ~(pagesize-1); + int our_fd = getHeapID(); + struct pmem_region sub = { 0, size }; + int err = ioctl(our_fd, PMEM_MAP, &sub); + LOGE_IF(err<0, "PMEM_MAP failed (%s), " + "mFD=%d, sub.offset=%lu, sub.size=%lu", + strerror(errno), our_fd, sub.offset, sub.len); + return -errno; +#else + return NO_ERROR; +#endif +} + +status_t MemoryHeapPmem::unslap() +{ +#if HAVE_ANDROID_OS + size_t size = getSize(); + const size_t pagesize = getpagesize(); + size = (size + pagesize-1) & ~(pagesize-1); + int our_fd = getHeapID(); + struct pmem_region sub = { 0, size }; + int err = ioctl(our_fd, PMEM_UNMAP, &sub); + LOGE_IF(err<0, "PMEM_UNMAP failed (%s), " + "mFD=%d, sub.offset=%lu, sub.size=%lu", + strerror(errno), our_fd, sub.offset, sub.len); + return -errno; +#else + return NO_ERROR; +#endif +} + +void MemoryHeapPmem::revoke() +{ + Vector< wp<SubRegionMemory> > allocations; + + { // scope for lock + Mutex::Autolock _l(mLock); + allocations = mAllocations; + mAllocations.clear(); + } + + ssize_t count = allocations.size(); + for (ssize_t i=0 ; i<count ; i++) { + sp<SubRegionMemory> memory(allocations[i].promote()); + if (memory != 0) + memory->revoke(); + } +} + +// --------------------------------------------------------------------------- +}; // namespace android diff --git a/libs/utils/NOTICE b/libs/utils/NOTICE new file mode 100644 index 0000000..c5b1efa --- /dev/null +++ b/libs/utils/NOTICE @@ -0,0 +1,190 @@ + + Copyright (c) 2005-2008, 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. + + 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. + + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + diff --git a/libs/utils/Parcel.cpp b/libs/utils/Parcel.cpp new file mode 100644 index 0000000..3eca4b0 --- /dev/null +++ b/libs/utils/Parcel.cpp @@ -0,0 +1,1311 @@ +/* + * Copyright (C) 2005 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. + */ + +#define LOG_TAG "Parcel" +//#define LOG_NDEBUG 0 + +#include <utils/Parcel.h> + +#include <utils/Binder.h> +#include <utils/BpBinder.h> +#include <utils/Debug.h> +#include <utils/ProcessState.h> +#include <utils/Log.h> +#include <utils/String8.h> +#include <utils/String16.h> +#include <utils/TextOutput.h> +#include <utils/misc.h> + +#include <private/utils/binder_module.h> + +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> + +#ifndef INT32_MAX +#define INT32_MAX ((int32_t)(2147483647)) +#endif + +#define LOG_REFS(...) +//#define LOG_REFS(...) LOG(LOG_DEBUG, "Parcel", __VA_ARGS__) + +// --------------------------------------------------------------------------- + +#define PAD_SIZE(s) (((s)+3)&~3) + +// XXX This can be made public if we want to provide +// support for typed data. +struct small_flat_data +{ + uint32_t type; + uint32_t data; +}; + +namespace android { + +void acquire_object(const sp<ProcessState>& proc, + const flat_binder_object& obj, const void* who) +{ + switch (obj.type) { + case BINDER_TYPE_BINDER: + if (obj.binder) { + LOG_REFS("Parcel %p acquiring reference on local %p", who, obj.cookie); + static_cast<IBinder*>(obj.cookie)->incStrong(who); + } + return; + case BINDER_TYPE_WEAK_BINDER: + if (obj.binder) + static_cast<RefBase::weakref_type*>(obj.binder)->incWeak(who); + return; + case BINDER_TYPE_HANDLE: { + const sp<IBinder> b = proc->getStrongProxyForHandle(obj.handle); + if (b != NULL) { + LOG_REFS("Parcel %p acquiring reference on remote %p", who, b.get()); + b->incStrong(who); + } + return; + } + case BINDER_TYPE_WEAK_HANDLE: { + const wp<IBinder> b = proc->getWeakProxyForHandle(obj.handle); + if (b != NULL) b.get_refs()->incWeak(who); + return; + } + case BINDER_TYPE_FD: { + // intentionally blank -- nothing to do to acquire this, but we do + // recognize it as a legitimate object type. + return; + } + } + + LOGD("Invalid object type 0x%08lx", obj.type); +} + +void release_object(const sp<ProcessState>& proc, + const flat_binder_object& obj, const void* who) +{ + switch (obj.type) { + case BINDER_TYPE_BINDER: + if (obj.binder) { + LOG_REFS("Parcel %p releasing reference on local %p", who, obj.cookie); + static_cast<IBinder*>(obj.cookie)->decStrong(who); + } + return; + case BINDER_TYPE_WEAK_BINDER: + if (obj.binder) + static_cast<RefBase::weakref_type*>(obj.binder)->decWeak(who); + return; + case BINDER_TYPE_HANDLE: { + const sp<IBinder> b = proc->getStrongProxyForHandle(obj.handle); + if (b != NULL) { + LOG_REFS("Parcel %p releasing reference on remote %p", who, b.get()); + b->decStrong(who); + } + return; + } + case BINDER_TYPE_WEAK_HANDLE: { + const wp<IBinder> b = proc->getWeakProxyForHandle(obj.handle); + if (b != NULL) b.get_refs()->decWeak(who); + return; + } + case BINDER_TYPE_FD: { + if (obj.cookie != (void*)0) close(obj.handle); + return; + } + } + + LOGE("Invalid object type 0x%08lx", obj.type); +} + +inline static status_t finish_flatten_binder( + const sp<IBinder>& binder, const flat_binder_object& flat, Parcel* out) +{ + return out->writeObject(flat, false); +} + +status_t flatten_binder(const sp<ProcessState>& proc, + const sp<IBinder>& binder, Parcel* out) +{ + flat_binder_object obj; + + obj.flags = 0x7f | FLAT_BINDER_FLAG_ACCEPTS_FDS; + if (binder != NULL) { + IBinder *local = binder->localBinder(); + if (!local) { + BpBinder *proxy = binder->remoteBinder(); + if (proxy == NULL) { + LOGE("null proxy"); + } + const int32_t handle = proxy ? proxy->handle() : 0; + obj.type = BINDER_TYPE_HANDLE; + obj.handle = handle; + obj.cookie = NULL; + } else { + obj.type = BINDER_TYPE_BINDER; + obj.binder = local->getWeakRefs(); + obj.cookie = local; + } + } else { + obj.type = BINDER_TYPE_BINDER; + obj.binder = NULL; + obj.cookie = NULL; + } + + return finish_flatten_binder(binder, obj, out); +} + +status_t flatten_binder(const sp<ProcessState>& proc, + const wp<IBinder>& binder, Parcel* out) +{ + flat_binder_object obj; + + obj.flags = 0x7f | FLAT_BINDER_FLAG_ACCEPTS_FDS; + if (binder != NULL) { + sp<IBinder> real = binder.promote(); + if (real != NULL) { + IBinder *local = real->localBinder(); + if (!local) { + BpBinder *proxy = real->remoteBinder(); + if (proxy == NULL) { + LOGE("null proxy"); + } + const int32_t handle = proxy ? proxy->handle() : 0; + obj.type = BINDER_TYPE_WEAK_HANDLE; + obj.handle = handle; + obj.cookie = NULL; + } else { + obj.type = BINDER_TYPE_WEAK_BINDER; + obj.binder = binder.get_refs(); + obj.cookie = binder.unsafe_get(); + } + return finish_flatten_binder(real, obj, out); + } + + // XXX How to deal? In order to flatten the given binder, + // we need to probe it for information, which requires a primary + // reference... but we don't have one. + // + // The OpenBinder implementation uses a dynamic_cast<> here, + // but we can't do that with the different reference counting + // implementation we are using. + LOGE("Unable to unflatten Binder weak reference!"); + obj.type = BINDER_TYPE_BINDER; + obj.binder = NULL; + obj.cookie = NULL; + return finish_flatten_binder(NULL, obj, out); + + } else { + obj.type = BINDER_TYPE_BINDER; + obj.binder = NULL; + obj.cookie = NULL; + return finish_flatten_binder(NULL, obj, out); + } +} + +inline static status_t finish_unflatten_binder( + BpBinder* proxy, const flat_binder_object& flat, const Parcel& in) +{ + return NO_ERROR; +} + +status_t unflatten_binder(const sp<ProcessState>& proc, + const Parcel& in, sp<IBinder>* out) +{ + const flat_binder_object* flat = in.readObject(false); + + if (flat) { + switch (flat->type) { + case BINDER_TYPE_BINDER: + *out = static_cast<IBinder*>(flat->cookie); + return finish_unflatten_binder(NULL, *flat, in); + case BINDER_TYPE_HANDLE: + *out = proc->getStrongProxyForHandle(flat->handle); + return finish_unflatten_binder( + static_cast<BpBinder*>(out->get()), *flat, in); + } + } + return BAD_TYPE; +} + +status_t unflatten_binder(const sp<ProcessState>& proc, + const Parcel& in, wp<IBinder>* out) +{ + const flat_binder_object* flat = in.readObject(false); + + if (flat) { + switch (flat->type) { + case BINDER_TYPE_BINDER: + *out = static_cast<IBinder*>(flat->cookie); + return finish_unflatten_binder(NULL, *flat, in); + case BINDER_TYPE_WEAK_BINDER: + if (flat->binder != NULL) { + out->set_object_and_refs( + static_cast<IBinder*>(flat->cookie), + static_cast<RefBase::weakref_type*>(flat->binder)); + } else { + *out = NULL; + } + return finish_unflatten_binder(NULL, *flat, in); + case BINDER_TYPE_HANDLE: + case BINDER_TYPE_WEAK_HANDLE: + *out = proc->getWeakProxyForHandle(flat->handle); + return finish_unflatten_binder( + static_cast<BpBinder*>(out->unsafe_get()), *flat, in); + } + } + return BAD_TYPE; +} + +// --------------------------------------------------------------------------- + +Parcel::Parcel() +{ + initState(); +} + +Parcel::~Parcel() +{ + freeDataNoInit(); +} + +const uint8_t* Parcel::data() const +{ + return mData; +} + +size_t Parcel::dataSize() const +{ + return (mDataSize > mDataPos ? mDataSize : mDataPos); +} + +size_t Parcel::dataAvail() const +{ + // TODO: decide what to do about the possibility that this can + // report an available-data size that exceeds a Java int's max + // positive value, causing havoc. Fortunately this will only + // happen if someone constructs a Parcel containing more than two + // gigabytes of data, which on typical phone hardware is simply + // not possible. + return dataSize() - dataPosition(); +} + +size_t Parcel::dataPosition() const +{ + return mDataPos; +} + +size_t Parcel::dataCapacity() const +{ + return mDataCapacity; +} + +status_t Parcel::setDataSize(size_t size) +{ + status_t err; + err = continueWrite(size); + if (err == NO_ERROR) { + mDataSize = size; + LOGV("setDataSize Setting data size of %p to %d\n", this, mDataSize); + } + return err; +} + +void Parcel::setDataPosition(size_t pos) const +{ + mDataPos = pos; + mNextObjectHint = 0; +} + +status_t Parcel::setDataCapacity(size_t size) +{ + if (size > mDataSize) return continueWrite(size); + return NO_ERROR; +} + +status_t Parcel::setData(const uint8_t* buffer, size_t len) +{ + status_t err = restartWrite(len); + if (err == NO_ERROR) { + memcpy(const_cast<uint8_t*>(data()), buffer, len); + mDataSize = len; + mFdsKnown = false; + } + return err; +} + +status_t Parcel::appendFrom(Parcel *parcel, size_t offset, size_t len) +{ + const sp<ProcessState> proc(ProcessState::self()); + status_t err; + uint8_t *data = parcel->mData; + size_t *objects = parcel->mObjects; + size_t size = parcel->mObjectsSize; + int startPos = mDataPos; + int firstIndex = -1, lastIndex = -2; + + if (len == 0) { + return NO_ERROR; + } + + // range checks against the source parcel size + if ((offset > parcel->mDataSize) + || (len > parcel->mDataSize) + || (offset + len > parcel->mDataSize)) { + return BAD_VALUE; + } + + // Count objects in range + for (int i = 0; i < (int) size; i++) { + size_t off = objects[i]; + if ((off >= offset) && (off < offset + len)) { + if (firstIndex == -1) { + firstIndex = i; + } + lastIndex = i; + } + } + int numObjects = lastIndex - firstIndex + 1; + + // grow data + err = growData(len); + if (err != NO_ERROR) { + return err; + } + + // append data + memcpy(mData + mDataPos, data + offset, len); + mDataPos += len; + mDataSize += len; + + if (numObjects > 0) { + // grow objects + if (mObjectsCapacity < mObjectsSize + numObjects) { + int newSize = ((mObjectsSize + numObjects)*3)/2; + size_t *objects = + (size_t*)realloc(mObjects, newSize*sizeof(size_t)); + if (objects == (size_t*)0) { + return NO_MEMORY; + } + mObjects = objects; + mObjectsCapacity = newSize; + } + + // append and acquire objects + int idx = mObjectsSize; + for (int i = firstIndex; i <= lastIndex; i++) { + size_t off = objects[i] - offset + startPos; + mObjects[idx++] = off; + mObjectsSize++; + + const flat_binder_object* flat + = reinterpret_cast<flat_binder_object*>(mData + off); + acquire_object(proc, *flat, this); + + // take note if the object is a file descriptor + if (flat->type == BINDER_TYPE_FD) { + mHasFds = mFdsKnown = true; + } + } + } + + return NO_ERROR; +} + +bool Parcel::hasFileDescriptors() const +{ + if (!mFdsKnown) { + scanForFds(); + } + return mHasFds; +} + +status_t Parcel::writeInterfaceToken(const String16& interface) +{ + // currently the interface identification token is just its name as a string + return writeString16(interface); +} + +bool Parcel::enforceInterface(const String16& interface) const +{ + String16 str = readString16(); + if (str == interface) { + return true; + } else { + LOGW("**** enforceInterface() expected '%s' but read '%s'\n", + String8(interface).string(), String8(str).string()); + return false; + } +} + +const size_t* Parcel::objects() const +{ + return mObjects; +} + +size_t Parcel::objectsCount() const +{ + return mObjectsSize; +} + +status_t Parcel::errorCheck() const +{ + return mError; +} + +void Parcel::setError(status_t err) +{ + mError = err; +} + +status_t Parcel::finishWrite(size_t len) +{ + //printf("Finish write of %d\n", len); + mDataPos += len; + LOGV("finishWrite Setting data pos of %p to %d\n", this, mDataPos); + if (mDataPos > mDataSize) { + mDataSize = mDataPos; + LOGV("finishWrite Setting data size of %p to %d\n", this, mDataSize); + } + //printf("New pos=%d, size=%d\n", mDataPos, mDataSize); + return NO_ERROR; +} + +status_t Parcel::writeUnpadded(const void* data, size_t len) +{ + size_t end = mDataPos + len; + if (end < mDataPos) { + // integer overflow + return BAD_VALUE; + } + + if (end <= mDataCapacity) { +restart_write: + memcpy(mData+mDataPos, data, len); + return finishWrite(len); + } + + status_t err = growData(len); + if (err == NO_ERROR) goto restart_write; + return err; +} + +status_t Parcel::write(const void* data, size_t len) +{ + void* const d = writeInplace(len); + if (d) { + memcpy(d, data, len); + return NO_ERROR; + } + return mError; +} + +void* Parcel::writeInplace(size_t len) +{ + const size_t padded = PAD_SIZE(len); + + // sanity check for integer overflow + if (mDataPos+padded < mDataPos) { + return NULL; + } + + if ((mDataPos+padded) <= mDataCapacity) { +restart_write: + //printf("Writing %ld bytes, padded to %ld\n", len, padded); + uint8_t* const data = mData+mDataPos; + + // Need to pad at end? + if (padded != len) { +#if BYTE_ORDER == BIG_ENDIAN + static const uint32_t mask[4] = { + 0x00000000, 0xffffff00, 0xffff0000, 0xff000000 + }; +#endif +#if BYTE_ORDER == LITTLE_ENDIAN + static const uint32_t mask[4] = { + 0x00000000, 0x00ffffff, 0x0000ffff, 0x000000ff + }; +#endif + //printf("Applying pad mask: %p to %p\n", (void*)mask[padded-len], + // *reinterpret_cast<void**>(data+padded-4)); + *reinterpret_cast<uint32_t*>(data+padded-4) &= mask[padded-len]; + } + + finishWrite(padded); + return data; + } + + status_t err = growData(padded); + if (err == NO_ERROR) goto restart_write; + return NULL; +} + +status_t Parcel::writeInt32(int32_t val) +{ + if ((mDataPos+sizeof(val)) <= mDataCapacity) { +restart_write: + *reinterpret_cast<int32_t*>(mData+mDataPos) = val; + return finishWrite(sizeof(val)); + } + + status_t err = growData(sizeof(val)); + if (err == NO_ERROR) goto restart_write; + return err; +} + +status_t Parcel::writeInt64(int64_t val) +{ + if ((mDataPos+sizeof(val)) <= mDataCapacity) { +restart_write: + *reinterpret_cast<int64_t*>(mData+mDataPos) = val; + return finishWrite(sizeof(val)); + } + + status_t err = growData(sizeof(val)); + if (err == NO_ERROR) goto restart_write; + return err; +} + +status_t Parcel::writeFloat(float val) +{ + if ((mDataPos+sizeof(val)) <= mDataCapacity) { +restart_write: + *reinterpret_cast<float*>(mData+mDataPos) = val; + return finishWrite(sizeof(val)); + } + + status_t err = growData(sizeof(val)); + if (err == NO_ERROR) goto restart_write; + return err; +} + +status_t Parcel::writeDouble(double val) +{ + if ((mDataPos+sizeof(val)) <= mDataCapacity) { +restart_write: + *reinterpret_cast<double*>(mData+mDataPos) = val; + return finishWrite(sizeof(val)); + } + + status_t err = growData(sizeof(val)); + if (err == NO_ERROR) goto restart_write; + return err; +} + +status_t Parcel::writeCString(const char* str) +{ + return write(str, strlen(str)+1); +} + +status_t Parcel::writeString8(const String8& str) +{ + status_t err = writeInt32(str.bytes()); + if (err == NO_ERROR) { + err = write(str.string(), str.bytes()+1); + } + return err; +} + +status_t Parcel::writeString16(const String16& str) +{ + return writeString16(str.string(), str.size()); +} + +status_t Parcel::writeString16(const char16_t* str, size_t len) +{ + if (str == NULL) return writeInt32(-1); + + status_t err = writeInt32(len); + if (err == NO_ERROR) { + len *= sizeof(char16_t); + uint8_t* data = (uint8_t*)writeInplace(len+sizeof(char16_t)); + if (data) { + memcpy(data, str, len); + *reinterpret_cast<char16_t*>(data+len) = 0; + return NO_ERROR; + } + err = mError; + } + return err; +} + +status_t Parcel::writeStrongBinder(const sp<IBinder>& val) +{ + return flatten_binder(ProcessState::self(), val, this); +} + +status_t Parcel::writeWeakBinder(const wp<IBinder>& val) +{ + return flatten_binder(ProcessState::self(), val, this); +} + +status_t Parcel::writeFileDescriptor(int fd) +{ + flat_binder_object obj; + obj.type = BINDER_TYPE_FD; + obj.flags = 0x7f | FLAT_BINDER_FLAG_ACCEPTS_FDS; + obj.handle = fd; + obj.cookie = (void*)0; + return writeObject(obj, true); +} + +status_t Parcel::writeDupFileDescriptor(int fd) +{ + flat_binder_object obj; + obj.type = BINDER_TYPE_FD; + obj.flags = 0x7f | FLAT_BINDER_FLAG_ACCEPTS_FDS; + obj.handle = dup(fd); + obj.cookie = (void*)1; + return writeObject(obj, true); +} + +status_t Parcel::writeObject(const flat_binder_object& val, bool nullMetaData) +{ + const bool enoughData = (mDataPos+sizeof(val)) <= mDataCapacity; + const bool enoughObjects = mObjectsSize < mObjectsCapacity; + if (enoughData && enoughObjects) { +restart_write: + *reinterpret_cast<flat_binder_object*>(mData+mDataPos) = val; + + // Need to write meta-data? + if (nullMetaData || val.binder != NULL) { + mObjects[mObjectsSize] = mDataPos; + acquire_object(ProcessState::self(), val, this); + mObjectsSize++; + } + + // remember if it's a file descriptor + if (val.type == BINDER_TYPE_FD) { + mHasFds = mFdsKnown = true; + } + + return finishWrite(sizeof(flat_binder_object)); + } + + if (!enoughData) { + const status_t err = growData(sizeof(val)); + if (err != NO_ERROR) return err; + } + if (!enoughObjects) { + size_t newSize = ((mObjectsSize+2)*3)/2; + size_t* objects = (size_t*)realloc(mObjects, newSize*sizeof(size_t)); + if (objects == NULL) return NO_MEMORY; + mObjects = objects; + mObjectsCapacity = newSize; + } + + goto restart_write; +} + + +void Parcel::remove(size_t start, size_t amt) +{ + LOG_ALWAYS_FATAL("Parcel::remove() not yet implemented!"); +} + +status_t Parcel::read(void* outData, size_t len) const +{ + if ((mDataPos+PAD_SIZE(len)) >= mDataPos && (mDataPos+PAD_SIZE(len)) <= mDataSize) { + memcpy(outData, mData+mDataPos, len); + mDataPos += PAD_SIZE(len); + LOGV("read Setting data pos of %p to %d\n", this, mDataPos); + return NO_ERROR; + } + return NOT_ENOUGH_DATA; +} + +const void* Parcel::readInplace(size_t len) const +{ + if ((mDataPos+PAD_SIZE(len)) >= mDataPos && (mDataPos+PAD_SIZE(len)) <= mDataSize) { + const void* data = mData+mDataPos; + mDataPos += PAD_SIZE(len); + LOGV("readInplace Setting data pos of %p to %d\n", this, mDataPos); + return data; + } + return NULL; +} + +status_t Parcel::readInt32(int32_t *pArg) const +{ + if ((mDataPos+sizeof(int32_t)) <= mDataSize) { + const void* data = mData+mDataPos; + mDataPos += sizeof(int32_t); + *pArg = *reinterpret_cast<const int32_t*>(data); + return NO_ERROR; + } else { + return NOT_ENOUGH_DATA; + } +} + +int32_t Parcel::readInt32() const +{ + if ((mDataPos+sizeof(int32_t)) <= mDataSize) { + const void* data = mData+mDataPos; + mDataPos += sizeof(int32_t); + LOGV("readInt32 Setting data pos of %p to %d\n", this, mDataPos); + return *reinterpret_cast<const int32_t*>(data); + } + return 0; +} + + +status_t Parcel::readInt64(int64_t *pArg) const +{ + if ((mDataPos+sizeof(int64_t)) <= mDataSize) { + const void* data = mData+mDataPos; + mDataPos += sizeof(int64_t); + *pArg = *reinterpret_cast<const int64_t*>(data); + LOGV("readInt64 Setting data pos of %p to %d\n", this, mDataPos); + return NO_ERROR; + } else { + return NOT_ENOUGH_DATA; + } +} + + +int64_t Parcel::readInt64() const +{ + if ((mDataPos+sizeof(int64_t)) <= mDataSize) { + const void* data = mData+mDataPos; + mDataPos += sizeof(int64_t); + LOGV("readInt64 Setting data pos of %p to %d\n", this, mDataPos); + return *reinterpret_cast<const int64_t*>(data); + } + return 0; +} + +status_t Parcel::readFloat(float *pArg) const +{ + if ((mDataPos+sizeof(float)) <= mDataSize) { + const void* data = mData+mDataPos; + mDataPos += sizeof(float); + LOGV("readFloat Setting data pos of %p to %d\n", this, mDataPos); + *pArg = *reinterpret_cast<const float*>(data); + return NO_ERROR; + } else { + return NOT_ENOUGH_DATA; + } +} + + +float Parcel::readFloat() const +{ + if ((mDataPos+sizeof(float)) <= mDataSize) { + const void* data = mData+mDataPos; + mDataPos += sizeof(float); + LOGV("readFloat Setting data pos of %p to %d\n", this, mDataPos); + return *reinterpret_cast<const float*>(data); + } + return 0; +} + +status_t Parcel::readDouble(double *pArg) const +{ + if ((mDataPos+sizeof(double)) <= mDataSize) { + const void* data = mData+mDataPos; + mDataPos += sizeof(double); + LOGV("readDouble Setting data pos of %p to %d\n", this, mDataPos); + *pArg = *reinterpret_cast<const double*>(data); + return NO_ERROR; + } else { + return NOT_ENOUGH_DATA; + } +} + + +double Parcel::readDouble() const +{ + if ((mDataPos+sizeof(double)) <= mDataSize) { + const void* data = mData+mDataPos; + mDataPos += sizeof(double); + LOGV("readDouble Setting data pos of %p to %d\n", this, mDataPos); + return *reinterpret_cast<const double*>(data); + } + return 0; +} + + +const char* Parcel::readCString() const +{ + const size_t avail = mDataSize-mDataPos; + if (avail > 0) { + const char* str = reinterpret_cast<const char*>(mData+mDataPos); + // is the string's trailing NUL within the parcel's valid bounds? + const char* eos = reinterpret_cast<const char*>(memchr(str, 0, avail)); + if (eos) { + const size_t len = eos - str; + mDataPos += PAD_SIZE(len+1); + LOGV("readCString Setting data pos of %p to %d\n", this, mDataPos); + return str; + } + } + return NULL; +} + +String8 Parcel::readString8() const +{ + int32_t size = readInt32(); + // watch for potential int overflow adding 1 for trailing NUL + if (size > 0 && size < INT32_MAX) { + const char* str = (const char*)readInplace(size+1); + if (str) return String8(str, size); + } + return String8(); +} + +String16 Parcel::readString16() const +{ + size_t len; + const char16_t* str = readString16Inplace(&len); + if (str) return String16(str, len); + LOGE("Reading a NULL string not supported here."); + return String16(); +} + +const char16_t* Parcel::readString16Inplace(size_t* outLen) const +{ + int32_t size = readInt32(); + // watch for potential int overflow from size+1 + if (size >= 0 && size < INT32_MAX) { + *outLen = size; + const char16_t* str = (const char16_t*)readInplace((size+1)*sizeof(char16_t)); + if (str != NULL) { + return str; + } + } + *outLen = 0; + return NULL; +} + +sp<IBinder> Parcel::readStrongBinder() const +{ + sp<IBinder> val; + unflatten_binder(ProcessState::self(), *this, &val); + return val; +} + +wp<IBinder> Parcel::readWeakBinder() const +{ + wp<IBinder> val; + unflatten_binder(ProcessState::self(), *this, &val); + return val; +} + +int Parcel::readFileDescriptor() const +{ + const flat_binder_object* flat = readObject(true); + if (flat) { + switch (flat->type) { + case BINDER_TYPE_FD: + //LOGI("Returning file descriptor %ld from parcel %p\n", flat->handle, this); + return flat->handle; + } + } + return BAD_TYPE; +} + +const flat_binder_object* Parcel::readObject(bool nullMetaData) const +{ + const size_t DPOS = mDataPos; + if ((DPOS+sizeof(flat_binder_object)) <= mDataSize) { + const flat_binder_object* obj + = reinterpret_cast<const flat_binder_object*>(mData+DPOS); + mDataPos = DPOS + sizeof(flat_binder_object); + if (!nullMetaData && (obj->cookie == NULL && obj->binder == NULL)) { + // When transfering a NULL object, we don't write it into + // the object list, so we don't want to check for it when + // reading. + LOGV("readObject Setting data pos of %p to %d\n", this, mDataPos); + return obj; + } + + // Ensure that this object is valid... + size_t* const OBJS = mObjects; + const size_t N = mObjectsSize; + size_t opos = mNextObjectHint; + + if (N > 0) { + LOGV("Parcel %p looking for obj at %d, hint=%d\n", + this, DPOS, opos); + + // Start at the current hint position, looking for an object at + // the current data position. + if (opos < N) { + while (opos < (N-1) && OBJS[opos] < DPOS) { + opos++; + } + } else { + opos = N-1; + } + if (OBJS[opos] == DPOS) { + // Found it! + LOGV("Parcel found obj %d at index %d with forward search", + this, DPOS, opos); + mNextObjectHint = opos+1; + LOGV("readObject Setting data pos of %p to %d\n", this, mDataPos); + return obj; + } + + // Look backwards for it... + while (opos > 0 && OBJS[opos] > DPOS) { + opos--; + } + if (OBJS[opos] == DPOS) { + // Found it! + LOGV("Parcel found obj %d at index %d with backward search", + this, DPOS, opos); + mNextObjectHint = opos+1; + LOGV("readObject Setting data pos of %p to %d\n", this, mDataPos); + return obj; + } + } + LOGW("Attempt to read object from Parcel %p at offset %d that is not in the object list", + this, DPOS); + } + return NULL; +} + +void Parcel::closeFileDescriptors() +{ + size_t i = mObjectsSize; + if (i > 0) { + //LOGI("Closing file descriptors for %d objects...", mObjectsSize); + } + while (i > 0) { + i--; + const flat_binder_object* flat + = reinterpret_cast<flat_binder_object*>(mData+mObjects[i]); + if (flat->type == BINDER_TYPE_FD) { + //LOGI("Closing fd: %ld\n", flat->handle); + close(flat->handle); + } + } +} + +const uint8_t* Parcel::ipcData() const +{ + return mData; +} + +size_t Parcel::ipcDataSize() const +{ + return (mDataSize > mDataPos ? mDataSize : mDataPos); +} + +const size_t* Parcel::ipcObjects() const +{ + return mObjects; +} + +size_t Parcel::ipcObjectsCount() const +{ + return mObjectsSize; +} + +void Parcel::ipcSetDataReference(const uint8_t* data, size_t dataSize, + const size_t* objects, size_t objectsCount, release_func relFunc, void* relCookie) +{ + freeDataNoInit(); + mError = NO_ERROR; + mData = const_cast<uint8_t*>(data); + mDataSize = mDataCapacity = dataSize; + //LOGI("setDataReference Setting data size of %p to %lu (pid=%d)\n", this, mDataSize, getpid()); + mDataPos = 0; + LOGV("setDataReference Setting data pos of %p to %d\n", this, mDataPos); + mObjects = const_cast<size_t*>(objects); + mObjectsSize = mObjectsCapacity = objectsCount; + mNextObjectHint = 0; + mOwner = relFunc; + mOwnerCookie = relCookie; + scanForFds(); +} + +void Parcel::print(TextOutput& to, uint32_t flags) const +{ + to << "Parcel("; + + if (errorCheck() != NO_ERROR) { + const status_t err = errorCheck(); + to << "Error: " << (void*)err << " \"" << strerror(-err) << "\""; + } else if (dataSize() > 0) { + const uint8_t* DATA = data(); + to << indent << HexDump(DATA, dataSize()) << dedent; + const size_t* OBJS = objects(); + const size_t N = objectsCount(); + for (size_t i=0; i<N; i++) { + const flat_binder_object* flat + = reinterpret_cast<const flat_binder_object*>(DATA+OBJS[i]); + to << endl << "Object #" << i << " @ " << (void*)OBJS[i] << ": " + << TypeCode(flat->type & 0x7f7f7f00) + << " = " << flat->binder; + } + } else { + to << "NULL"; + } + + to << ")"; +} + +void Parcel::releaseObjects() +{ + const sp<ProcessState> proc(ProcessState::self()); + size_t i = mObjectsSize; + uint8_t* const data = mData; + size_t* const objects = mObjects; + while (i > 0) { + i--; + const flat_binder_object* flat + = reinterpret_cast<flat_binder_object*>(data+objects[i]); + release_object(proc, *flat, this); + } +} + +void Parcel::acquireObjects() +{ + const sp<ProcessState> proc(ProcessState::self()); + size_t i = mObjectsSize; + uint8_t* const data = mData; + size_t* const objects = mObjects; + while (i > 0) { + i--; + const flat_binder_object* flat + = reinterpret_cast<flat_binder_object*>(data+objects[i]); + acquire_object(proc, *flat, this); + } +} + +void Parcel::freeData() +{ + freeDataNoInit(); + initState(); +} + +void Parcel::freeDataNoInit() +{ + if (mOwner) { + //LOGI("Freeing data ref of %p (pid=%d)\n", this, getpid()); + mOwner(this, mData, mDataSize, mObjects, mObjectsSize, mOwnerCookie); + } else { + releaseObjects(); + if (mData) free(mData); + if (mObjects) free(mObjects); + } +} + +status_t Parcel::growData(size_t len) +{ + size_t newSize = ((mDataSize+len)*3)/2; + return (newSize <= mDataSize) + ? (status_t) NO_MEMORY + : continueWrite(newSize); +} + +status_t Parcel::restartWrite(size_t desired) +{ + if (mOwner) { + freeData(); + return continueWrite(desired); + } + + uint8_t* data = (uint8_t*)realloc(mData, desired); + if (!data && desired > mDataCapacity) { + mError = NO_MEMORY; + return NO_MEMORY; + } + + releaseObjects(); + + if (data) { + mData = data; + mDataCapacity = desired; + } + + mDataSize = mDataPos = 0; + LOGV("restartWrite Setting data size of %p to %d\n", this, mDataSize); + LOGV("restartWrite Setting data pos of %p to %d\n", this, mDataPos); + + free(mObjects); + mObjects = NULL; + mObjectsSize = mObjectsCapacity = 0; + mNextObjectHint = 0; + mHasFds = false; + mFdsKnown = true; + + return NO_ERROR; +} + +status_t Parcel::continueWrite(size_t desired) +{ + // If shrinking, first adjust for any objects that appear + // after the new data size. + size_t objectsSize = mObjectsSize; + if (desired < mDataSize) { + if (desired == 0) { + objectsSize = 0; + } else { + while (objectsSize > 0) { + if (mObjects[objectsSize-1] < desired) + break; + objectsSize--; + } + } + } + + if (mOwner) { + // If the size is going to zero, just release the owner's data. + if (desired == 0) { + freeData(); + return NO_ERROR; + } + + // If there is a different owner, we need to take + // posession. + uint8_t* data = (uint8_t*)malloc(desired); + if (!data) { + mError = NO_MEMORY; + return NO_MEMORY; + } + size_t* objects = NULL; + + if (objectsSize) { + objects = (size_t*)malloc(objectsSize*sizeof(size_t)); + if (!objects) { + mError = NO_MEMORY; + return NO_MEMORY; + } + + // Little hack to only acquire references on objects + // we will be keeping. + size_t oldObjectsSize = mObjectsSize; + mObjectsSize = objectsSize; + acquireObjects(); + mObjectsSize = oldObjectsSize; + } + + if (mData) { + memcpy(data, mData, mDataSize < desired ? mDataSize : desired); + } + if (objects && mObjects) { + memcpy(objects, mObjects, objectsSize*sizeof(size_t)); + } + //LOGI("Freeing data ref of %p (pid=%d)\n", this, getpid()); + mOwner(this, mData, mDataSize, mObjects, mObjectsSize, mOwnerCookie); + mOwner = NULL; + + mData = data; + mObjects = objects; + mDataSize = (mDataSize < desired) ? mDataSize : desired; + LOGV("continueWrite Setting data size of %p to %d\n", this, mDataSize); + mDataCapacity = desired; + mObjectsSize = mObjectsCapacity = objectsSize; + mNextObjectHint = 0; + + } else if (mData) { + if (objectsSize < mObjectsSize) { + // Need to release refs on any objects we are dropping. + const sp<ProcessState> proc(ProcessState::self()); + for (size_t i=objectsSize; i<mObjectsSize; i++) { + const flat_binder_object* flat + = reinterpret_cast<flat_binder_object*>(mData+mObjects[i]); + if (flat->type == BINDER_TYPE_FD) { + // will need to rescan because we may have lopped off the only FDs + mFdsKnown = false; + } + release_object(proc, *flat, this); + } + size_t* objects = + (size_t*)realloc(mObjects, objectsSize*sizeof(size_t)); + if (objects) { + mObjects = objects; + } + mObjectsSize = objectsSize; + mNextObjectHint = 0; + } + + // We own the data, so we can just do a realloc(). + if (desired > mDataCapacity) { + uint8_t* data = (uint8_t*)realloc(mData, desired); + if (data) { + mData = data; + mDataCapacity = desired; + } else if (desired > mDataCapacity) { + mError = NO_MEMORY; + return NO_MEMORY; + } + } else { + mDataSize = desired; + LOGV("continueWrite Setting data size of %p to %d\n", this, mDataSize); + if (mDataPos > desired) { + mDataPos = desired; + LOGV("continueWrite Setting data pos of %p to %d\n", this, mDataPos); + } + } + + } else { + // This is the first data. Easy! + uint8_t* data = (uint8_t*)malloc(desired); + if (!data) { + mError = NO_MEMORY; + return NO_MEMORY; + } + + if(!(mDataCapacity == 0 && mObjects == NULL + && mObjectsCapacity == 0)) { + LOGE("continueWrite: %d/%p/%d/%d", mDataCapacity, mObjects, mObjectsCapacity, desired); + } + + mData = data; + mDataSize = mDataPos = 0; + LOGV("continueWrite Setting data size of %p to %d\n", this, mDataSize); + LOGV("continueWrite Setting data pos of %p to %d\n", this, mDataPos); + mDataCapacity = desired; + } + + return NO_ERROR; +} + +void Parcel::initState() +{ + mError = NO_ERROR; + mData = 0; + mDataSize = 0; + mDataCapacity = 0; + mDataPos = 0; + LOGV("initState Setting data size of %p to %d\n", this, mDataSize); + LOGV("initState Setting data pos of %p to %d\n", this, mDataPos); + mObjects = NULL; + mObjectsSize = 0; + mObjectsCapacity = 0; + mNextObjectHint = 0; + mHasFds = false; + mFdsKnown = true; + mOwner = NULL; +} + +void Parcel::scanForFds() const +{ + bool hasFds = false; + for (size_t i=0; i<mObjectsSize; i++) { + const flat_binder_object* flat + = reinterpret_cast<const flat_binder_object*>(mData + mObjects[i]); + if (flat->type == BINDER_TYPE_FD) { + hasFds = true; + break; + } + } + mHasFds = hasFds; + mFdsKnown = true; +} + +}; // namespace android diff --git a/libs/utils/Pipe.cpp b/libs/utils/Pipe.cpp new file mode 100644 index 0000000..613906b --- /dev/null +++ b/libs/utils/Pipe.cpp @@ -0,0 +1,465 @@ +/* + * Copyright (C) 2005 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. + */ + +// +// Unidirectional pipe. +// + +#include <utils/Pipe.h> +#include <utils/Log.h> + +#if defined(HAVE_WIN32_IPC) +# include <windows.h> +#else +# include <fcntl.h> +# include <unistd.h> +# include <errno.h> +#endif + +#include <stdlib.h> +#include <stdio.h> +#include <assert.h> +#include <string.h> + +using namespace android; + +const unsigned long kInvalidHandle = (unsigned long) -1; + + +/* + * Constructor. Do little. + */ +Pipe::Pipe(void) + : mReadNonBlocking(false), mReadHandle(kInvalidHandle), + mWriteHandle(kInvalidHandle) +{ +} + +/* + * Destructor. Use the system-appropriate close call. + */ +Pipe::~Pipe(void) +{ +#if defined(HAVE_WIN32_IPC) + if (mReadHandle != kInvalidHandle) { + if (!CloseHandle((HANDLE)mReadHandle)) + LOG(LOG_WARN, "pipe", "failed closing read handle (%ld)\n", + mReadHandle); + } + if (mWriteHandle != kInvalidHandle) { + FlushFileBuffers((HANDLE)mWriteHandle); + if (!CloseHandle((HANDLE)mWriteHandle)) + LOG(LOG_WARN, "pipe", "failed closing write handle (%ld)\n", + mWriteHandle); + } +#else + if (mReadHandle != kInvalidHandle) { + if (close((int) mReadHandle) != 0) + LOG(LOG_WARN, "pipe", "failed closing read fd (%d)\n", + (int) mReadHandle); + } + if (mWriteHandle != kInvalidHandle) { + if (close((int) mWriteHandle) != 0) + LOG(LOG_WARN, "pipe", "failed closing write fd (%d)\n", + (int) mWriteHandle); + } +#endif +} + +/* + * Create the pipe. + * + * Use the POSIX stuff for everything but Windows. + */ +bool Pipe::create(void) +{ + assert(mReadHandle == kInvalidHandle); + assert(mWriteHandle == kInvalidHandle); + +#if defined(HAVE_WIN32_IPC) + /* we use this across processes, so they need to be inheritable */ + HANDLE handles[2]; + SECURITY_ATTRIBUTES saAttr; + + saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); + saAttr.bInheritHandle = TRUE; + saAttr.lpSecurityDescriptor = NULL; + + if (!CreatePipe(&handles[0], &handles[1], &saAttr, 0)) { + LOG(LOG_ERROR, "pipe", "unable to create pipe\n"); + return false; + } + mReadHandle = (unsigned long) handles[0]; + mWriteHandle = (unsigned long) handles[1]; + return true; +#else + int fds[2]; + + if (pipe(fds) != 0) { + LOG(LOG_ERROR, "pipe", "unable to create pipe\n"); + return false; + } + mReadHandle = fds[0]; + mWriteHandle = fds[1]; + return true; +#endif +} + +/* + * Create a "half pipe". Please, no Segway riding. + */ +bool Pipe::createReader(unsigned long handle) +{ + mReadHandle = handle; + assert(mWriteHandle == kInvalidHandle); + return true; +} + +/* + * Create a "half pipe" for writing. + */ +bool Pipe::createWriter(unsigned long handle) +{ + mWriteHandle = handle; + assert(mReadHandle == kInvalidHandle); + return true; +} + +/* + * Return "true" if create() has been called successfully. + */ +bool Pipe::isCreated(void) +{ + // one or the other should be open + return (mReadHandle != kInvalidHandle || mWriteHandle != kInvalidHandle); +} + + +/* + * Read data from the pipe. + * + * For Linux and Darwin, just call read(). For Windows, implement + * non-blocking reads by calling PeekNamedPipe first. + */ +int Pipe::read(void* buf, int count) +{ + assert(mReadHandle != kInvalidHandle); + +#if defined(HAVE_WIN32_IPC) + DWORD totalBytesAvail = count; + DWORD bytesRead; + + if (mReadNonBlocking) { + // use PeekNamedPipe to adjust read count expectations + if (!PeekNamedPipe((HANDLE) mReadHandle, NULL, 0, NULL, + &totalBytesAvail, NULL)) + { + LOG(LOG_ERROR, "pipe", "PeekNamedPipe failed\n"); + return -1; + } + + if (totalBytesAvail == 0) + return 0; + } + + if (!ReadFile((HANDLE) mReadHandle, buf, totalBytesAvail, &bytesRead, + NULL)) + { + DWORD err = GetLastError(); + if (err == ERROR_HANDLE_EOF || err == ERROR_BROKEN_PIPE) + return 0; + LOG(LOG_ERROR, "pipe", "ReadFile failed (err=%ld)\n", err); + return -1; + } + + return (int) bytesRead; +#else + int cc; + cc = ::read(mReadHandle, buf, count); + if (cc < 0 && errno == EAGAIN) + return 0; + return cc; +#endif +} + +/* + * Write data to the pipe. + * + * POSIX systems are trivial, Windows uses a different call and doesn't + * handle non-blocking writes. + * + * If we add non-blocking support here, we probably want to make it an + * all-or-nothing write. + * + * DO NOT use LOG() here, we could be writing a log message. + */ +int Pipe::write(const void* buf, int count) +{ + assert(mWriteHandle != kInvalidHandle); + +#if defined(HAVE_WIN32_IPC) + DWORD bytesWritten; + + if (mWriteNonBlocking) { + // BUG: can't use PeekNamedPipe() to get the amount of space + // left. Looks like we need to use "overlapped I/O" functions. + // I just don't care that much. + } + + if (!WriteFile((HANDLE) mWriteHandle, buf, count, &bytesWritten, NULL)) { + // can't LOG, use stderr + fprintf(stderr, "WriteFile failed (err=%ld)\n", GetLastError()); + return -1; + } + + return (int) bytesWritten; +#else + int cc; + cc = ::write(mWriteHandle, buf, count); + if (cc < 0 && errno == EAGAIN) + return 0; + return cc; +#endif +} + +/* + * Figure out if there is data available on the read fd. + * + * We return "true" on error because we want the caller to try to read + * from the pipe. They'll notice the read failure and do something + * appropriate. + */ +bool Pipe::readReady(void) +{ + assert(mReadHandle != kInvalidHandle); + +#if defined(HAVE_WIN32_IPC) + DWORD totalBytesAvail; + + if (!PeekNamedPipe((HANDLE) mReadHandle, NULL, 0, NULL, + &totalBytesAvail, NULL)) + { + LOG(LOG_ERROR, "pipe", "PeekNamedPipe failed\n"); + return true; + } + + return (totalBytesAvail != 0); +#else + errno = 0; + fd_set readfds; + struct timeval tv = { 0, 0 }; + int cc; + + FD_ZERO(&readfds); + FD_SET(mReadHandle, &readfds); + + cc = select(mReadHandle+1, &readfds, NULL, NULL, &tv); + if (cc < 0) { + LOG(LOG_ERROR, "pipe", "select() failed\n"); + return true; + } else if (cc == 0) { + /* timed out, nothing available */ + return false; + } else if (cc == 1) { + /* our fd is ready */ + return true; + } else { + LOG(LOG_ERROR, "pipe", "HUH? select() returned > 1\n"); + return true; + } +#endif +} + +/* + * Enable or disable non-blocking mode for the read descriptor. + * + * NOTE: the calls succeed under Mac OS X, but the pipe doesn't appear to + * actually be in non-blocking mode. If this matters -- i.e. you're not + * using a select() call -- put a call to readReady() in front of the + * ::read() call, with a PIPE_NONBLOCK_BROKEN #ifdef in the Makefile for + * Darwin. + */ +bool Pipe::setReadNonBlocking(bool val) +{ + assert(mReadHandle != kInvalidHandle); + +#if defined(HAVE_WIN32_IPC) + // nothing to do +#else + int flags; + + if (fcntl(mReadHandle, F_GETFL, &flags) == -1) { + LOG(LOG_ERROR, "pipe", "couldn't get flags for pipe read fd\n"); + return false; + } + if (val) + flags |= O_NONBLOCK; + else + flags &= ~(O_NONBLOCK); + if (fcntl(mReadHandle, F_SETFL, &flags) == -1) { + LOG(LOG_ERROR, "pipe", "couldn't set flags for pipe read fd\n"); + return false; + } +#endif + + mReadNonBlocking = val; + return true; +} + +/* + * Enable or disable non-blocking mode for the write descriptor. + * + * As with setReadNonBlocking(), this does not work on the Mac. + */ +bool Pipe::setWriteNonBlocking(bool val) +{ + assert(mWriteHandle != kInvalidHandle); + +#if defined(HAVE_WIN32_IPC) + // nothing to do +#else + int flags; + + if (fcntl(mWriteHandle, F_GETFL, &flags) == -1) { + LOG(LOG_WARN, "pipe", + "Warning: couldn't get flags for pipe write fd (errno=%d)\n", + errno); + return false; + } + if (val) + flags |= O_NONBLOCK; + else + flags &= ~(O_NONBLOCK); + if (fcntl(mWriteHandle, F_SETFL, &flags) == -1) { + LOG(LOG_WARN, "pipe", + "Warning: couldn't set flags for pipe write fd (errno=%d)\n", + errno); + return false; + } +#endif + + mWriteNonBlocking = val; + return true; +} + +/* + * Specify whether a file descriptor can be inherited by a child process. + * Under Linux this means setting the close-on-exec flag, under Windows + * this is SetHandleInformation(HANDLE_FLAG_INHERIT). + */ +bool Pipe::disallowReadInherit(void) +{ + if (mReadHandle == kInvalidHandle) + return false; + +#if defined(HAVE_WIN32_IPC) + if (SetHandleInformation((HANDLE) mReadHandle, HANDLE_FLAG_INHERIT, 0) == 0) + return false; +#else + if (fcntl((int) mReadHandle, F_SETFD, FD_CLOEXEC) != 0) + return false; +#endif + return true; +} +bool Pipe::disallowWriteInherit(void) +{ + if (mWriteHandle == kInvalidHandle) + return false; + +#if defined(HAVE_WIN32_IPC) + if (SetHandleInformation((HANDLE) mWriteHandle, HANDLE_FLAG_INHERIT, 0) == 0) + return false; +#else + if (fcntl((int) mWriteHandle, F_SETFD, FD_CLOEXEC) != 0) + return false; +#endif + return true; +} + +/* + * Close read descriptor. + */ +bool Pipe::closeRead(void) +{ + if (mReadHandle == kInvalidHandle) + return false; + +#if defined(HAVE_WIN32_IPC) + if (mReadHandle != kInvalidHandle) { + if (!CloseHandle((HANDLE)mReadHandle)) { + LOG(LOG_WARN, "pipe", "failed closing read handle\n"); + return false; + } + } +#else + if (mReadHandle != kInvalidHandle) { + if (close((int) mReadHandle) != 0) { + LOG(LOG_WARN, "pipe", "failed closing read fd\n"); + return false; + } + } +#endif + mReadHandle = kInvalidHandle; + return true; +} + +/* + * Close write descriptor. + */ +bool Pipe::closeWrite(void) +{ + if (mWriteHandle == kInvalidHandle) + return false; + +#if defined(HAVE_WIN32_IPC) + if (mWriteHandle != kInvalidHandle) { + if (!CloseHandle((HANDLE)mWriteHandle)) { + LOG(LOG_WARN, "pipe", "failed closing write handle\n"); + return false; + } + } +#else + if (mWriteHandle != kInvalidHandle) { + if (close((int) mWriteHandle) != 0) { + LOG(LOG_WARN, "pipe", "failed closing write fd\n"); + return false; + } + } +#endif + mWriteHandle = kInvalidHandle; + return true; +} + +/* + * Get the read handle. + */ +unsigned long Pipe::getReadHandle(void) +{ + assert(mReadHandle != kInvalidHandle); + + return mReadHandle; +} + +/* + * Get the write handle. + */ +unsigned long Pipe::getWriteHandle(void) +{ + assert(mWriteHandle != kInvalidHandle); + + return mWriteHandle; +} + diff --git a/libs/utils/ProcessState.cpp b/libs/utils/ProcessState.cpp new file mode 100644 index 0000000..4567df6 --- /dev/null +++ b/libs/utils/ProcessState.cpp @@ -0,0 +1,398 @@ +/* + * Copyright (C) 2005 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. + */ + +#define LOG_TAG "ProcessState" + +#include <cutils/process_name.h> + +#include <utils/ProcessState.h> + +#include <utils/Atomic.h> +#include <utils/BpBinder.h> +#include <utils/IPCThreadState.h> +#include <utils/Log.h> +#include <utils/String8.h> +#include <utils/IServiceManager.h> +#include <utils/String8.h> +#include <utils/threads.h> + +#include <private/utils/binder_module.h> +#include <private/utils/Static.h> + +#include <errno.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <sys/ioctl.h> +#include <sys/mman.h> +#include <sys/stat.h> + +#define BINDER_VM_SIZE (1*1024*1024) + +static bool gSingleProcess = false; + + +// --------------------------------------------------------------------------- + +namespace android { + +// Global variables +int mArgC; +const char* const* mArgV; +int mArgLen; + +class PoolThread : public Thread +{ +public: + PoolThread(bool isMain) + : mIsMain(isMain) + { + } + +protected: + virtual bool threadLoop() + { + IPCThreadState::self()->joinThreadPool(mIsMain); + return false; + } + + const bool mIsMain; +}; + +sp<ProcessState> ProcessState::self() +{ + if (gProcess != NULL) return gProcess; + + AutoMutex _l(gProcessMutex); + if (gProcess == NULL) gProcess = new ProcessState; + return gProcess; +} + +void ProcessState::setSingleProcess(bool singleProcess) +{ + gSingleProcess = singleProcess; +} + + +void ProcessState::setContextObject(const sp<IBinder>& object) +{ + setContextObject(object, String16("default")); +} + +sp<IBinder> ProcessState::getContextObject(const sp<IBinder>& caller) +{ + if (supportsProcesses()) { + return getStrongProxyForHandle(0); + } else { + return getContextObject(String16("default"), caller); + } +} + +void ProcessState::setContextObject(const sp<IBinder>& object, const String16& name) +{ + AutoMutex _l(mLock); + mContexts.add(name, object); +} + +sp<IBinder> ProcessState::getContextObject(const String16& name, const sp<IBinder>& caller) +{ + mLock.lock(); + sp<IBinder> object( + mContexts.indexOfKey(name) >= 0 ? mContexts.valueFor(name) : NULL); + mLock.unlock(); + + //printf("Getting context object %s for %p\n", String8(name).string(), caller.get()); + + if (object != NULL) return object; + + // Don't attempt to retrieve contexts if we manage them + if (mManagesContexts) { + LOGE("getContextObject(%s) failed, but we manage the contexts!\n", + String8(name).string()); + return NULL; + } + + IPCThreadState* ipc = IPCThreadState::self(); + { + Parcel data, reply; + // no interface token on this magic transaction + data.writeString16(name); + data.writeStrongBinder(caller); + status_t result = ipc->transact(0 /*magic*/, 0, data, &reply, 0); + if (result == NO_ERROR) { + object = reply.readStrongBinder(); + } + } + + ipc->flushCommands(); + + if (object != NULL) setContextObject(object, name); + return object; +} + +bool ProcessState::supportsProcesses() const +{ + return mDriverFD >= 0; +} + +void ProcessState::startThreadPool() +{ + AutoMutex _l(mLock); + if (!mThreadPoolStarted) { + mThreadPoolStarted = true; + spawnPooledThread(true); + } +} + +bool ProcessState::isContextManager(void) const +{ + return mManagesContexts; +} + +bool ProcessState::becomeContextManager(context_check_func checkFunc, void* userData) +{ + if (!mManagesContexts) { + AutoMutex _l(mLock); + mBinderContextCheckFunc = checkFunc; + mBinderContextUserData = userData; + if (mDriverFD >= 0) { + int dummy = 0; +#if defined(HAVE_ANDROID_OS) + status_t result = ioctl(mDriverFD, BINDER_SET_CONTEXT_MGR, &dummy); +#else + status_t result = INVALID_OPERATION; +#endif + if (result == 0) { + mManagesContexts = true; + } else if (result == -1) { + mBinderContextCheckFunc = NULL; + mBinderContextUserData = NULL; + LOGE("Binder ioctl to become context manager failed: %s\n", strerror(errno)); + } + } else { + // If there is no driver, our only world is the local + // process so we can always become the context manager there. + mManagesContexts = true; + } + } + return mManagesContexts; +} + +ProcessState::handle_entry* ProcessState::lookupHandleLocked(int32_t handle) +{ + const size_t N=mHandleToObject.size(); + if (N <= (size_t)handle) { + handle_entry e; + e.binder = NULL; + e.refs = NULL; + status_t err = mHandleToObject.insertAt(e, N, handle+1-N); + if (err < NO_ERROR) return NULL; + } + return &mHandleToObject.editItemAt(handle); +} + +sp<IBinder> ProcessState::getStrongProxyForHandle(int32_t handle) +{ + sp<IBinder> result; + + AutoMutex _l(mLock); + + handle_entry* e = lookupHandleLocked(handle); + + if (e != NULL) { + // We need to create a new BpBinder if there isn't currently one, OR we + // are unable to acquire a weak reference on this current one. See comment + // in getWeakProxyForHandle() for more info about this. + IBinder* b = e->binder; + if (b == NULL || !e->refs->attemptIncWeak(this)) { + b = new BpBinder(handle); + e->binder = b; + if (b) e->refs = b->getWeakRefs(); + result = b; + } else { + // This little bit of nastyness is to allow us to add a primary + // reference to the remote proxy when this team doesn't have one + // but another team is sending the handle to us. + result.force_set(b); + e->refs->decWeak(this); + } + } + + return result; +} + +wp<IBinder> ProcessState::getWeakProxyForHandle(int32_t handle) +{ + wp<IBinder> result; + + AutoMutex _l(mLock); + + handle_entry* e = lookupHandleLocked(handle); + + if (e != NULL) { + // We need to create a new BpBinder if there isn't currently one, OR we + // are unable to acquire a weak reference on this current one. The + // attemptIncWeak() is safe because we know the BpBinder destructor will always + // call expungeHandle(), which acquires the same lock we are holding now. + // We need to do this because there is a race condition between someone + // releasing a reference on this BpBinder, and a new reference on its handle + // arriving from the driver. + IBinder* b = e->binder; + if (b == NULL || !e->refs->attemptIncWeak(this)) { + b = new BpBinder(handle); + result = b; + e->binder = b; + if (b) e->refs = b->getWeakRefs(); + } else { + result = b; + e->refs->decWeak(this); + } + } + + return result; +} + +void ProcessState::expungeHandle(int32_t handle, IBinder* binder) +{ + AutoMutex _l(mLock); + + handle_entry* e = lookupHandleLocked(handle); + + // This handle may have already been replaced with a new BpBinder + // (if someone failed the AttemptIncWeak() above); we don't want + // to overwrite it. + if (e && e->binder == binder) e->binder = NULL; +} + +void ProcessState::setArgs(int argc, const char* const argv[]) +{ + mArgC = argc; + mArgV = (const char **)argv; + + mArgLen = 0; + for (int i=0; i<argc; i++) { + mArgLen += strlen(argv[i]) + 1; + } + mArgLen--; +} + +int ProcessState::getArgC() const +{ + return mArgC; +} + +const char* const* ProcessState::getArgV() const +{ + return mArgV; +} + +void ProcessState::setArgV0(const char* txt) +{ + if (mArgV != NULL) { + strncpy((char*)mArgV[0], txt, mArgLen); + set_process_name(txt); + } +} + +void ProcessState::spawnPooledThread(bool isMain) +{ + if (mThreadPoolStarted) { + int32_t s = android_atomic_add(1, &mThreadPoolSeq); + char buf[32]; + sprintf(buf, "Binder Thread #%d", s); + LOGV("Spawning new pooled thread, name=%s\n", buf); + sp<Thread> t = new PoolThread(isMain); + t->run(buf); + } +} + +static int open_driver() +{ + if (gSingleProcess) { + return -1; + } + + int fd = open("/dev/binder", O_RDWR); + if (fd >= 0) { + fcntl(fd, F_SETFD, FD_CLOEXEC); + int vers; +#if defined(HAVE_ANDROID_OS) + status_t result = ioctl(fd, BINDER_VERSION, &vers); +#else + status_t result = -1; + errno = EPERM; +#endif + if (result == -1) { + LOGE("Binder ioctl to obtain version failed: %s", strerror(errno)); + close(fd); + fd = -1; + } + if (result != 0 || vers != BINDER_CURRENT_PROTOCOL_VERSION) { + LOGE("Binder driver protocol does not match user space protocol!"); + close(fd); + fd = -1; + } +#if defined(HAVE_ANDROID_OS) + size_t maxThreads = 15; + result = ioctl(fd, BINDER_SET_MAX_THREADS, &maxThreads); + if (result == -1) { + LOGE("Binder ioctl to set max threads failed: %s", strerror(errno)); + } +#endif + + } else { + LOGW("Opening '/dev/binder' failed: %s\n", strerror(errno)); + } + return fd; +} + +ProcessState::ProcessState() + : mDriverFD(open_driver()) + , mVMStart(MAP_FAILED) + , mManagesContexts(false) + , mBinderContextCheckFunc(NULL) + , mBinderContextUserData(NULL) + , mThreadPoolStarted(false) + , mThreadPoolSeq(1) +{ + if (mDriverFD >= 0) { + // XXX Ideally, there should be a specific define for whether we + // have mmap (or whether we could possibly have the kernel module + // availabla). +#if !defined(HAVE_WIN32_IPC) + // mmap the binder, providing a chunk of virtual address space to receive transactions. + mVMStart = mmap(0, BINDER_VM_SIZE, PROT_READ, MAP_PRIVATE | MAP_NORESERVE, mDriverFD, 0); + if (mVMStart == MAP_FAILED) { + // *sigh* + LOGE("Using /dev/binder failed: unable to mmap transaction memory.\n"); + close(mDriverFD); + mDriverFD = -1; + } +#else + mDriverFD = -1; +#endif + } + if (mDriverFD < 0) { + // Need to run without the driver, starting our own thread pool. + } +} + +ProcessState::~ProcessState() +{ +} + +}; // namespace android diff --git a/libs/utils/README b/libs/utils/README new file mode 100644 index 0000000..36a706d --- /dev/null +++ b/libs/utils/README @@ -0,0 +1,14 @@ +Android Utility Function Library + +If you need a feature that is native to Linux but not present on other +platforms, construct a platform-dependent implementation that shares +the Linux interface. That way the actual device runs as "light" as +possible. + +If that isn't feasible, create a system-independent interface and hide +the details. + +The ultimate goal is *not* to create a super-duper platform abstraction +layer. The goal is to provide an optimized solution for Linux with +reasonable implementations for other platforms. + diff --git a/libs/utils/RefBase.cpp b/libs/utils/RefBase.cpp new file mode 100644 index 0000000..0bd1af4 --- /dev/null +++ b/libs/utils/RefBase.cpp @@ -0,0 +1,534 @@ +/* + * Copyright (C) 2005 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. + */ + +#define LOG_TAG "RefBase" + +#include <utils/RefBase.h> + +#include <utils/Atomic.h> +#include <utils/CallStack.h> +#include <utils/KeyedVector.h> +#include <utils/Log.h> +#include <utils/threads.h> + +#include <stdlib.h> +#include <stdio.h> +#include <typeinfo> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <unistd.h> + +// compile with refcounting debugging enabled +#define DEBUG_REFS 0 +#define DEBUG_REFS_ENABLED_BY_DEFAULT 1 +#define DEBUG_REFS_CALLSTACK_ENABLED 1 + +// log all reference counting operations +#define PRINT_REFS 0 + +// --------------------------------------------------------------------------- + +namespace android { + +#define INITIAL_STRONG_VALUE (1<<28) + +// --------------------------------------------------------------------------- + +class RefBase::weakref_impl : public RefBase::weakref_type +{ +public: + volatile int32_t mStrong; + volatile int32_t mWeak; + RefBase* const mBase; + volatile int32_t mFlags; + + +#if !DEBUG_REFS + + weakref_impl(RefBase* base) + : mStrong(INITIAL_STRONG_VALUE) + , mWeak(0) + , mBase(base) + , mFlags(0) + { + } + + void addStrongRef(const void* /*id*/) { } + void removeStrongRef(const void* /*id*/) { } + void addWeakRef(const void* /*id*/) { } + void removeWeakRef(const void* /*id*/) { } + void printRefs() const { } + void trackMe(bool, bool) { } + +#else + + weakref_impl(RefBase* base) + : mStrong(INITIAL_STRONG_VALUE) + , mWeak(0) + , mBase(base) + , mFlags(0) + , mStrongRefs(NULL) + , mWeakRefs(NULL) + , mTrackEnabled(!!DEBUG_REFS_ENABLED_BY_DEFAULT) + , mRetain(false) + { + //LOGI("NEW weakref_impl %p for RefBase %p", this, base); + } + + ~weakref_impl() + { + LOG_ALWAYS_FATAL_IF(!mRetain && mStrongRefs != NULL, "Strong references remain!"); + LOG_ALWAYS_FATAL_IF(!mRetain && mWeakRefs != NULL, "Weak references remain!"); + } + + void addStrongRef(const void* id) + { + addRef(&mStrongRefs, id, mStrong); + } + + void removeStrongRef(const void* id) + { + if (!mRetain) + removeRef(&mStrongRefs, id); + else + addRef(&mStrongRefs, id, -mStrong); + } + + void addWeakRef(const void* id) + { + addRef(&mWeakRefs, id, mWeak); + } + + void removeWeakRef(const void* id) + { + if (!mRetain) + removeRef(&mWeakRefs, id); + else + addRef(&mWeakRefs, id, -mWeak); + } + + void trackMe(bool track, bool retain) + { + mTrackEnabled = track; + mRetain = retain; + } + + void printRefs() const + { + String8 text; + + { + AutoMutex _l(const_cast<weakref_impl*>(this)->mMutex); + + char buf[128]; + sprintf(buf, "Strong references on RefBase %p (weakref_type %p):\n", mBase, this); + text.append(buf); + printRefsLocked(&text, mStrongRefs); + sprintf(buf, "Weak references on RefBase %p (weakref_type %p):\n", mBase, this); + text.append(buf); + printRefsLocked(&text, mWeakRefs); + } + + { + char name[100]; + snprintf(name, 100, "/data/%p.stack", this); + int rc = open(name, O_RDWR | O_CREAT | O_APPEND); + if (rc >= 0) { + write(rc, text.string(), text.length()); + close(rc); + LOGD("STACK TRACE for %p saved in %s", this, name); + } + else LOGE("FAILED TO PRINT STACK TRACE for %p in %s: %s", this, + name, strerror(errno)); + } + } + +private: + struct ref_entry + { + ref_entry* next; + const void* id; +#if DEBUG_REFS_CALLSTACK_ENABLED + CallStack stack; +#endif + int32_t ref; + }; + + void addRef(ref_entry** refs, const void* id, int32_t mRef) + { + if (mTrackEnabled) { + AutoMutex _l(mMutex); + ref_entry* ref = new ref_entry; + // Reference count at the time of the snapshot, but before the + // update. Positive value means we increment, negative--we + // decrement the reference count. + ref->ref = mRef; + ref->id = id; +#if DEBUG_REFS_CALLSTACK_ENABLED + ref->stack.update(2); +#endif + + ref->next = *refs; + *refs = ref; + } + } + + void removeRef(ref_entry** refs, const void* id) + { + if (mTrackEnabled) { + AutoMutex _l(mMutex); + + ref_entry* ref = *refs; + while (ref != NULL) { + if (ref->id == id) { + *refs = ref->next; + delete ref; + return; + } + + refs = &ref->next; + ref = *refs; + } + + LOG_ALWAYS_FATAL("RefBase: removing id %p on RefBase %p (weakref_type %p) that doesn't exist!", + id, mBase, this); + } + } + + void printRefsLocked(String8* out, const ref_entry* refs) const + { + char buf[128]; + while (refs) { + char inc = refs->ref >= 0 ? '+' : '-'; + sprintf(buf, "\t%c ID %p (ref %d):\n", + inc, refs->id, refs->ref); + out->append(buf); +#if DEBUG_REFS_CALLSTACK_ENABLED + out->append(refs->stack.toString("\t\t")); +#else + out->append("\t\t(call stacks disabled)"); +#endif + refs = refs->next; + } + } + + Mutex mMutex; + ref_entry* mStrongRefs; + ref_entry* mWeakRefs; + + bool mTrackEnabled; + // Collect stack traces on addref and removeref, instead of deleting the stack references + // on removeref that match the address ones. + bool mRetain; + +#if 0 + void addRef(KeyedVector<const void*, int32_t>* refs, const void* id) + { + AutoMutex _l(mMutex); + ssize_t i = refs->indexOfKey(id); + if (i >= 0) { + ++(refs->editValueAt(i)); + } else { + i = refs->add(id, 1); + } + } + + void removeRef(KeyedVector<const void*, int32_t>* refs, const void* id) + { + AutoMutex _l(mMutex); + ssize_t i = refs->indexOfKey(id); + LOG_ALWAYS_FATAL_IF(i < 0, "RefBase: removing id %p that doesn't exist!", id); + if (i >= 0) { + int32_t val = --(refs->editValueAt(i)); + if (val == 0) { + refs->removeItemsAt(i); + } + } + } + + void printRefs(const KeyedVector<const void*, int32_t>& refs) + { + const size_t N=refs.size(); + for (size_t i=0; i<N; i++) { + printf("\tID %p: %d remain\n", refs.keyAt(i), refs.valueAt(i)); + } + } + + mutable Mutex mMutex; + KeyedVector<const void*, int32_t> mStrongRefs; + KeyedVector<const void*, int32_t> mWeakRefs; +#endif + +#endif +}; + +// --------------------------------------------------------------------------- + +void RefBase::incStrong(const void* id) const +{ + weakref_impl* const refs = mRefs; + refs->addWeakRef(id); + refs->incWeak(id); + + refs->addStrongRef(id); + const int32_t c = android_atomic_inc(&refs->mStrong); + LOG_ASSERT(c > 0, "incStrong() called on %p after last strong ref", refs); +#if PRINT_REFS + LOGD("incStrong of %p from %p: cnt=%d\n", this, id, c); +#endif + if (c != INITIAL_STRONG_VALUE) { + return; + } + + android_atomic_add(-INITIAL_STRONG_VALUE, &refs->mStrong); + const_cast<RefBase*>(this)->onFirstRef(); +} + +void RefBase::decStrong(const void* id) const +{ + weakref_impl* const refs = mRefs; + refs->removeStrongRef(id); + const int32_t c = android_atomic_dec(&refs->mStrong); +#if PRINT_REFS + LOGD("decStrong of %p from %p: cnt=%d\n", this, id, c); +#endif + LOG_ASSERT(c >= 1, "decStrong() called on %p too many times", refs); + if (c == 1) { + const_cast<RefBase*>(this)->onLastStrongRef(id); + if ((refs->mFlags&OBJECT_LIFETIME_WEAK) != OBJECT_LIFETIME_WEAK) { + delete this; + } + } + refs->removeWeakRef(id); + refs->decWeak(id); +} + +void RefBase::forceIncStrong(const void* id) const +{ + weakref_impl* const refs = mRefs; + refs->addWeakRef(id); + refs->incWeak(id); + + refs->addStrongRef(id); + const int32_t c = android_atomic_inc(&refs->mStrong); + LOG_ASSERT(c >= 0, "forceIncStrong called on %p after ref count underflow", + refs); +#if PRINT_REFS + LOGD("forceIncStrong of %p from %p: cnt=%d\n", this, id, c); +#endif + + switch (c) { + case INITIAL_STRONG_VALUE: + android_atomic_add(-INITIAL_STRONG_VALUE, &refs->mStrong); + // fall through... + case 0: + const_cast<RefBase*>(this)->onFirstRef(); + } +} + +int32_t RefBase::getStrongCount() const +{ + return mRefs->mStrong; +} + + + +RefBase* RefBase::weakref_type::refBase() const +{ + return static_cast<const weakref_impl*>(this)->mBase; +} + +void RefBase::weakref_type::incWeak(const void* id) +{ + weakref_impl* const impl = static_cast<weakref_impl*>(this); + impl->addWeakRef(id); + const int32_t c = android_atomic_inc(&impl->mWeak); + LOG_ASSERT(c >= 0, "incWeak called on %p after last weak ref", this); +} + +void RefBase::weakref_type::decWeak(const void* id) +{ + weakref_impl* const impl = static_cast<weakref_impl*>(this); + impl->removeWeakRef(id); + const int32_t c = android_atomic_dec(&impl->mWeak); + LOG_ASSERT(c >= 1, "decWeak called on %p too many times", this); + if (c != 1) return; + + if ((impl->mFlags&OBJECT_LIFETIME_WEAK) != OBJECT_LIFETIME_WEAK) { + if (impl->mStrong == INITIAL_STRONG_VALUE) + delete impl->mBase; + else { +// LOGV("Freeing refs %p of old RefBase %p\n", this, impl->mBase); + delete impl; + } + } else { + impl->mBase->onLastWeakRef(id); + if ((impl->mFlags&OBJECT_LIFETIME_FOREVER) != OBJECT_LIFETIME_FOREVER) { + delete impl->mBase; + } + } +} + +bool RefBase::weakref_type::attemptIncStrong(const void* id) +{ + incWeak(id); + + weakref_impl* const impl = static_cast<weakref_impl*>(this); + + int32_t curCount = impl->mStrong; + LOG_ASSERT(curCount >= 0, "attemptIncStrong called on %p after underflow", + this); + while (curCount > 0 && curCount != INITIAL_STRONG_VALUE) { + if (android_atomic_cmpxchg(curCount, curCount+1, &impl->mStrong) == 0) { + break; + } + curCount = impl->mStrong; + } + + if (curCount <= 0 || curCount == INITIAL_STRONG_VALUE) { + bool allow; + if (curCount == INITIAL_STRONG_VALUE) { + // Attempting to acquire first strong reference... this is allowed + // if the object does NOT have a longer lifetime (meaning the + // implementation doesn't need to see this), or if the implementation + // allows it to happen. + allow = (impl->mFlags&OBJECT_LIFETIME_WEAK) != OBJECT_LIFETIME_WEAK + || impl->mBase->onIncStrongAttempted(FIRST_INC_STRONG, id); + } else { + // Attempting to revive the object... this is allowed + // if the object DOES have a longer lifetime (so we can safely + // call the object with only a weak ref) and the implementation + // allows it to happen. + allow = (impl->mFlags&OBJECT_LIFETIME_WEAK) == OBJECT_LIFETIME_WEAK + && impl->mBase->onIncStrongAttempted(FIRST_INC_STRONG, id); + } + if (!allow) { + decWeak(id); + return false; + } + curCount = android_atomic_inc(&impl->mStrong); + + // If the strong reference count has already been incremented by + // someone else, the implementor of onIncStrongAttempted() is holding + // an unneeded reference. So call onLastStrongRef() here to remove it. + // (No, this is not pretty.) Note that we MUST NOT do this if we + // are in fact acquiring the first reference. + if (curCount > 0 && curCount < INITIAL_STRONG_VALUE) { + impl->mBase->onLastStrongRef(id); + } + } + + impl->addWeakRef(id); + impl->addStrongRef(id); + +#if PRINT_REFS + LOGD("attemptIncStrong of %p from %p: cnt=%d\n", this, id, curCount); +#endif + + if (curCount == INITIAL_STRONG_VALUE) { + android_atomic_add(-INITIAL_STRONG_VALUE, &impl->mStrong); + impl->mBase->onFirstRef(); + } + + return true; +} + +bool RefBase::weakref_type::attemptIncWeak(const void* id) +{ + weakref_impl* const impl = static_cast<weakref_impl*>(this); + + int32_t curCount = impl->mWeak; + LOG_ASSERT(curCount >= 0, "attemptIncWeak called on %p after underflow", + this); + while (curCount > 0) { + if (android_atomic_cmpxchg(curCount, curCount+1, &impl->mWeak) == 0) { + break; + } + curCount = impl->mWeak; + } + + if (curCount > 0) { + impl->addWeakRef(id); + } + + return curCount > 0; +} + +int32_t RefBase::weakref_type::getWeakCount() const +{ + return static_cast<const weakref_impl*>(this)->mWeak; +} + +void RefBase::weakref_type::printRefs() const +{ + static_cast<const weakref_impl*>(this)->printRefs(); +} + +void RefBase::weakref_type::trackMe(bool enable, bool retain) +{ + static_cast<const weakref_impl*>(this)->trackMe(enable, retain); +} + +RefBase::weakref_type* RefBase::createWeak(const void* id) const +{ + mRefs->incWeak(id); + return mRefs; +} + +RefBase::weakref_type* RefBase::getWeakRefs() const +{ + return mRefs; +} + +RefBase::RefBase() + : mRefs(new weakref_impl(this)) +{ +// LOGV("Creating refs %p with RefBase %p\n", mRefs, this); +} + +RefBase::~RefBase() +{ +// LOGV("Destroying RefBase %p (refs %p)\n", this, mRefs); + if (mRefs->mWeak == 0) { +// LOGV("Freeing refs %p of old RefBase %p\n", mRefs, this); + delete mRefs; + } +} + +void RefBase::extendObjectLifetime(int32_t mode) +{ + android_atomic_or(mode, &mRefs->mFlags); +} + +void RefBase::onFirstRef() +{ +} + +void RefBase::onLastStrongRef(const void* /*id*/) +{ +} + +bool RefBase::onIncStrongAttempted(uint32_t flags, const void* id) +{ + return (flags&FIRST_INC_STRONG) ? true : false; +} + +void RefBase::onLastWeakRef(const void* /*id*/) +{ +} + +}; // namespace android diff --git a/libs/utils/ResourceTypes.cpp b/libs/utils/ResourceTypes.cpp new file mode 100644 index 0000000..a5fe9fb --- /dev/null +++ b/libs/utils/ResourceTypes.cpp @@ -0,0 +1,3969 @@ +/* + * Copyright (C) 2008 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. + */ + +#define LOG_TAG "ResourceType" +//#define LOG_NDEBUG 0 + +#include <utils/Atomic.h> +#include <utils/ByteOrder.h> +#include <utils/Debug.h> +#include <utils/ResourceTypes.h> +#include <utils/String16.h> +#include <utils/String8.h> +#include <utils/TextOutput.h> +#include <utils/Log.h> + +#include <stdlib.h> +#include <string.h> +#include <memory.h> +#include <ctype.h> +#include <stdint.h> + +#ifndef INT32_MAX +#define INT32_MAX ((int32_t)(2147483647)) +#endif + +#define POOL_NOISY(x) //x +#define XML_NOISY(x) //x +#define TABLE_NOISY(x) //x +#define TABLE_GETENTRY(x) //x +#define TABLE_SUPER_NOISY(x) //x +#define LOAD_TABLE_NOISY(x) //x + +namespace android { + +#ifdef HAVE_WINSOCK +#undef nhtol +#undef htonl + +#ifdef HAVE_LITTLE_ENDIAN +#define ntohl(x) ( ((x) << 24) | (((x) >> 24) & 255) | (((x) << 8) & 0xff0000) | (((x) >> 8) & 0xff00) ) +#define htonl(x) ntohl(x) +#define ntohs(x) ( (((x) << 8) & 0xff00) | (((x) >> 8) & 255) ) +#define htons(x) ntohs(x) +#else +#define ntohl(x) (x) +#define htonl(x) (x) +#define ntohs(x) (x) +#define htons(x) (x) +#endif +#endif + +static void printToLogFunc(void* cookie, const char* txt) +{ + LOGV("%s", txt); +} + +// Standard C isspace() is only required to look at the low byte of its input, so +// produces incorrect results for UTF-16 characters. For safety's sake, assume that +// any high-byte UTF-16 code point is not whitespace. +inline int isspace16(char16_t c) { + return (c < 0x0080 && isspace(c)); +} + +// range checked; guaranteed to NUL-terminate within the stated number of available slots +// NOTE: if this truncates the dst string due to running out of space, no attempt is +// made to avoid splitting surrogate pairs. +static void strcpy16_dtoh(uint16_t* dst, const uint16_t* src, size_t avail) +{ + uint16_t* last = dst + avail - 1; + while (*src && (dst < last)) { + char16_t s = dtohs(*src); + *dst++ = s; + src++; + } + *dst = 0; +} + +static status_t validate_chunk(const ResChunk_header* chunk, + size_t minSize, + const uint8_t* dataEnd, + const char* name) +{ + const uint16_t headerSize = dtohs(chunk->headerSize); + const uint32_t size = dtohl(chunk->size); + + if (headerSize >= minSize) { + if (headerSize <= size) { + if (((headerSize|size)&0x3) == 0) { + if ((ssize_t)size <= (dataEnd-((const uint8_t*)chunk))) { + return NO_ERROR; + } + LOGW("%s data size %p extends beyond resource end %p.", + name, (void*)size, + (void*)(dataEnd-((const uint8_t*)chunk))); + return BAD_TYPE; + } + LOGW("%s size 0x%x or headerSize 0x%x is not on an integer boundary.", + name, (int)size, (int)headerSize); + return BAD_TYPE; + } + LOGW("%s size %p is smaller than header size %p.", + name, (void*)size, (void*)(int)headerSize); + return BAD_TYPE; + } + LOGW("%s header size %p is too small.", + name, (void*)(int)headerSize); + return BAD_TYPE; +} + +inline void Res_value::copyFrom_dtoh(const Res_value& src) +{ + size = dtohs(src.size); + res0 = src.res0; + dataType = src.dataType; + data = dtohl(src.data); +} + +void Res_png_9patch::deviceToFile() +{ + for (int i = 0; i < numXDivs; i++) { + xDivs[i] = htonl(xDivs[i]); + } + for (int i = 0; i < numYDivs; i++) { + yDivs[i] = htonl(yDivs[i]); + } + paddingLeft = htonl(paddingLeft); + paddingRight = htonl(paddingRight); + paddingTop = htonl(paddingTop); + paddingBottom = htonl(paddingBottom); + for (int i=0; i<numColors; i++) { + colors[i] = htonl(colors[i]); + } +} + +void Res_png_9patch::fileToDevice() +{ + for (int i = 0; i < numXDivs; i++) { + xDivs[i] = ntohl(xDivs[i]); + } + for (int i = 0; i < numYDivs; i++) { + yDivs[i] = ntohl(yDivs[i]); + } + paddingLeft = ntohl(paddingLeft); + paddingRight = ntohl(paddingRight); + paddingTop = ntohl(paddingTop); + paddingBottom = ntohl(paddingBottom); + for (int i=0; i<numColors; i++) { + colors[i] = ntohl(colors[i]); + } +} + +size_t Res_png_9patch::serializedSize() +{ + return sizeof(Res_png_9patch) + + numXDivs * sizeof(int32_t) + + numYDivs * sizeof(int32_t) + + numColors * sizeof(uint32_t); +} + +void* Res_png_9patch::serialize() +{ + void* newData = malloc(serializedSize()); + serialize(newData); + return newData; +} + +void Res_png_9patch::serialize(void * outData) +{ + char* data = (char*) outData; + memmove(data, this, sizeof(Res_png_9patch)); + data += sizeof(Res_png_9patch); + memmove(data, this->xDivs, numXDivs * sizeof(int32_t)); + data += numXDivs * sizeof(int32_t); + memmove(data, this->yDivs, numYDivs * sizeof(int32_t)); + data += numYDivs * sizeof(int32_t); + memmove(data, this->colors, numColors * sizeof(uint32_t)); +} + +Res_png_9patch* Res_png_9patch::deserialize(const void* inData) +{ + deserialize(inData, (Res_png_9patch*) inData); + return (Res_png_9patch*) inData; +} + +void Res_png_9patch::deserialize(const void* inData, Res_png_9patch* outData) { + Res_png_9patch* patch = (Res_png_9patch*) inData; + if (inData != outData) { + memcpy(outData, inData, patch->serializedSize()); + } + outData->wasDeserialized = true; + char* data = (char*)outData; + data += sizeof(Res_png_9patch); + outData->xDivs = (int32_t*) data; + data += patch->numXDivs * sizeof(int32_t); + outData->yDivs = (int32_t*) data; + data += patch->numYDivs * sizeof(int32_t); + outData->colors = (uint32_t*) data; +} + +// -------------------------------------------------------------------- +// -------------------------------------------------------------------- +// -------------------------------------------------------------------- + +ResStringPool::ResStringPool() + : mError(NO_INIT), mOwnedData(NULL) +{ +} + +ResStringPool::ResStringPool(const void* data, size_t size, bool copyData) + : mError(NO_INIT), mOwnedData(NULL) +{ + setTo(data, size, copyData); +} + +ResStringPool::~ResStringPool() +{ + uninit(); +} + +status_t ResStringPool::setTo(const void* data, size_t size, bool copyData) +{ + if (!data || !size) { + return (mError=BAD_TYPE); + } + + uninit(); + + const bool notDeviceEndian = htods(0xf0) != 0xf0; + + if (copyData || notDeviceEndian) { + mOwnedData = malloc(size); + if (mOwnedData == NULL) { + return (mError=NO_MEMORY); + } + memcpy(mOwnedData, data, size); + data = mOwnedData; + } + + mHeader = (const ResStringPool_header*)data; + + if (notDeviceEndian) { + ResStringPool_header* h = const_cast<ResStringPool_header*>(mHeader); + h->header.headerSize = dtohs(mHeader->header.headerSize); + h->header.type = dtohs(mHeader->header.type); + h->header.size = dtohl(mHeader->header.size); + h->stringCount = dtohl(mHeader->stringCount); + h->styleCount = dtohl(mHeader->styleCount); + h->flags = dtohl(mHeader->flags); + h->stringsStart = dtohl(mHeader->stringsStart); + h->stylesStart = dtohl(mHeader->stylesStart); + } + + if (mHeader->header.headerSize > mHeader->header.size + || mHeader->header.size > size) { + LOGW("Bad string block: header size %d or total size %d is larger than data size %d\n", + (int)mHeader->header.headerSize, (int)mHeader->header.size, (int)size); + return (mError=BAD_TYPE); + } + mSize = mHeader->header.size; + mEntries = (const uint32_t*) + (((const uint8_t*)data)+mHeader->header.headerSize); + + if (mHeader->stringCount > 0) { + if ((mHeader->stringCount*sizeof(uint32_t) < mHeader->stringCount) // uint32 overflow? + || (mHeader->header.headerSize+(mHeader->stringCount*sizeof(uint32_t))) + > size) { + LOGW("Bad string block: entry of %d items extends past data size %d\n", + (int)(mHeader->header.headerSize+(mHeader->stringCount*sizeof(uint32_t))), + (int)size); + return (mError=BAD_TYPE); + } + mStrings = (const char16_t*) + (((const uint8_t*)data)+mHeader->stringsStart); + if (mHeader->stringsStart >= (mHeader->header.size-sizeof(uint16_t))) { + LOGW("Bad string block: string pool starts at %d, after total size %d\n", + (int)mHeader->stringsStart, (int)mHeader->header.size); + return (mError=BAD_TYPE); + } + if (mHeader->styleCount == 0) { + mStringPoolSize = + (mHeader->header.size-mHeader->stringsStart)/sizeof(uint16_t); + } else { + // check invariant: styles follow the strings + if (mHeader->stylesStart <= mHeader->stringsStart) { + LOGW("Bad style block: style block starts at %d, before strings at %d\n", + (int)mHeader->stylesStart, (int)mHeader->stringsStart); + return (mError=BAD_TYPE); + } + mStringPoolSize = + (mHeader->stylesStart-mHeader->stringsStart)/sizeof(uint16_t); + } + + // check invariant: stringCount > 0 requires a string pool to exist + if (mStringPoolSize == 0) { + LOGW("Bad string block: stringCount is %d but pool size is 0\n", (int)mHeader->stringCount); + return (mError=BAD_TYPE); + } + + if (notDeviceEndian) { + size_t i; + uint32_t* e = const_cast<uint32_t*>(mEntries); + for (i=0; i<mHeader->stringCount; i++) { + e[i] = dtohl(mEntries[i]); + } + char16_t* s = const_cast<char16_t*>(mStrings); + for (i=0; i<mStringPoolSize; i++) { + s[i] = dtohs(mStrings[i]); + } + } + + if (mStrings[mStringPoolSize-1] != 0) { + LOGW("Bad string block: last string is not 0-terminated\n"); + return (mError=BAD_TYPE); + } + } else { + mStrings = NULL; + mStringPoolSize = 0; + } + + if (mHeader->styleCount > 0) { + mEntryStyles = mEntries + mHeader->stringCount; + // invariant: integer overflow in calculating mEntryStyles + if (mEntryStyles < mEntries) { + LOGW("Bad string block: integer overflow finding styles\n"); + return (mError=BAD_TYPE); + } + + if (((const uint8_t*)mEntryStyles-(const uint8_t*)mHeader) > (int)size) { + LOGW("Bad string block: entry of %d styles extends past data size %d\n", + (int)((const uint8_t*)mEntryStyles-(const uint8_t*)mHeader), + (int)size); + return (mError=BAD_TYPE); + } + mStyles = (const uint32_t*) + (((const uint8_t*)data)+mHeader->stylesStart); + if (mHeader->stylesStart >= mHeader->header.size) { + LOGW("Bad string block: style pool starts %d, after total size %d\n", + (int)mHeader->stylesStart, (int)mHeader->header.size); + return (mError=BAD_TYPE); + } + mStylePoolSize = + (mHeader->header.size-mHeader->stylesStart)/sizeof(uint32_t); + + if (notDeviceEndian) { + size_t i; + uint32_t* e = const_cast<uint32_t*>(mEntryStyles); + for (i=0; i<mHeader->styleCount; i++) { + e[i] = dtohl(mEntryStyles[i]); + } + uint32_t* s = const_cast<uint32_t*>(mStyles); + for (i=0; i<mStylePoolSize; i++) { + s[i] = dtohl(mStyles[i]); + } + } + + const ResStringPool_span endSpan = { + { htodl(ResStringPool_span::END) }, + htodl(ResStringPool_span::END), htodl(ResStringPool_span::END) + }; + if (memcmp(&mStyles[mStylePoolSize-(sizeof(endSpan)/sizeof(uint32_t))], + &endSpan, sizeof(endSpan)) != 0) { + LOGW("Bad string block: last style is not 0xFFFFFFFF-terminated\n"); + return (mError=BAD_TYPE); + } + } else { + mEntryStyles = NULL; + mStyles = NULL; + mStylePoolSize = 0; + } + + return (mError=NO_ERROR); +} + +status_t ResStringPool::getError() const +{ + return mError; +} + +void ResStringPool::uninit() +{ + mError = NO_INIT; + if (mOwnedData) { + free(mOwnedData); + mOwnedData = NULL; + } +} + +const uint16_t* ResStringPool::stringAt(size_t idx, size_t* outLen) const +{ + if (mError == NO_ERROR && idx < mHeader->stringCount) { + const uint32_t off = (mEntries[idx]/sizeof(uint16_t)); + if (off < (mStringPoolSize-1)) { + const char16_t* str = mStrings+off; + *outLen = *str; + if ((*str)&0x8000) { + str++; + *outLen = (((*outLen)&0x7fff)<<16) + *str; + } + if ((uint32_t)(str+1+*outLen-mStrings) < mStringPoolSize) { + return str+1; + } else { + LOGW("Bad string block: string #%d extends to %d, past end at %d\n", + (int)idx, (int)(str+1+*outLen-mStrings), (int)mStringPoolSize); + } + } else { + LOGW("Bad string block: string #%d entry is at %d, past end at %d\n", + (int)idx, (int)(off*sizeof(uint16_t)), + (int)(mStringPoolSize*sizeof(uint16_t))); + } + } + return NULL; +} + +const ResStringPool_span* ResStringPool::styleAt(const ResStringPool_ref& ref) const +{ + return styleAt(ref.index); +} + +const ResStringPool_span* ResStringPool::styleAt(size_t idx) const +{ + if (mError == NO_ERROR && idx < mHeader->styleCount) { + const uint32_t off = (mEntryStyles[idx]/sizeof(uint32_t)); + if (off < mStylePoolSize) { + return (const ResStringPool_span*)(mStyles+off); + } else { + LOGW("Bad string block: style #%d entry is at %d, past end at %d\n", + (int)idx, (int)(off*sizeof(uint32_t)), + (int)(mStylePoolSize*sizeof(uint32_t))); + } + } + return NULL; +} + +ssize_t ResStringPool::indexOfString(const char16_t* str, size_t strLen) const +{ + if (mError != NO_ERROR) { + return mError; + } + + size_t len; + + if (mHeader->flags&ResStringPool_header::SORTED_FLAG) { + // Do a binary search for the string... + ssize_t l = 0; + ssize_t h = mHeader->stringCount-1; + + ssize_t mid; + while (l <= h) { + mid = l + (h - l)/2; + const char16_t* s = stringAt(mid, &len); + int c = s ? strzcmp16(s, len, str, strLen) : -1; + POOL_NOISY(printf("Looking for %s, at %s, cmp=%d, l/mid/h=%d/%d/%d\n", + String8(str).string(), + String8(s).string(), + c, (int)l, (int)mid, (int)h)); + if (c == 0) { + return mid; + } else if (c < 0) { + l = mid + 1; + } else { + h = mid - 1; + } + } + } else { + // It is unusual to get the ID from an unsorted string block... + // most often this happens because we want to get IDs for style + // span tags; since those always appear at the end of the string + // block, start searching at the back. + for (int i=mHeader->stringCount-1; i>=0; i--) { + const char16_t* s = stringAt(i, &len); + POOL_NOISY(printf("Looking for %s, at %s, i=%d\n", + String8(str, strLen).string(), + String8(s).string(), + i)); + if (s && strzcmp16(s, len, str, strLen) == 0) { + return i; + } + } + } + + return NAME_NOT_FOUND; +} + +size_t ResStringPool::size() const +{ + return (mError == NO_ERROR) ? mHeader->stringCount : 0; +} + +// -------------------------------------------------------------------- +// -------------------------------------------------------------------- +// -------------------------------------------------------------------- + +ResXMLParser::ResXMLParser(const ResXMLTree& tree) + : mTree(tree), mEventCode(BAD_DOCUMENT) +{ +} + +void ResXMLParser::restart() +{ + mCurNode = NULL; + mEventCode = mTree.mError == NO_ERROR ? START_DOCUMENT : BAD_DOCUMENT; +} + +ResXMLParser::event_code_t ResXMLParser::getEventType() const +{ + return mEventCode; +} + +ResXMLParser::event_code_t ResXMLParser::next() +{ + if (mEventCode == START_DOCUMENT) { + mCurNode = mTree.mRootNode; + mCurExt = mTree.mRootExt; + return (mEventCode=mTree.mRootCode); + } else if (mEventCode >= FIRST_CHUNK_CODE) { + return nextNode(); + } + return mEventCode; +} + +const int32_t ResXMLParser::getCommentID() const +{ + return mCurNode != NULL ? dtohl(mCurNode->comment.index) : -1; +} + +const uint16_t* ResXMLParser::getComment(size_t* outLen) const +{ + int32_t id = getCommentID(); + return id >= 0 ? mTree.mStrings.stringAt(id, outLen) : NULL; +} + +const uint32_t ResXMLParser::getLineNumber() const +{ + return mCurNode != NULL ? dtohl(mCurNode->lineNumber) : -1; +} + +const int32_t ResXMLParser::getTextID() const +{ + if (mEventCode == TEXT) { + return dtohl(((const ResXMLTree_cdataExt*)mCurExt)->data.index); + } + return -1; +} + +const uint16_t* ResXMLParser::getText(size_t* outLen) const +{ + int32_t id = getTextID(); + return id >= 0 ? mTree.mStrings.stringAt(id, outLen) : NULL; +} + +ssize_t ResXMLParser::getTextValue(Res_value* outValue) const +{ + if (mEventCode == TEXT) { + outValue->copyFrom_dtoh(((const ResXMLTree_cdataExt*)mCurExt)->typedData); + return sizeof(Res_value); + } + return BAD_TYPE; +} + +const int32_t ResXMLParser::getNamespacePrefixID() const +{ + if (mEventCode == START_NAMESPACE || mEventCode == END_NAMESPACE) { + return dtohl(((const ResXMLTree_namespaceExt*)mCurExt)->prefix.index); + } + return -1; +} + +const uint16_t* ResXMLParser::getNamespacePrefix(size_t* outLen) const +{ + int32_t id = getNamespacePrefixID(); + //printf("prefix=%d event=%p\n", id, mEventCode); + return id >= 0 ? mTree.mStrings.stringAt(id, outLen) : NULL; +} + +const int32_t ResXMLParser::getNamespaceUriID() const +{ + if (mEventCode == START_NAMESPACE || mEventCode == END_NAMESPACE) { + return dtohl(((const ResXMLTree_namespaceExt*)mCurExt)->uri.index); + } + return -1; +} + +const uint16_t* ResXMLParser::getNamespaceUri(size_t* outLen) const +{ + int32_t id = getNamespaceUriID(); + //printf("uri=%d event=%p\n", id, mEventCode); + return id >= 0 ? mTree.mStrings.stringAt(id, outLen) : NULL; +} + +const int32_t ResXMLParser::getElementNamespaceID() const +{ + if (mEventCode == START_TAG) { + return dtohl(((const ResXMLTree_attrExt*)mCurExt)->ns.index); + } + if (mEventCode == END_TAG) { + return dtohl(((const ResXMLTree_endElementExt*)mCurExt)->ns.index); + } + return -1; +} + +const uint16_t* ResXMLParser::getElementNamespace(size_t* outLen) const +{ + int32_t id = getElementNamespaceID(); + return id >= 0 ? mTree.mStrings.stringAt(id, outLen) : NULL; +} + +const int32_t ResXMLParser::getElementNameID() const +{ + if (mEventCode == START_TAG) { + return dtohl(((const ResXMLTree_attrExt*)mCurExt)->name.index); + } + if (mEventCode == END_TAG) { + return dtohl(((const ResXMLTree_endElementExt*)mCurExt)->name.index); + } + return -1; +} + +const uint16_t* ResXMLParser::getElementName(size_t* outLen) const +{ + int32_t id = getElementNameID(); + return id >= 0 ? mTree.mStrings.stringAt(id, outLen) : NULL; +} + +size_t ResXMLParser::getAttributeCount() const +{ + if (mEventCode == START_TAG) { + return dtohs(((const ResXMLTree_attrExt*)mCurExt)->attributeCount); + } + return 0; +} + +const int32_t ResXMLParser::getAttributeNamespaceID(size_t idx) const +{ + if (mEventCode == START_TAG) { + const ResXMLTree_attrExt* tag = (const ResXMLTree_attrExt*)mCurExt; + if (idx < dtohs(tag->attributeCount)) { + const ResXMLTree_attribute* attr = (const ResXMLTree_attribute*) + (((const uint8_t*)tag) + + dtohs(tag->attributeStart) + + (dtohs(tag->attributeSize)*idx)); + return dtohl(attr->ns.index); + } + } + return -2; +} + +const uint16_t* ResXMLParser::getAttributeNamespace(size_t idx, size_t* outLen) const +{ + int32_t id = getAttributeNamespaceID(idx); + //printf("attribute namespace=%d idx=%d event=%p\n", id, idx, mEventCode); + //XML_NOISY(printf("getAttributeNamespace 0x%x=0x%x\n", idx, id)); + return id >= 0 ? mTree.mStrings.stringAt(id, outLen) : NULL; +} + +const int32_t ResXMLParser::getAttributeNameID(size_t idx) const +{ + if (mEventCode == START_TAG) { + const ResXMLTree_attrExt* tag = (const ResXMLTree_attrExt*)mCurExt; + if (idx < dtohs(tag->attributeCount)) { + const ResXMLTree_attribute* attr = (const ResXMLTree_attribute*) + (((const uint8_t*)tag) + + dtohs(tag->attributeStart) + + (dtohs(tag->attributeSize)*idx)); + return dtohl(attr->name.index); + } + } + return -1; +} + +const uint16_t* ResXMLParser::getAttributeName(size_t idx, size_t* outLen) const +{ + int32_t id = getAttributeNameID(idx); + //printf("attribute name=%d idx=%d event=%p\n", id, idx, mEventCode); + //XML_NOISY(printf("getAttributeName 0x%x=0x%x\n", idx, id)); + return id >= 0 ? mTree.mStrings.stringAt(id, outLen) : NULL; +} + +const uint32_t ResXMLParser::getAttributeNameResID(size_t idx) const +{ + int32_t id = getAttributeNameID(idx); + if (id >= 0 && (size_t)id < mTree.mNumResIds) { + return dtohl(mTree.mResIds[id]); + } + return 0; +} + +const int32_t ResXMLParser::getAttributeValueStringID(size_t idx) const +{ + if (mEventCode == START_TAG) { + const ResXMLTree_attrExt* tag = (const ResXMLTree_attrExt*)mCurExt; + if (idx < dtohs(tag->attributeCount)) { + const ResXMLTree_attribute* attr = (const ResXMLTree_attribute*) + (((const uint8_t*)tag) + + dtohs(tag->attributeStart) + + (dtohs(tag->attributeSize)*idx)); + return dtohl(attr->rawValue.index); + } + } + return -1; +} + +const uint16_t* ResXMLParser::getAttributeStringValue(size_t idx, size_t* outLen) const +{ + int32_t id = getAttributeValueStringID(idx); + //XML_NOISY(printf("getAttributeValue 0x%x=0x%x\n", idx, id)); + return id >= 0 ? mTree.mStrings.stringAt(id, outLen) : NULL; +} + +int32_t ResXMLParser::getAttributeDataType(size_t idx) const +{ + if (mEventCode == START_TAG) { + const ResXMLTree_attrExt* tag = (const ResXMLTree_attrExt*)mCurExt; + if (idx < dtohs(tag->attributeCount)) { + const ResXMLTree_attribute* attr = (const ResXMLTree_attribute*) + (((const uint8_t*)tag) + + dtohs(tag->attributeStart) + + (dtohs(tag->attributeSize)*idx)); + return attr->typedValue.dataType; + } + } + return Res_value::TYPE_NULL; +} + +int32_t ResXMLParser::getAttributeData(size_t idx) const +{ + if (mEventCode == START_TAG) { + const ResXMLTree_attrExt* tag = (const ResXMLTree_attrExt*)mCurExt; + if (idx < dtohs(tag->attributeCount)) { + const ResXMLTree_attribute* attr = (const ResXMLTree_attribute*) + (((const uint8_t*)tag) + + dtohs(tag->attributeStart) + + (dtohs(tag->attributeSize)*idx)); + return dtohl(attr->typedValue.data); + } + } + return 0; +} + +ssize_t ResXMLParser::getAttributeValue(size_t idx, Res_value* outValue) const +{ + if (mEventCode == START_TAG) { + const ResXMLTree_attrExt* tag = (const ResXMLTree_attrExt*)mCurExt; + if (idx < dtohs(tag->attributeCount)) { + const ResXMLTree_attribute* attr = (const ResXMLTree_attribute*) + (((const uint8_t*)tag) + + dtohs(tag->attributeStart) + + (dtohs(tag->attributeSize)*idx)); + outValue->copyFrom_dtoh(attr->typedValue); + return sizeof(Res_value); + } + } + return BAD_TYPE; +} + +ssize_t ResXMLParser::indexOfAttribute(const char* ns, const char* attr) const +{ + String16 nsStr(ns != NULL ? ns : ""); + String16 attrStr(attr); + return indexOfAttribute(ns ? nsStr.string() : NULL, ns ? nsStr.size() : 0, + attrStr.string(), attrStr.size()); +} + +ssize_t ResXMLParser::indexOfAttribute(const char16_t* ns, size_t nsLen, + const char16_t* attr, size_t attrLen) const +{ + if (mEventCode == START_TAG) { + const size_t N = getAttributeCount(); + for (size_t i=0; i<N; i++) { + size_t curNsLen, curAttrLen; + const char16_t* curNs = getAttributeNamespace(i, &curNsLen); + const char16_t* curAttr = getAttributeName(i, &curAttrLen); + //printf("%d: ns=%p attr=%p curNs=%p curAttr=%p\n", + // i, ns, attr, curNs, curAttr); + //printf(" --> attr=%s, curAttr=%s\n", + // String8(attr).string(), String8(curAttr).string()); + if (attr && curAttr && (strzcmp16(attr, attrLen, curAttr, curAttrLen) == 0)) { + if (ns == NULL) { + if (curNs == NULL) return i; + } else if (curNs != NULL) { + //printf(" --> ns=%s, curNs=%s\n", + // String8(ns).string(), String8(curNs).string()); + if (strzcmp16(ns, nsLen, curNs, curNsLen) == 0) return i; + } + } + } + } + + return NAME_NOT_FOUND; +} + +ssize_t ResXMLParser::indexOfID() const +{ + if (mEventCode == START_TAG) { + const ssize_t idx = dtohs(((const ResXMLTree_attrExt*)mCurExt)->idIndex); + if (idx > 0) return (idx-1); + } + return NAME_NOT_FOUND; +} + +ssize_t ResXMLParser::indexOfClass() const +{ + if (mEventCode == START_TAG) { + const ssize_t idx = dtohs(((const ResXMLTree_attrExt*)mCurExt)->classIndex); + if (idx > 0) return (idx-1); + } + return NAME_NOT_FOUND; +} + +ssize_t ResXMLParser::indexOfStyle() const +{ + if (mEventCode == START_TAG) { + const ssize_t idx = dtohs(((const ResXMLTree_attrExt*)mCurExt)->styleIndex); + if (idx > 0) return (idx-1); + } + return NAME_NOT_FOUND; +} + +ResXMLParser::event_code_t ResXMLParser::nextNode() +{ + if (mEventCode < 0) { + return mEventCode; + } + + do { + const ResXMLTree_node* next = (const ResXMLTree_node*) + (((const uint8_t*)mCurNode) + dtohl(mCurNode->header.size)); + //LOGW("Next node: prev=%p, next=%p\n", mCurNode, next); + + if (((const uint8_t*)next) >= mTree.mDataEnd) { + mCurNode = NULL; + return (mEventCode=END_DOCUMENT); + } + + if (mTree.validateNode(next) != NO_ERROR) { + mCurNode = NULL; + return (mEventCode=BAD_DOCUMENT); + } + + mCurNode = next; + const uint16_t headerSize = dtohs(next->header.headerSize); + const uint32_t totalSize = dtohl(next->header.size); + mCurExt = ((const uint8_t*)next) + headerSize; + size_t minExtSize = 0; + event_code_t eventCode = (event_code_t)dtohs(next->header.type); + switch ((mEventCode=eventCode)) { + case RES_XML_START_NAMESPACE_TYPE: + case RES_XML_END_NAMESPACE_TYPE: + minExtSize = sizeof(ResXMLTree_namespaceExt); + break; + case RES_XML_START_ELEMENT_TYPE: + minExtSize = sizeof(ResXMLTree_attrExt); + break; + case RES_XML_END_ELEMENT_TYPE: + minExtSize = sizeof(ResXMLTree_endElementExt); + break; + case RES_XML_CDATA_TYPE: + minExtSize = sizeof(ResXMLTree_cdataExt); + break; + default: + LOGW("Unknown XML block: header type %d in node at %d\n", + (int)dtohs(next->header.type), + (int)(((const uint8_t*)next)-((const uint8_t*)mTree.mHeader))); + continue; + } + + if ((totalSize-headerSize) < minExtSize) { + LOGW("Bad XML block: header type 0x%x in node at 0x%x has size %d, need %d\n", + (int)dtohs(next->header.type), + (int)(((const uint8_t*)next)-((const uint8_t*)mTree.mHeader)), + (int)(totalSize-headerSize), (int)minExtSize); + return (mEventCode=BAD_DOCUMENT); + } + + //printf("CurNode=%p, CurExt=%p, headerSize=%d, minExtSize=%d\n", + // mCurNode, mCurExt, headerSize, minExtSize); + + return eventCode; + } while (true); +} + +void ResXMLParser::getPosition(ResXMLParser::ResXMLPosition* pos) const +{ + pos->eventCode = mEventCode; + pos->curNode = mCurNode; + pos->curExt = mCurExt; +} + +void ResXMLParser::setPosition(const ResXMLParser::ResXMLPosition& pos) +{ + mEventCode = pos.eventCode; + mCurNode = pos.curNode; + mCurExt = pos.curExt; +} + + +// -------------------------------------------------------------------- + +static volatile int32_t gCount = 0; + +ResXMLTree::ResXMLTree() + : ResXMLParser(*this) + , mError(NO_INIT), mOwnedData(NULL) +{ + //LOGI("Creating ResXMLTree %p #%d\n", this, android_atomic_inc(&gCount)+1); + restart(); +} + +ResXMLTree::ResXMLTree(const void* data, size_t size, bool copyData) + : ResXMLParser(*this) + , mError(NO_INIT), mOwnedData(NULL) +{ + //LOGI("Creating ResXMLTree %p #%d\n", this, android_atomic_inc(&gCount)+1); + setTo(data, size, copyData); +} + +ResXMLTree::~ResXMLTree() +{ + //LOGI("Destroying ResXMLTree in %p #%d\n", this, android_atomic_dec(&gCount)-1); + uninit(); +} + +status_t ResXMLTree::setTo(const void* data, size_t size, bool copyData) +{ + uninit(); + mEventCode = START_DOCUMENT; + + if (copyData) { + mOwnedData = malloc(size); + if (mOwnedData == NULL) { + return (mError=NO_MEMORY); + } + memcpy(mOwnedData, data, size); + data = mOwnedData; + } + + mHeader = (const ResXMLTree_header*)data; + mSize = dtohl(mHeader->header.size); + if (dtohs(mHeader->header.headerSize) > mSize || mSize > size) { + LOGW("Bad XML block: header size %d or total size %d is larger than data size %d\n", + (int)dtohs(mHeader->header.headerSize), + (int)dtohl(mHeader->header.size), (int)size); + mError = BAD_TYPE; + restart(); + return mError; + } + mDataEnd = ((const uint8_t*)mHeader) + mSize; + + mStrings.uninit(); + mRootNode = NULL; + mResIds = NULL; + mNumResIds = 0; + + // First look for a couple interesting chunks: the string block + // and first XML node. + const ResChunk_header* chunk = + (const ResChunk_header*)(((const uint8_t*)mHeader) + dtohs(mHeader->header.headerSize)); + const ResChunk_header* lastChunk = chunk; + while (((const uint8_t*)chunk) < (mDataEnd-sizeof(ResChunk_header)) && + ((const uint8_t*)chunk) < (mDataEnd-dtohl(chunk->size))) { + status_t err = validate_chunk(chunk, sizeof(ResChunk_header), mDataEnd, "XML"); + if (err != NO_ERROR) { + mError = err; + goto done; + } + const uint16_t type = dtohs(chunk->type); + const size_t size = dtohl(chunk->size); + XML_NOISY(printf("Scanning @ %p: type=0x%x, size=0x%x\n", + (void*)(((uint32_t)chunk)-((uint32_t)mHeader)), type, size)); + if (type == RES_STRING_POOL_TYPE) { + mStrings.setTo(chunk, size); + } else if (type == RES_XML_RESOURCE_MAP_TYPE) { + mResIds = (const uint32_t*) + (((const uint8_t*)chunk)+dtohs(chunk->headerSize)); + mNumResIds = (dtohl(chunk->size)-dtohs(chunk->headerSize))/sizeof(uint32_t); + } else if (type >= RES_XML_FIRST_CHUNK_TYPE + && type <= RES_XML_LAST_CHUNK_TYPE) { + if (validateNode((const ResXMLTree_node*)chunk) != NO_ERROR) { + mError = BAD_TYPE; + goto done; + } + mCurNode = (const ResXMLTree_node*)lastChunk; + if (nextNode() == BAD_DOCUMENT) { + mError = BAD_TYPE; + goto done; + } + mRootNode = mCurNode; + mRootExt = mCurExt; + mRootCode = mEventCode; + break; + } else { + XML_NOISY(printf("Skipping unknown chunk!\n")); + } + lastChunk = chunk; + chunk = (const ResChunk_header*) + (((const uint8_t*)chunk) + size); + } + + if (mRootNode == NULL) { + LOGW("Bad XML block: no root element node found\n"); + mError = BAD_TYPE; + goto done; + } + + mError = mStrings.getError(); + +done: + restart(); + return mError; +} + +status_t ResXMLTree::getError() const +{ + return mError; +} + +void ResXMLTree::uninit() +{ + mError = NO_INIT; + if (mOwnedData) { + free(mOwnedData); + mOwnedData = NULL; + } + restart(); +} + +const ResStringPool& ResXMLTree::getStrings() const +{ + return mStrings; +} + +status_t ResXMLTree::validateNode(const ResXMLTree_node* node) const +{ + const uint16_t eventCode = dtohs(node->header.type); + + status_t err = validate_chunk( + &node->header, sizeof(ResXMLTree_node), + mDataEnd, "ResXMLTree_node"); + + if (err >= NO_ERROR) { + // Only perform additional validation on START nodes + if (eventCode != RES_XML_START_ELEMENT_TYPE) { + return NO_ERROR; + } + + const uint16_t headerSize = dtohs(node->header.headerSize); + const uint32_t size = dtohl(node->header.size); + const ResXMLTree_attrExt* attrExt = (const ResXMLTree_attrExt*) + (((const uint8_t*)node) + headerSize); + // check for sensical values pulled out of the stream so far... + if ((size >= headerSize + sizeof(ResXMLTree_attrExt)) + && ((void*)attrExt > (void*)node)) { + const size_t attrSize = ((size_t)dtohs(attrExt->attributeSize)) + * dtohs(attrExt->attributeCount); + if ((dtohs(attrExt->attributeStart)+attrSize) <= (size-headerSize)) { + return NO_ERROR; + } + LOGW("Bad XML block: node attributes use 0x%x bytes, only have 0x%x bytes\n", + (unsigned int)(dtohs(attrExt->attributeStart)+attrSize), + (unsigned int)(size-headerSize)); + } + else { + LOGW("Bad XML start block: node header size 0x%x, size 0x%x\n", + (unsigned int)headerSize, (unsigned int)size); + } + return BAD_TYPE; + } + + return err; + +#if 0 + const bool isStart = dtohs(node->header.type) == RES_XML_START_ELEMENT_TYPE; + + const uint16_t headerSize = dtohs(node->header.headerSize); + const uint32_t size = dtohl(node->header.size); + + if (headerSize >= (isStart ? sizeof(ResXMLTree_attrNode) : sizeof(ResXMLTree_node))) { + if (size >= headerSize) { + if (((const uint8_t*)node) <= (mDataEnd-size)) { + if (!isStart) { + return NO_ERROR; + } + if ((((size_t)dtohs(node->attributeSize))*dtohs(node->attributeCount)) + <= (size-headerSize)) { + return NO_ERROR; + } + LOGW("Bad XML block: node attributes use 0x%x bytes, only have 0x%x bytes\n", + ((int)dtohs(node->attributeSize))*dtohs(node->attributeCount), + (int)(size-headerSize)); + return BAD_TYPE; + } + LOGW("Bad XML block: node at 0x%x extends beyond data end 0x%x\n", + (int)(((const uint8_t*)node)-((const uint8_t*)mHeader)), (int)mSize); + return BAD_TYPE; + } + LOGW("Bad XML block: node at 0x%x header size 0x%x smaller than total size 0x%x\n", + (int)(((const uint8_t*)node)-((const uint8_t*)mHeader)), + (int)headerSize, (int)size); + return BAD_TYPE; + } + LOGW("Bad XML block: node at 0x%x header size 0x%x too small\n", + (int)(((const uint8_t*)node)-((const uint8_t*)mHeader)), + (int)headerSize); + return BAD_TYPE; +#endif +} + +// -------------------------------------------------------------------- +// -------------------------------------------------------------------- +// -------------------------------------------------------------------- + +struct ResTable::Header +{ + Header() : ownedData(NULL), header(NULL) { } + + void* ownedData; + const ResTable_header* header; + size_t size; + const uint8_t* dataEnd; + size_t index; + void* cookie; + + ResStringPool values; +}; + +struct ResTable::Type +{ + Type(const Header* _header, const Package* _package, size_t count) + : header(_header), package(_package), entryCount(count), + typeSpec(NULL), typeSpecFlags(NULL) { } + const Header* const header; + const Package* const package; + const size_t entryCount; + const ResTable_typeSpec* typeSpec; + const uint32_t* typeSpecFlags; + Vector<const ResTable_type*> configs; +}; + +struct ResTable::Package +{ + Package(const Header* _header, const ResTable_package* _package) + : header(_header), package(_package) { } + ~Package() + { + size_t i = types.size(); + while (i > 0) { + i--; + delete types[i]; + } + } + + const Header* const header; + const ResTable_package* const package; + Vector<Type*> types; + + const Type* getType(size_t idx) const { + return idx < types.size() ? types[idx] : NULL; + } +}; + +// A group of objects describing a particular resource package. +// The first in 'package' is always the root object (from the resource +// table that defined the package); the ones after are skins on top of it. +struct ResTable::PackageGroup +{ + PackageGroup(const String16& _name, uint32_t _id) + : name(_name), id(_id), typeCount(0), bags(NULL) { } + ~PackageGroup() { + clearBagCache(); + const size_t N = packages.size(); + for (size_t i=0; i<N; i++) { + delete packages[i]; + } + } + + void clearBagCache() { + if (bags) { + TABLE_NOISY(printf("bags=%p\n", bags)); + Package* pkg = packages[0]; + TABLE_NOISY(printf("typeCount=%x\n", typeCount)); + for (size_t i=0; i<typeCount; i++) { + TABLE_NOISY(printf("type=%d\n", i)); + const Type* type = pkg->getType(i); + if (type != NULL) { + bag_set** typeBags = bags[i]; + TABLE_NOISY(printf("typeBags=%p\n", typeBags)); + if (typeBags) { + TABLE_NOISY(printf("type->entryCount=%x\n", type->entryCount)); + const size_t N = type->entryCount; + for (size_t j=0; j<N; j++) { + if (typeBags[j] && typeBags[j] != (bag_set*)0xFFFFFFFF) + free(typeBags[j]); + } + free(typeBags); + } + } + } + free(bags); + bags = NULL; + } + } + + String16 const name; + uint32_t const id; + Vector<Package*> packages; + + // Taken from the root package. + ResStringPool typeStrings; + ResStringPool keyStrings; + size_t typeCount; + + // Computed attribute bags, first indexed by the type and second + // by the entry in that type. + bag_set*** bags; +}; + +struct ResTable::bag_set +{ + size_t numAttrs; // number in array + size_t availAttrs; // total space in array + uint32_t typeSpecFlags; + // Followed by 'numAttr' bag_entry structures. +}; + +ResTable::Theme::Theme(const ResTable& table) + : mTable(table) +{ + memset(mPackages, 0, sizeof(mPackages)); +} + +ResTable::Theme::~Theme() +{ + for (size_t i=0; i<Res_MAXPACKAGE; i++) { + package_info* pi = mPackages[i]; + if (pi != NULL) { + free_package(pi); + } + } +} + +void ResTable::Theme::free_package(package_info* pi) +{ + for (size_t j=0; j<pi->numTypes; j++) { + theme_entry* te = pi->types[j].entries; + if (te != NULL) { + free(te); + } + } + free(pi); +} + +ResTable::Theme::package_info* ResTable::Theme::copy_package(package_info* pi) +{ + package_info* newpi = (package_info*)malloc( + sizeof(package_info) + (pi->numTypes*sizeof(type_info))); + newpi->numTypes = pi->numTypes; + for (size_t j=0; j<newpi->numTypes; j++) { + size_t cnt = pi->types[j].numEntries; + newpi->types[j].numEntries = cnt; + theme_entry* te = pi->types[j].entries; + if (te != NULL) { + theme_entry* newte = (theme_entry*)malloc(cnt*sizeof(theme_entry)); + newpi->types[j].entries = newte; + memcpy(newte, te, cnt*sizeof(theme_entry)); + } else { + newpi->types[j].entries = NULL; + } + } + return newpi; +} + +status_t ResTable::Theme::applyStyle(uint32_t resID, bool force) +{ + const bag_entry* bag; + uint32_t bagTypeSpecFlags = 0; + mTable.lock(); + const ssize_t N = mTable.getBagLocked(resID, &bag, &bagTypeSpecFlags); + TABLE_NOISY(LOGV("Applying style 0x%08x to theme %p, count=%d", resID, this, N)); + if (N < 0) { + mTable.unlock(); + return N; + } + + uint32_t curPackage = 0xffffffff; + ssize_t curPackageIndex = 0; + package_info* curPI = NULL; + uint32_t curType = 0xffffffff; + size_t numEntries = 0; + theme_entry* curEntries = NULL; + + const bag_entry* end = bag + N; + while (bag < end) { + const uint32_t attrRes = bag->map.name.ident; + const uint32_t p = Res_GETPACKAGE(attrRes); + const uint32_t t = Res_GETTYPE(attrRes); + const uint32_t e = Res_GETENTRY(attrRes); + + if (curPackage != p) { + const ssize_t pidx = mTable.getResourcePackageIndex(attrRes); + if (pidx < 0) { + LOGE("Style contains key with bad package: 0x%08x\n", attrRes); + bag++; + continue; + } + curPackage = p; + curPackageIndex = pidx; + curPI = mPackages[pidx]; + if (curPI == NULL) { + PackageGroup* const grp = mTable.mPackageGroups[pidx]; + int cnt = grp->typeCount; + curPI = (package_info*)malloc( + sizeof(package_info) + (cnt*sizeof(type_info))); + curPI->numTypes = cnt; + memset(curPI->types, 0, cnt*sizeof(type_info)); + mPackages[pidx] = curPI; + } + curType = 0xffffffff; + } + if (curType != t) { + if (t >= curPI->numTypes) { + LOGE("Style contains key with bad type: 0x%08x\n", attrRes); + bag++; + continue; + } + curType = t; + curEntries = curPI->types[t].entries; + if (curEntries == NULL) { + PackageGroup* const grp = mTable.mPackageGroups[curPackageIndex]; + const Type* type = grp->packages[0]->getType(t); + int cnt = type != NULL ? type->entryCount : 0; + curEntries = (theme_entry*)malloc(cnt*sizeof(theme_entry)); + memset(curEntries, Res_value::TYPE_NULL, cnt*sizeof(theme_entry)); + curPI->types[t].numEntries = cnt; + curPI->types[t].entries = curEntries; + } + numEntries = curPI->types[t].numEntries; + } + if (e >= numEntries) { + LOGE("Style contains key with bad entry: 0x%08x\n", attrRes); + bag++; + continue; + } + theme_entry* curEntry = curEntries + e; + TABLE_NOISY(LOGV("Attr 0x%08x: type=0x%x, data=0x%08x; curType=0x%x", + attrRes, bag->map.value.dataType, bag->map.value.data, + curEntry->value.dataType)); + if (force || curEntry->value.dataType == Res_value::TYPE_NULL) { + curEntry->stringBlock = bag->stringBlock; + curEntry->typeSpecFlags |= bagTypeSpecFlags; + curEntry->value = bag->map.value; + } + + bag++; + } + + mTable.unlock(); + + //LOGI("Applying style 0x%08x (force=%d) theme %p...\n", resID, force, this); + //dumpToLog(); + + return NO_ERROR; +} + +status_t ResTable::Theme::setTo(const Theme& other) +{ + //LOGI("Setting theme %p from theme %p...\n", this, &other); + //dumpToLog(); + //other.dumpToLog(); + + if (&mTable == &other.mTable) { + for (size_t i=0; i<Res_MAXPACKAGE; i++) { + if (mPackages[i] != NULL) { + free_package(mPackages[i]); + } + if (other.mPackages[i] != NULL) { + mPackages[i] = copy_package(other.mPackages[i]); + } else { + mPackages[i] = NULL; + } + } + } else { + // @todo: need to really implement this, not just copy + // the system package (which is still wrong because it isn't + // fixing up resource references). + for (size_t i=0; i<Res_MAXPACKAGE; i++) { + if (mPackages[i] != NULL) { + free_package(mPackages[i]); + } + if (i == 0 && other.mPackages[i] != NULL) { + mPackages[i] = copy_package(other.mPackages[i]); + } else { + mPackages[i] = NULL; + } + } + } + + //LOGI("Final theme:"); + //dumpToLog(); + + return NO_ERROR; +} + +ssize_t ResTable::Theme::getAttribute(uint32_t resID, Res_value* outValue, + uint32_t* outTypeSpecFlags) const +{ + int cnt = 20; + + if (outTypeSpecFlags != NULL) *outTypeSpecFlags = 0; + + do { + const ssize_t p = mTable.getResourcePackageIndex(resID); + const uint32_t t = Res_GETTYPE(resID); + const uint32_t e = Res_GETENTRY(resID); + + TABLE_NOISY(LOGV("Looking up attr 0x%08x in theme %p", resID, this)); + + if (p >= 0) { + const package_info* const pi = mPackages[p]; + if (pi != NULL) { + if (t < pi->numTypes) { + const type_info& ti = pi->types[t]; + if (e < ti.numEntries) { + const theme_entry& te = ti.entries[e]; + if (outTypeSpecFlags != NULL) { + *outTypeSpecFlags |= te.typeSpecFlags; + } + const uint8_t type = te.value.dataType; + if (type == Res_value::TYPE_ATTRIBUTE) { + if (cnt > 0) { + cnt--; + resID = te.value.data; + continue; + } + LOGW("Too many attribute references, stopped at: 0x%08x\n", resID); + return BAD_INDEX; + } else if (type != Res_value::TYPE_NULL) { + *outValue = te.value; + return te.stringBlock; + } + return BAD_INDEX; + } + } + } + } + break; + + } while (true); + + return BAD_INDEX; +} + +ssize_t ResTable::Theme::resolveAttributeReference(Res_value* inOutValue, + ssize_t blockIndex, uint32_t* outLastRef, + uint32_t* inoutTypeSpecFlags) const +{ + //printf("Resolving type=0x%x\n", inOutValue->dataType); + if (inOutValue->dataType == Res_value::TYPE_ATTRIBUTE) { + uint32_t newTypeSpecFlags; + blockIndex = getAttribute(inOutValue->data, inOutValue, &newTypeSpecFlags); + if (inoutTypeSpecFlags != NULL) *inoutTypeSpecFlags |= newTypeSpecFlags; + //printf("Retrieved attribute new type=0x%x\n", inOutValue->dataType); + if (blockIndex < 0) { + return blockIndex; + } + } + return mTable.resolveReference(inOutValue, blockIndex, outLastRef); +} + +void ResTable::Theme::dumpToLog() const +{ + LOGI("Theme %p:\n", this); + for (size_t i=0; i<Res_MAXPACKAGE; i++) { + package_info* pi = mPackages[i]; + if (pi == NULL) continue; + + LOGI(" Package #0x%02x:\n", (int)(i+1)); + for (size_t j=0; j<pi->numTypes; j++) { + type_info& ti = pi->types[j]; + if (ti.numEntries == 0) continue; + + LOGI(" Type #0x%02x:\n", (int)(j+1)); + for (size_t k=0; k<ti.numEntries; k++) { + theme_entry& te = ti.entries[k]; + if (te.value.dataType == Res_value::TYPE_NULL) continue; + LOGI(" 0x%08x: t=0x%x, d=0x%08x (block=%d)\n", + (int)Res_MAKEID(i, j, k), + te.value.dataType, (int)te.value.data, (int)te.stringBlock); + } + } + } +} + +ResTable::ResTable() + : mError(NO_INIT) +{ + memset(&mParams, 0, sizeof(mParams)); + memset(mPackageMap, 0, sizeof(mPackageMap)); + //LOGI("Creating ResTable %p\n", this); +} + +ResTable::ResTable(const void* data, size_t size, void* cookie, bool copyData) + : mError(NO_INIT) +{ + memset(&mParams, 0, sizeof(mParams)); + memset(mPackageMap, 0, sizeof(mPackageMap)); + add(data, size, cookie, copyData); + LOG_FATAL_IF(mError != NO_ERROR, "Error parsing resource table"); + //LOGI("Creating ResTable %p\n", this); +} + +ResTable::~ResTable() +{ + //LOGI("Destroying ResTable in %p\n", this); + uninit(); +} + +inline ssize_t ResTable::getResourcePackageIndex(uint32_t resID) const +{ + return ((ssize_t)mPackageMap[Res_GETPACKAGE(resID)+1])-1; +} + +status_t ResTable::add(const void* data, size_t size, void* cookie, bool copyData) +{ + return add(data, size, cookie, NULL, copyData); +} + +status_t ResTable::add(Asset* asset, void* cookie, bool copyData) +{ + const void* data = asset->getBuffer(true); + if (data == NULL) { + LOGW("Unable to get buffer of resource asset file"); + return UNKNOWN_ERROR; + } + size_t size = (size_t)asset->getLength(); + return add(data, size, cookie, asset, copyData); +} + +status_t ResTable::add(const void* data, size_t size, void* cookie, + Asset* asset, bool copyData) +{ + if (!data) return NO_ERROR; + Header* header = new Header; + header->index = mHeaders.size(); + header->cookie = cookie; + mHeaders.add(header); + + const bool notDeviceEndian = htods(0xf0) != 0xf0; + + LOAD_TABLE_NOISY( + LOGV("Adding resources to ResTable: data=%p, size=0x%x, cookie=%p, asset=%p, copy=%d\n", + data, size, cookie, asset, copyData)); + + if (copyData || notDeviceEndian) { + header->ownedData = malloc(size); + if (header->ownedData == NULL) { + return (mError=NO_MEMORY); + } + memcpy(header->ownedData, data, size); + data = header->ownedData; + } + + header->header = (const ResTable_header*)data; + header->size = dtohl(header->header->header.size); + //LOGI("Got size 0x%x, again size 0x%x, raw size 0x%x\n", header->size, + // dtohl(header->header->header.size), header->header->header.size); + LOAD_TABLE_NOISY(LOGV("Loading ResTable @%p:\n", header->header)); + LOAD_TABLE_NOISY(printHexData(2, header->header, header->size < 256 ? header->size : 256, + 16, 16, 0, false, printToLogFunc)); + if (dtohs(header->header->header.headerSize) > header->size + || header->size > size) { + LOGW("Bad resource table: header size 0x%x or total size 0x%x is larger than data size 0x%x\n", + (int)dtohs(header->header->header.headerSize), + (int)header->size, (int)size); + return (mError=BAD_TYPE); + } + if (((dtohs(header->header->header.headerSize)|header->size)&0x3) != 0) { + LOGW("Bad resource table: header size 0x%x or total size 0x%x is not on an integer boundary\n", + (int)dtohs(header->header->header.headerSize), + (int)header->size); + return (mError=BAD_TYPE); + } + header->dataEnd = ((const uint8_t*)header->header) + header->size; + + // Iterate through all chunks. + size_t curPackage = 0; + + const ResChunk_header* chunk = + (const ResChunk_header*)(((const uint8_t*)header->header) + + dtohs(header->header->header.headerSize)); + while (((const uint8_t*)chunk) <= (header->dataEnd-sizeof(ResChunk_header)) && + ((const uint8_t*)chunk) <= (header->dataEnd-dtohl(chunk->size))) { + status_t err = validate_chunk(chunk, sizeof(ResChunk_header), header->dataEnd, "ResTable"); + if (err != NO_ERROR) { + return (mError=err); + } + TABLE_NOISY(LOGV("Chunk: type=0x%x, headerSize=0x%x, size=0x%x, pos=%p\n", + dtohs(chunk->type), dtohs(chunk->headerSize), dtohl(chunk->size), + (void*)(((const uint8_t*)chunk) - ((const uint8_t*)header->header)))); + const size_t csize = dtohl(chunk->size); + const uint16_t ctype = dtohs(chunk->type); + if (ctype == RES_STRING_POOL_TYPE) { + if (header->values.getError() != NO_ERROR) { + // Only use the first string chunk; ignore any others that + // may appear. + status_t err = header->values.setTo(chunk, csize); + if (err != NO_ERROR) { + return (mError=err); + } + } else { + LOGW("Multiple string chunks found in resource table."); + } + } else if (ctype == RES_TABLE_PACKAGE_TYPE) { + if (curPackage >= dtohl(header->header->packageCount)) { + LOGW("More package chunks were found than the %d declared in the header.", + dtohl(header->header->packageCount)); + return (mError=BAD_TYPE); + } + if (parsePackage((ResTable_package*)chunk, header) != NO_ERROR) { + return mError; + } + curPackage++; + } else { + LOGW("Unknown chunk type %p in table at %p.\n", + (void*)(int)(ctype), + (void*)(((const uint8_t*)chunk) - ((const uint8_t*)header->header))); + } + chunk = (const ResChunk_header*) + (((const uint8_t*)chunk) + csize); + } + + if (curPackage < dtohl(header->header->packageCount)) { + LOGW("Fewer package chunks (%d) were found than the %d declared in the header.", + (int)curPackage, dtohl(header->header->packageCount)); + return (mError=BAD_TYPE); + } + mError = header->values.getError(); + if (mError != NO_ERROR) { + LOGW("No string values found in resource table!"); + } + TABLE_NOISY(LOGV("Returning from add with mError=%d\n", mError)); + return mError; +} + +status_t ResTable::getError() const +{ + return mError; +} + +void ResTable::uninit() +{ + mError = NO_INIT; + size_t N = mPackageGroups.size(); + for (size_t i=0; i<N; i++) { + PackageGroup* g = mPackageGroups[i]; + delete g; + } + N = mHeaders.size(); + for (size_t i=0; i<N; i++) { + Header* header = mHeaders[i]; + if (header->ownedData) { + free(header->ownedData); + } + delete header; + } + + mPackageGroups.clear(); + mHeaders.clear(); +} + +bool ResTable::getResourceName(uint32_t resID, resource_name* outName) const +{ + if (mError != NO_ERROR) { + return false; + } + + const ssize_t p = getResourcePackageIndex(resID); + const int t = Res_GETTYPE(resID); + const int e = Res_GETENTRY(resID); + + if (p < 0) { + LOGW("No package identifier when getting name for resource number 0x%08x", resID); + return false; + } + if (t < 0) { + LOGW("No type identifier when getting name for resource number 0x%08x", resID); + return false; + } + + const PackageGroup* const grp = mPackageGroups[p]; + if (grp == NULL) { + LOGW("Bad identifier when getting name for resource number 0x%08x", resID); + return false; + } + if (grp->packages.size() > 0) { + const Package* const package = grp->packages[0]; + + const ResTable_type* type; + const ResTable_entry* entry; + ssize_t offset = getEntry(package, t, e, NULL, &type, &entry, NULL); + if (offset <= 0) { + return false; + } + + outName->package = grp->name.string(); + outName->packageLen = grp->name.size(); + outName->type = grp->typeStrings.stringAt(t, &outName->typeLen); + outName->name = grp->keyStrings.stringAt( + dtohl(entry->key.index), &outName->nameLen); + return true; + } + + return false; +} + +ssize_t ResTable::getResource(uint32_t resID, Res_value* outValue, bool mayBeBag, + uint32_t* outSpecFlags) const +{ + if (mError != NO_ERROR) { + return mError; + } + + const ssize_t p = getResourcePackageIndex(resID); + const int t = Res_GETTYPE(resID); + const int e = Res_GETENTRY(resID); + + if (p < 0) { + LOGW("No package identifier when getting value for resource number 0x%08x", resID); + return BAD_INDEX; + } + if (t < 0) { + LOGW("No type identifier when getting value for resource number 0x%08x", resID); + return BAD_INDEX; + } + + const Res_value* bestValue = NULL; + const Package* bestPackage = NULL; + ResTable_config bestItem; + memset(&bestItem, 0, sizeof(bestItem)); // make the compiler shut up + + if (outSpecFlags != NULL) *outSpecFlags = 0; + + // Look through all resource packages, starting with the most + // recently added. + const PackageGroup* const grp = mPackageGroups[p]; + if (grp == NULL) { + LOGW("Bad identifier when getting value for resource number 0x%08x", resID); + return false; + } + size_t ip = grp->packages.size(); + while (ip > 0) { + ip--; + + const Package* const package = grp->packages[ip]; + + const ResTable_type* type; + const ResTable_entry* entry; + const Type* typeClass; + ssize_t offset = getEntry(package, t, e, &mParams, &type, &entry, &typeClass); + if (offset <= 0) { + if (offset < 0) { + LOGW("Failure getting entry for 0x%08x (t=%d e=%d) in package %d: 0x%08x\n", + resID, t, e, (int)ip, (int)offset); + return offset; + } + continue; + } + + if ((dtohs(entry->flags)&entry->FLAG_COMPLEX) != 0) { + if (!mayBeBag) { + LOGW("Requesting resource %p failed because it is complex\n", + (void*)resID); + } + continue; + } + + TABLE_NOISY(aout << "Resource type data: " + << HexDump(type, dtohl(type->header.size)) << endl); + + if ((size_t)offset > (dtohl(type->header.size)-sizeof(Res_value))) { + LOGW("ResTable_item at %d is beyond type chunk data %d", + (int)offset, dtohl(type->header.size)); + return BAD_TYPE; + } + + const Res_value* item = + (const Res_value*)(((const uint8_t*)type) + offset); + ResTable_config thisConfig; + thisConfig.copyFromDtoH(type->config); + + if (outSpecFlags != NULL) { + if (typeClass->typeSpecFlags != NULL) { + *outSpecFlags |= dtohl(typeClass->typeSpecFlags[e]); + } else { + *outSpecFlags = -1; + } + } + + if (bestPackage != NULL && bestItem.isBetterThan(thisConfig)) { + continue; + } + + bestItem = thisConfig; + bestValue = item; + bestPackage = package; + } + + TABLE_NOISY(printf("Found result: package %p\n", bestPackage)); + + if (bestValue) { + outValue->size = dtohs(bestValue->size); + outValue->res0 = bestValue->res0; + outValue->dataType = bestValue->dataType; + outValue->data = dtohl(bestValue->data); + TABLE_NOISY(size_t len; + printf("Found value: pkg=%d, type=%d, str=%s, int=%d\n", + bestPackage->header->index, + outValue->dataType, + outValue->dataType == bestValue->TYPE_STRING + ? String8(bestPackage->header->values.stringAt( + outValue->data, &len)).string() + : "", + outValue->data)); + return bestPackage->header->index; + } + + return BAD_INDEX; +} + +ssize_t ResTable::resolveReference(Res_value* value, ssize_t blockIndex, + uint32_t* outLastRef, uint32_t* inoutTypeSpecFlags) const +{ + int count=0; + while (blockIndex >= 0 && value->dataType == value->TYPE_REFERENCE + && value->data != 0 && count < 20) { + if (outLastRef) *outLastRef = value->data; + uint32_t lastRef = value->data; + uint32_t newFlags = 0; + const ssize_t newIndex = getResource(value->data, value, true, &newFlags); + //LOGI("Resolving reference d=%p: newIndex=%d, t=0x%02x, d=%p\n", + // (void*)lastRef, (int)newIndex, (int)value->dataType, (void*)value->data); + //printf("Getting reference 0x%08x: newIndex=%d\n", value->data, newIndex); + if (inoutTypeSpecFlags != NULL) *inoutTypeSpecFlags |= newFlags; + if (newIndex < 0) { + // This can fail if the resource being referenced is a style... + // in this case, just return the reference, and expect the + // caller to deal with. + return blockIndex; + } + blockIndex = newIndex; + count++; + } + return blockIndex; +} + +const char16_t* ResTable::valueToString( + const Res_value* value, size_t stringBlock, + char16_t tmpBuffer[TMP_BUFFER_SIZE], size_t* outLen) +{ + if (!value) { + return NULL; + } + if (value->dataType == value->TYPE_STRING) { + return getTableStringBlock(stringBlock)->stringAt(value->data, outLen); + } + // XXX do int to string conversions. + return NULL; +} + +ssize_t ResTable::lockBag(uint32_t resID, const bag_entry** outBag) const +{ + mLock.lock(); + ssize_t err = getBagLocked(resID, outBag); + if (err < NO_ERROR) { + //printf("*** get failed! unlocking\n"); + mLock.unlock(); + } + return err; +} + +void ResTable::unlockBag(const bag_entry* bag) const +{ + //printf("<<< unlockBag %p\n", this); + mLock.unlock(); +} + +void ResTable::lock() const +{ + mLock.lock(); +} + +void ResTable::unlock() const +{ + mLock.unlock(); +} + +ssize_t ResTable::getBagLocked(uint32_t resID, const bag_entry** outBag, + uint32_t* outTypeSpecFlags) const +{ + if (mError != NO_ERROR) { + return mError; + } + + const ssize_t p = getResourcePackageIndex(resID); + const int t = Res_GETTYPE(resID); + const int e = Res_GETENTRY(resID); + + if (p < 0) { + LOGW("Invalid package identifier when getting bag for resource number 0x%08x", resID); + return BAD_INDEX; + } + if (t < 0) { + LOGW("No type identifier when getting bag for resource number 0x%08x", resID); + return BAD_INDEX; + } + + //printf("Get bag: id=0x%08x, p=%d, t=%d\n", resID, p, t); + PackageGroup* const grp = mPackageGroups[p]; + if (grp == NULL) { + LOGW("Bad identifier when getting bag for resource number 0x%08x", resID); + return false; + } + + if (t >= (int)grp->typeCount) { + LOGW("Type identifier 0x%x is larger than type count 0x%x", + t+1, (int)grp->typeCount); + return BAD_INDEX; + } + + const Package* const basePackage = grp->packages[0]; + + const Type* const typeConfigs = basePackage->getType(t); + + const size_t NENTRY = typeConfigs->entryCount; + if (e >= (int)NENTRY) { + LOGW("Entry identifier 0x%x is larger than entry count 0x%x", + e, (int)typeConfigs->entryCount); + return BAD_INDEX; + } + + // First see if we've already computed this bag... + if (grp->bags) { + bag_set** typeSet = grp->bags[t]; + if (typeSet) { + bag_set* set = typeSet[e]; + if (set) { + if (set != (bag_set*)0xFFFFFFFF) { + if (outTypeSpecFlags != NULL) { + *outTypeSpecFlags = set->typeSpecFlags; + } + *outBag = (bag_entry*)(set+1); + //LOGI("Found existing bag for: %p\n", (void*)resID); + return set->numAttrs; + } + LOGW("Attempt to retrieve bag 0x%08x which is invalid or in a cycle.", + resID); + return BAD_INDEX; + } + } + } + + // Bag not found, we need to compute it! + if (!grp->bags) { + grp->bags = (bag_set***)malloc(sizeof(bag_set*)*grp->typeCount); + if (!grp->bags) return NO_MEMORY; + memset(grp->bags, 0, sizeof(bag_set*)*grp->typeCount); + } + + bag_set** typeSet = grp->bags[t]; + if (!typeSet) { + typeSet = (bag_set**)malloc(sizeof(bag_set*)*NENTRY); + if (!typeSet) return NO_MEMORY; + memset(typeSet, 0, sizeof(bag_set*)*NENTRY); + grp->bags[t] = typeSet; + } + + // Mark that we are currently working on this one. + typeSet[e] = (bag_set*)0xFFFFFFFF; + + // This is what we are building. + bag_set* set = NULL; + + TABLE_NOISY(LOGI("Building bag: %p\n", (void*)resID)); + + // Now collect all bag attributes from all packages. + size_t ip = grp->packages.size(); + while (ip > 0) { + ip--; + + const Package* const package = grp->packages[ip]; + + const ResTable_type* type; + const ResTable_entry* entry; + const Type* typeClass; + LOGV("Getting entry pkg=%p, t=%d, e=%d\n", package, t, e); + ssize_t offset = getEntry(package, t, e, &mParams, &type, &entry, &typeClass); + LOGV("Resulting offset=%d\n", offset); + if (offset <= 0) { + if (offset < 0) { + if (set) free(set); + return offset; + } + continue; + } + + if ((dtohs(entry->flags)&entry->FLAG_COMPLEX) == 0) { + LOGW("Skipping entry %p in package table %d because it is not complex!\n", + (void*)resID, (int)ip); + continue; + } + + const uint16_t entrySize = dtohs(entry->size); + const uint32_t parent = entrySize >= sizeof(ResTable_map_entry) + ? dtohl(((const ResTable_map_entry*)entry)->parent.ident) : 0; + const uint32_t count = entrySize >= sizeof(ResTable_map_entry) + ? dtohl(((const ResTable_map_entry*)entry)->count) : 0; + + size_t N = count; + + TABLE_NOISY(LOGI("Found map: size=%p parent=%p count=%d\n", + entrySize, parent, count)); + + if (set == NULL) { + // If this map inherits from another, we need to start + // with its parent's values. Otherwise start out empty. + TABLE_NOISY(printf("Creating new bag, entrySize=0x%08x, parent=0x%08x\n", + entrySize, parent)); + if (parent) { + const bag_entry* parentBag; + uint32_t parentTypeSpecFlags = 0; + const ssize_t NP = getBagLocked(parent, &parentBag, &parentTypeSpecFlags); + const size_t NT = ((NP >= 0) ? NP : 0) + N; + set = (bag_set*)malloc(sizeof(bag_set)+sizeof(bag_entry)*NT); + if (set == NULL) { + return NO_MEMORY; + } + if (NP > 0) { + memcpy(set+1, parentBag, NP*sizeof(bag_entry)); + set->numAttrs = NP; + TABLE_NOISY(LOGI("Initialized new bag with %d inherited attributes.\n", NP)); + } else { + TABLE_NOISY(LOGI("Initialized new bag with no inherited attributes.\n")); + set->numAttrs = 0; + } + set->availAttrs = NT; + set->typeSpecFlags = parentTypeSpecFlags; + } else { + set = (bag_set*)malloc(sizeof(bag_set)+sizeof(bag_entry)*N); + if (set == NULL) { + return NO_MEMORY; + } + set->numAttrs = 0; + set->availAttrs = N; + set->typeSpecFlags = 0; + } + } + + if (typeClass->typeSpecFlags != NULL) { + set->typeSpecFlags |= dtohl(typeClass->typeSpecFlags[e]); + } else { + set->typeSpecFlags = -1; + } + + // Now merge in the new attributes... + ssize_t curOff = offset; + const ResTable_map* map; + bag_entry* entries = (bag_entry*)(set+1); + size_t curEntry = 0; + uint32_t pos = 0; + TABLE_NOISY(LOGI("Starting with set %p, entries=%p, avail=%d\n", + set, entries, set->availAttrs)); + while (pos < count) { + TABLE_NOISY(printf("Now at %p\n", (void*)curOff)); + + if ((size_t)curOff > (dtohl(type->header.size)-sizeof(ResTable_map))) { + LOGW("ResTable_map at %d is beyond type chunk data %d", + (int)curOff, dtohl(type->header.size)); + return BAD_TYPE; + } + map = (const ResTable_map*)(((const uint8_t*)type) + curOff); + N++; + + const uint32_t newName = htodl(map->name.ident); + bool isInside; + uint32_t oldName = 0; + while ((isInside=(curEntry < set->numAttrs)) + && (oldName=entries[curEntry].map.name.ident) < newName) { + TABLE_NOISY(printf("#%d: Keeping existing attribute: 0x%08x\n", + curEntry, entries[curEntry].map.name.ident)); + curEntry++; + } + + if ((!isInside) || oldName != newName) { + // This is a new attribute... figure out what to do with it. + if (set->numAttrs >= set->availAttrs) { + // Need to alloc more memory... + const size_t newAvail = set->availAttrs+N; + set = (bag_set*)realloc(set, + sizeof(bag_set) + + sizeof(bag_entry)*newAvail); + if (set == NULL) { + return NO_MEMORY; + } + set->availAttrs = newAvail; + entries = (bag_entry*)(set+1); + TABLE_NOISY(printf("Reallocated set %p, entries=%p, avail=%d\n", + set, entries, set->availAttrs)); + } + if (isInside) { + // Going in the middle, need to make space. + memmove(entries+curEntry+1, entries+curEntry, + sizeof(bag_entry)*(set->numAttrs-curEntry)); + set->numAttrs++; + } + TABLE_NOISY(printf("#%d: Inserting new attribute: 0x%08x\n", + curEntry, newName)); + } else { + TABLE_NOISY(printf("#%d: Replacing existing attribute: 0x%08x\n", + curEntry, oldName)); + } + + bag_entry* cur = entries+curEntry; + + cur->stringBlock = package->header->index; + cur->map.name.ident = newName; + cur->map.value.copyFrom_dtoh(map->value); + TABLE_NOISY(printf("Setting entry #%d %p: block=%d, name=0x%08x, type=%d, data=0x%08x\n", + curEntry, cur, cur->stringBlock, cur->map.name.ident, + cur->map.value.dataType, cur->map.value.data)); + + // On to the next! + curEntry++; + pos++; + const size_t size = dtohs(map->value.size); + curOff += size + sizeof(*map)-sizeof(map->value); + }; + if (curEntry > set->numAttrs) { + set->numAttrs = curEntry; + } + } + + // And this is it... + typeSet[e] = set; + if (set) { + if (outTypeSpecFlags != NULL) { + *outTypeSpecFlags = set->typeSpecFlags; + } + *outBag = (bag_entry*)(set+1); + TABLE_NOISY(LOGI("Returning %d attrs\n", set->numAttrs)); + return set->numAttrs; + } + return BAD_INDEX; +} + +void ResTable::setParameters(const ResTable_config* params) +{ + mLock.lock(); + TABLE_GETENTRY(LOGI("Setting parameters: imsi:%d/%d lang:%c%c cnt:%c%c " + "orien:%d touch:%d density:%d key:%d inp:%d nav:%d w:%d h:%d\n", + params->mcc, params->mnc, + params->language[0] ? params->language[0] : '-', + params->language[1] ? params->language[1] : '-', + params->country[0] ? params->country[0] : '-', + params->country[1] ? params->country[1] : '-', + params->orientation, + params->touchscreen, + params->density, + params->keyboard, + params->inputFlags, + params->navigation, + params->screenWidth, + params->screenHeight)); + mParams = *params; + for (size_t i=0; i<mPackageGroups.size(); i++) { + TABLE_NOISY(LOGI("CLEARING BAGS FOR GROUP %d!", i)); + mPackageGroups[i]->clearBagCache(); + } + mLock.unlock(); +} + +void ResTable::getParameters(ResTable_config* params) const +{ + mLock.lock(); + *params = mParams; + mLock.unlock(); +} + +struct id_name_map { + uint32_t id; + size_t len; + char16_t name[6]; +}; + +const static id_name_map ID_NAMES[] = { + { ResTable_map::ATTR_TYPE, 5, { '^', 't', 'y', 'p', 'e' } }, + { ResTable_map::ATTR_L10N, 5, { '^', 'l', '1', '0', 'n' } }, + { ResTable_map::ATTR_MIN, 4, { '^', 'm', 'i', 'n' } }, + { ResTable_map::ATTR_MAX, 4, { '^', 'm', 'a', 'x' } }, + { ResTable_map::ATTR_OTHER, 6, { '^', 'o', 't', 'h', 'e', 'r' } }, + { ResTable_map::ATTR_ZERO, 5, { '^', 'z', 'e', 'r', 'o' } }, + { ResTable_map::ATTR_ONE, 4, { '^', 'o', 'n', 'e' } }, + { ResTable_map::ATTR_TWO, 4, { '^', 't', 'w', 'o' } }, + { ResTable_map::ATTR_FEW, 4, { '^', 'f', 'e', 'w' } }, + { ResTable_map::ATTR_MANY, 5, { '^', 'm', 'a', 'n', 'y' } }, +}; + +uint32_t ResTable::identifierForName(const char16_t* name, size_t nameLen, + const char16_t* type, size_t typeLen, + const char16_t* package, + size_t packageLen, + uint32_t* outTypeSpecFlags) const +{ + TABLE_SUPER_NOISY(printf("Identifier for name: error=%d\n", mError)); + + // Check for internal resource identifier as the very first thing, so + // that we will always find them even when there are no resources. + if (name[0] == '^') { + const int N = (sizeof(ID_NAMES)/sizeof(ID_NAMES[0])); + size_t len; + for (int i=0; i<N; i++) { + const id_name_map* m = ID_NAMES + i; + len = m->len; + if (len != nameLen) { + continue; + } + for (size_t j=1; j<len; j++) { + if (m->name[j] != name[j]) { + goto nope; + } + } + return m->id; +nope: + ; + } + if (nameLen > 7) { + if (name[1] == 'i' && name[2] == 'n' + && name[3] == 'd' && name[4] == 'e' && name[5] == 'x' + && name[6] == '_') { + int index = atoi(String8(name + 7, nameLen - 7).string()); + if (Res_CHECKID(index)) { + LOGW("Array resource index: %d is too large.", + index); + return 0; + } + return Res_MAKEARRAY(index); + } + } + return 0; + } + + if (mError != NO_ERROR) { + return 0; + } + + // Figure out the package and type we are looking in... + + const char16_t* packageEnd = NULL; + const char16_t* typeEnd = NULL; + const char16_t* const nameEnd = name+nameLen; + const char16_t* p = name; + while (p < nameEnd) { + if (*p == ':') packageEnd = p; + else if (*p == '/') typeEnd = p; + p++; + } + if (*name == '@') name++; + if (name >= nameEnd) { + return 0; + } + + if (packageEnd) { + package = name; + packageLen = packageEnd-name; + name = packageEnd+1; + } else if (!package) { + return 0; + } + + if (typeEnd) { + type = name; + typeLen = typeEnd-name; + name = typeEnd+1; + } else if (!type) { + return 0; + } + + if (name >= nameEnd) { + return 0; + } + nameLen = nameEnd-name; + + TABLE_NOISY(printf("Looking for identifier: type=%s, name=%s, package=%s\n", + String8(type, typeLen).string(), + String8(name, nameLen).string(), + String8(package, packageLen).string())); + + const size_t NG = mPackageGroups.size(); + for (size_t ig=0; ig<NG; ig++) { + const PackageGroup* group = mPackageGroups[ig]; + + if (strzcmp16(package, packageLen, + group->name.string(), group->name.size())) { + TABLE_NOISY(printf("Skipping package group: %s\n", String8(group->name).string())); + continue; + } + + const ssize_t ti = group->typeStrings.indexOfString(type, typeLen); + if (ti < 0) { + TABLE_NOISY(printf("Type not found in package %s\n", String8(group->name).string())); + continue; + } + + const ssize_t ei = group->keyStrings.indexOfString(name, nameLen); + if (ei < 0) { + TABLE_NOISY(printf("Name not found in package %s\n", String8(group->name).string())); + continue; + } + + TABLE_NOISY(printf("Search indices: type=%d, name=%d\n", ti, ei)); + + const Type* const typeConfigs = group->packages[0]->getType(ti); + if (typeConfigs == NULL || typeConfigs->configs.size() <= 0) { + TABLE_NOISY(printf("Expected type structure not found in package %s for idnex %d\n", + String8(group->name).string(), ti)); + } + + size_t NTC = typeConfigs->configs.size(); + for (size_t tci=0; tci<NTC; tci++) { + const ResTable_type* const ty = typeConfigs->configs[tci]; + const uint32_t typeOffset = dtohl(ty->entriesStart); + + const uint8_t* const end = ((const uint8_t*)ty) + dtohl(ty->header.size); + const uint32_t* const eindex = (const uint32_t*) + (((const uint8_t*)ty) + dtohs(ty->header.headerSize)); + + const size_t NE = dtohl(ty->entryCount); + for (size_t i=0; i<NE; i++) { + uint32_t offset = dtohl(eindex[i]); + if (offset == ResTable_type::NO_ENTRY) { + continue; + } + + offset += typeOffset; + + if (offset > (dtohl(ty->header.size)-sizeof(ResTable_entry))) { + LOGW("ResTable_entry at %d is beyond type chunk data %d", + offset, dtohl(ty->header.size)); + return 0; + } + if ((offset&0x3) != 0) { + LOGW("ResTable_entry at %d (pkg=%d type=%d ent=%d) is not on an integer boundary when looking for %s:%s/%s", + (int)offset, (int)group->id, (int)ti+1, (int)i, + String8(package, packageLen).string(), + String8(type, typeLen).string(), + String8(name, nameLen).string()); + return 0; + } + + const ResTable_entry* const entry = (const ResTable_entry*) + (((const uint8_t*)ty) + offset); + if (dtohs(entry->size) < sizeof(*entry)) { + LOGW("ResTable_entry size %d is too small", dtohs(entry->size)); + return BAD_TYPE; + } + + TABLE_SUPER_NOISY(printf("Looking at entry #%d: want str %d, have %d\n", + i, ei, dtohl(entry->key.index))); + if (dtohl(entry->key.index) == (size_t)ei) { + if (outTypeSpecFlags) { + *outTypeSpecFlags = typeConfigs->typeSpecFlags[i]; + } + return Res_MAKEID(group->id-1, ti, i); + } + } + } + } + + return 0; +} + +bool ResTable::expandResourceRef(const uint16_t* refStr, size_t refLen, + String16* outPackage, + String16* outType, + String16* outName, + const String16* defType, + const String16* defPackage, + const char** outErrorMsg) +{ + const char16_t* packageEnd = NULL; + const char16_t* typeEnd = NULL; + const char16_t* p = refStr; + const char16_t* const end = p + refLen; + while (p < end) { + if (*p == ':') packageEnd = p; + else if (*p == '/') { + typeEnd = p; + break; + } + p++; + } + p = refStr; + if (*p == '@') p++; + + if (packageEnd) { + *outPackage = String16(p, packageEnd-p); + p = packageEnd+1; + } else { + if (!defPackage) { + if (outErrorMsg) { + *outErrorMsg = "No resource package specified"; + } + return false; + } + *outPackage = *defPackage; + } + if (typeEnd) { + *outType = String16(p, typeEnd-p); + p = typeEnd+1; + } else { + if (!defType) { + if (outErrorMsg) { + *outErrorMsg = "No resource type specified"; + } + return false; + } + *outType = *defType; + } + *outName = String16(p, end-p); + return true; +} + +static uint32_t get_hex(char c, bool* outError) +{ + if (c >= '0' && c <= '9') { + return c - '0'; + } else if (c >= 'a' && c <= 'f') { + return c - 'a' + 0xa; + } else if (c >= 'A' && c <= 'F') { + return c - 'A' + 0xa; + } + *outError = true; + return 0; +} + +struct unit_entry +{ + const char* name; + size_t len; + uint8_t type; + uint32_t unit; + float scale; +}; + +static const unit_entry unitNames[] = { + { "px", strlen("px"), Res_value::TYPE_DIMENSION, Res_value::COMPLEX_UNIT_PX, 1.0f }, + { "dip", strlen("dip"), Res_value::TYPE_DIMENSION, Res_value::COMPLEX_UNIT_DIP, 1.0f }, + { "dp", strlen("dp"), Res_value::TYPE_DIMENSION, Res_value::COMPLEX_UNIT_DIP, 1.0f }, + { "sp", strlen("sp"), Res_value::TYPE_DIMENSION, Res_value::COMPLEX_UNIT_SP, 1.0f }, + { "pt", strlen("pt"), Res_value::TYPE_DIMENSION, Res_value::COMPLEX_UNIT_PT, 1.0f }, + { "in", strlen("in"), Res_value::TYPE_DIMENSION, Res_value::COMPLEX_UNIT_IN, 1.0f }, + { "mm", strlen("mm"), Res_value::TYPE_DIMENSION, Res_value::COMPLEX_UNIT_MM, 1.0f }, + { "%", strlen("%"), Res_value::TYPE_FRACTION, Res_value::COMPLEX_UNIT_FRACTION, 1.0f/100 }, + { "%p", strlen("%p"), Res_value::TYPE_FRACTION, Res_value::COMPLEX_UNIT_FRACTION_PARENT, 1.0f/100 }, + { NULL, 0, 0, 0, 0 } +}; + +static bool parse_unit(const char* str, Res_value* outValue, + float* outScale, const char** outEnd) +{ + const char* end = str; + while (*end != 0 && !isspace((unsigned char)*end)) { + end++; + } + const size_t len = end-str; + + const char* realEnd = end; + while (*realEnd != 0 && isspace((unsigned char)*realEnd)) { + realEnd++; + } + if (*realEnd != 0) { + return false; + } + + const unit_entry* cur = unitNames; + while (cur->name) { + if (len == cur->len && strncmp(cur->name, str, len) == 0) { + outValue->dataType = cur->type; + outValue->data = cur->unit << Res_value::COMPLEX_UNIT_SHIFT; + *outScale = cur->scale; + *outEnd = end; + //printf("Found unit %s for %s\n", cur->name, str); + return true; + } + cur++; + } + + return false; +} + + +bool ResTable::stringToInt(const char16_t* s, size_t len, Res_value* outValue) +{ + while (len > 0 && isspace16(*s)) { + s++; + len--; + } + + if (len <= 0) { + return false; + } + + size_t i = 0; + int32_t val = 0; + bool neg = false; + + if (*s == '-') { + neg = true; + i++; + } + + if (s[i] < '0' || s[i] > '9') { + return false; + } + + // Decimal or hex? + if (s[i] == '0' && s[i+1] == 'x') { + if (outValue) + outValue->dataType = outValue->TYPE_INT_HEX; + i += 2; + bool error = false; + while (i < len && !error) { + val = (val*16) + get_hex(s[i], &error); + i++; + } + if (error) { + return false; + } + } else { + if (outValue) + outValue->dataType = outValue->TYPE_INT_DEC; + while (i < len) { + if (s[i] < '0' || s[i] > '9') { + return false; + } + val = (val*10) + s[i]-'0'; + i++; + } + } + + if (neg) val = -val; + + while (i < len && isspace16(s[i])) { + i++; + } + + if (i == len) { + if (outValue) + outValue->data = val; + return true; + } + + return false; +} + +bool ResTable::stringToFloat(const char16_t* s, size_t len, Res_value* outValue) +{ + while (len > 0 && isspace16(*s)) { + s++; + len--; + } + + if (len <= 0) { + return false; + } + + char buf[128]; + int i=0; + while (len > 0 && *s != 0 && i < 126) { + if (*s > 255) { + return false; + } + buf[i++] = *s++; + len--; + } + + if (len > 0) { + return false; + } + if (buf[0] < '0' && buf[0] > '9' && buf[0] != '.') { + return false; + } + + buf[i] = 0; + const char* end; + float f = strtof(buf, (char**)&end); + + if (*end != 0 && !isspace((unsigned char)*end)) { + // Might be a unit... + float scale; + if (parse_unit(end, outValue, &scale, &end)) { + f *= scale; + const bool neg = f < 0; + if (neg) f = -f; + uint64_t bits = (uint64_t)(f*(1<<23)+.5f); + uint32_t radix; + uint32_t shift; + if ((bits&0x7fffff) == 0) { + // Always use 23p0 if there is no fraction, just to make + // things easier to read. + radix = Res_value::COMPLEX_RADIX_23p0; + shift = 23; + } else if ((bits&0xffffffffff800000LL) == 0) { + // Magnitude is zero -- can fit in 0 bits of precision. + radix = Res_value::COMPLEX_RADIX_0p23; + shift = 0; + } else if ((bits&0xffffffff80000000LL) == 0) { + // Magnitude can fit in 8 bits of precision. + radix = Res_value::COMPLEX_RADIX_8p15; + shift = 8; + } else if ((bits&0xffffff8000000000LL) == 0) { + // Magnitude can fit in 16 bits of precision. + radix = Res_value::COMPLEX_RADIX_16p7; + shift = 16; + } else { + // Magnitude needs entire range, so no fractional part. + radix = Res_value::COMPLEX_RADIX_23p0; + shift = 23; + } + int32_t mantissa = (int32_t)( + (bits>>shift) & Res_value::COMPLEX_MANTISSA_MASK); + if (neg) { + mantissa = (-mantissa) & Res_value::COMPLEX_MANTISSA_MASK; + } + outValue->data |= + (radix<<Res_value::COMPLEX_RADIX_SHIFT) + | (mantissa<<Res_value::COMPLEX_MANTISSA_SHIFT); + //printf("Input value: %f 0x%016Lx, mult: %f, radix: %d, shift: %d, final: 0x%08x\n", + // f * (neg ? -1 : 1), bits, f*(1<<23), + // radix, shift, outValue->data); + return true; + } + return false; + } + + while (*end != 0 && isspace((unsigned char)*end)) { + end++; + } + + if (*end == 0) { + if (outValue) { + outValue->dataType = outValue->TYPE_FLOAT; + *(float*)(&outValue->data) = f; + return true; + } + } + + return false; +} + +bool ResTable::stringToValue(Res_value* outValue, String16* outString, + const char16_t* s, size_t len, + bool preserveSpaces, bool coerceType, + uint32_t attrID, + const String16* defType, + const String16* defPackage, + Accessor* accessor, + void* accessorCookie, + uint32_t attrType, + bool enforcePrivate) const +{ + bool localizationSetting = accessor != NULL && accessor->getLocalizationSetting(); + const char* errorMsg = NULL; + + outValue->size = sizeof(Res_value); + outValue->res0 = 0; + + // First strip leading/trailing whitespace. Do this before handling + // escapes, so they can be used to force whitespace into the string. + if (!preserveSpaces) { + while (len > 0 && isspace16(*s)) { + s++; + len--; + } + while (len > 0 && isspace16(s[len-1])) { + len--; + } + // If the string ends with '\', then we keep the space after it. + if (len > 0 && s[len-1] == '\\' && s[len] != 0) { + len++; + } + } + + //printf("Value for: %s\n", String8(s, len).string()); + + uint32_t l10nReq = ResTable_map::L10N_NOT_REQUIRED; + uint32_t attrMin = 0x80000000, attrMax = 0x7fffffff; + bool fromAccessor = false; + if (attrID != 0 && !Res_INTERNALID(attrID)) { + const ssize_t p = getResourcePackageIndex(attrID); + const bag_entry* bag; + ssize_t cnt = p >= 0 ? lockBag(attrID, &bag) : -1; + //printf("For attr 0x%08x got bag of %d\n", attrID, cnt); + if (cnt >= 0) { + while (cnt > 0) { + //printf("Entry 0x%08x = 0x%08x\n", bag->map.name.ident, bag->map.value.data); + switch (bag->map.name.ident) { + case ResTable_map::ATTR_TYPE: + attrType = bag->map.value.data; + break; + case ResTable_map::ATTR_MIN: + attrMin = bag->map.value.data; + break; + case ResTable_map::ATTR_MAX: + attrMax = bag->map.value.data; + break; + case ResTable_map::ATTR_L10N: + l10nReq = bag->map.value.data; + break; + } + bag++; + cnt--; + } + unlockBag(bag); + } else if (accessor && accessor->getAttributeType(attrID, &attrType)) { + fromAccessor = true; + if (attrType == ResTable_map::TYPE_ENUM + || attrType == ResTable_map::TYPE_FLAGS + || attrType == ResTable_map::TYPE_INTEGER) { + accessor->getAttributeMin(attrID, &attrMin); + accessor->getAttributeMax(attrID, &attrMax); + } + if (localizationSetting) { + l10nReq = accessor->getAttributeL10N(attrID); + } + } + } + + const bool canStringCoerce = + coerceType && (attrType&ResTable_map::TYPE_STRING) != 0; + + if (*s == '@') { + outValue->dataType = outValue->TYPE_REFERENCE; + + // Note: we don't check attrType here because the reference can + // be to any other type; we just need to count on the client making + // sure the referenced type is correct. + + //printf("Looking up ref: %s\n", String8(s, len).string()); + + // It's a reference! + if (len == 5 && s[1]=='n' && s[2]=='u' && s[3]=='l' && s[4]=='l') { + outValue->data = 0; + return true; + } else { + bool createIfNotFound = false; + const char16_t* resourceRefName; + int resourceNameLen; + if (len > 2 && s[1] == '+') { + createIfNotFound = true; + resourceRefName = s + 2; + resourceNameLen = len - 2; + } else if (len > 2 && s[1] == '*') { + enforcePrivate = false; + resourceRefName = s + 2; + resourceNameLen = len - 2; + } else { + createIfNotFound = false; + resourceRefName = s + 1; + resourceNameLen = len - 1; + } + String16 package, type, name; + if (!expandResourceRef(resourceRefName,resourceNameLen, &package, &type, &name, + defType, defPackage, &errorMsg)) { + if (accessor != NULL) { + accessor->reportError(accessorCookie, errorMsg); + } + return false; + } + + uint32_t specFlags = 0; + uint32_t rid = identifierForName(name.string(), name.size(), type.string(), + type.size(), package.string(), package.size(), &specFlags); + if (rid != 0) { + if (enforcePrivate) { + if ((specFlags&ResTable_typeSpec::SPEC_PUBLIC) == 0) { + if (accessor != NULL) { + accessor->reportError(accessorCookie, "Resource is not public."); + } + return false; + } + } + if (!accessor) { + outValue->data = rid; + return true; + } + rid = Res_MAKEID( + accessor->getRemappedPackage(Res_GETPACKAGE(rid)), + Res_GETTYPE(rid), Res_GETENTRY(rid)); + TABLE_NOISY(printf("Incl %s:%s/%s: 0x%08x\n", + String8(package).string(), String8(type).string(), + String8(name).string(), rid)); + outValue->data = rid; + return true; + } + + if (accessor) { + uint32_t rid = accessor->getCustomResourceWithCreation(package, type, name, + createIfNotFound); + if (rid != 0) { + TABLE_NOISY(printf("Pckg %s:%s/%s: 0x%08x\n", + String8(package).string(), String8(type).string(), + String8(name).string(), rid)); + outValue->data = rid; + return true; + } + } + } + + if (accessor != NULL) { + accessor->reportError(accessorCookie, "No resource found that matches the given name"); + } + return false; + } + + // if we got to here, and localization is required and it's not a reference, + // complain and bail. + if (l10nReq == ResTable_map::L10N_SUGGESTED) { + if (localizationSetting) { + if (accessor != NULL) { + accessor->reportError(accessorCookie, "This attribute must be localized."); + } + } + } + + if (*s == '#') { + // It's a color! Convert to an integer of the form 0xaarrggbb. + uint32_t color = 0; + bool error = false; + if (len == 4) { + outValue->dataType = outValue->TYPE_INT_COLOR_RGB4; + color |= 0xFF000000; + color |= get_hex(s[1], &error) << 20; + color |= get_hex(s[1], &error) << 16; + color |= get_hex(s[2], &error) << 12; + color |= get_hex(s[2], &error) << 8; + color |= get_hex(s[3], &error) << 4; + color |= get_hex(s[3], &error); + } else if (len == 5) { + outValue->dataType = outValue->TYPE_INT_COLOR_ARGB4; + color |= get_hex(s[1], &error) << 28; + color |= get_hex(s[1], &error) << 24; + color |= get_hex(s[2], &error) << 20; + color |= get_hex(s[2], &error) << 16; + color |= get_hex(s[3], &error) << 12; + color |= get_hex(s[3], &error) << 8; + color |= get_hex(s[4], &error) << 4; + color |= get_hex(s[4], &error); + } else if (len == 7) { + outValue->dataType = outValue->TYPE_INT_COLOR_RGB8; + color |= 0xFF000000; + color |= get_hex(s[1], &error) << 20; + color |= get_hex(s[2], &error) << 16; + color |= get_hex(s[3], &error) << 12; + color |= get_hex(s[4], &error) << 8; + color |= get_hex(s[5], &error) << 4; + color |= get_hex(s[6], &error); + } else if (len == 9) { + outValue->dataType = outValue->TYPE_INT_COLOR_ARGB8; + color |= get_hex(s[1], &error) << 28; + color |= get_hex(s[2], &error) << 24; + color |= get_hex(s[3], &error) << 20; + color |= get_hex(s[4], &error) << 16; + color |= get_hex(s[5], &error) << 12; + color |= get_hex(s[6], &error) << 8; + color |= get_hex(s[7], &error) << 4; + color |= get_hex(s[8], &error); + } else { + error = true; + } + if (!error) { + if ((attrType&ResTable_map::TYPE_COLOR) == 0) { + if (!canStringCoerce) { + if (accessor != NULL) { + accessor->reportError(accessorCookie, + "Color types not allowed"); + } + return false; + } + } else { + outValue->data = color; + //printf("Color input=%s, output=0x%x\n", String8(s, len).string(), color); + return true; + } + } else { + if ((attrType&ResTable_map::TYPE_COLOR) != 0) { + if (accessor != NULL) { + accessor->reportError(accessorCookie, "Color value not valid --" + " must be #rgb, #argb, #rrggbb, or #aarrggbb"); + } + #if 0 + fprintf(stderr, "%s: Color ID %s value %s is not valid\n", + "Resource File", //(const char*)in->getPrintableSource(), + String8(*curTag).string(), + String8(s, len).string()); + #endif + return false; + } + } + } + + if (*s == '?') { + outValue->dataType = outValue->TYPE_ATTRIBUTE; + + // Note: we don't check attrType here because the reference can + // be to any other type; we just need to count on the client making + // sure the referenced type is correct. + + //printf("Looking up attr: %s\n", String8(s, len).string()); + + static const String16 attr16("attr"); + String16 package, type, name; + if (!expandResourceRef(s+1, len-1, &package, &type, &name, + &attr16, defPackage, &errorMsg)) { + if (accessor != NULL) { + accessor->reportError(accessorCookie, errorMsg); + } + return false; + } + + //printf("Pkg: %s, Type: %s, Name: %s\n", + // String8(package).string(), String8(type).string(), + // String8(name).string()); + uint32_t specFlags = 0; + uint32_t rid = + identifierForName(name.string(), name.size(), + type.string(), type.size(), + package.string(), package.size(), &specFlags); + if (rid != 0) { + if (enforcePrivate) { + if ((specFlags&ResTable_typeSpec::SPEC_PUBLIC) == 0) { + if (accessor != NULL) { + accessor->reportError(accessorCookie, "Attribute is not public."); + } + return false; + } + } + if (!accessor) { + outValue->data = rid; + return true; + } + rid = Res_MAKEID( + accessor->getRemappedPackage(Res_GETPACKAGE(rid)), + Res_GETTYPE(rid), Res_GETENTRY(rid)); + //printf("Incl %s:%s/%s: 0x%08x\n", + // String8(package).string(), String8(type).string(), + // String8(name).string(), rid); + outValue->data = rid; + return true; + } + + if (accessor) { + uint32_t rid = accessor->getCustomResource(package, type, name); + if (rid != 0) { + //printf("Mine %s:%s/%s: 0x%08x\n", + // String8(package).string(), String8(type).string(), + // String8(name).string(), rid); + outValue->data = rid; + return true; + } + } + + if (accessor != NULL) { + accessor->reportError(accessorCookie, "No resource found that matches the given name"); + } + return false; + } + + if (stringToInt(s, len, outValue)) { + if ((attrType&ResTable_map::TYPE_INTEGER) == 0) { + // If this type does not allow integers, but does allow floats, + // fall through on this error case because the float type should + // be able to accept any integer value. + if (!canStringCoerce && (attrType&ResTable_map::TYPE_FLOAT) == 0) { + if (accessor != NULL) { + accessor->reportError(accessorCookie, "Integer types not allowed"); + } + return false; + } + } else { + if (((int32_t)outValue->data) < ((int32_t)attrMin) + || ((int32_t)outValue->data) > ((int32_t)attrMax)) { + if (accessor != NULL) { + accessor->reportError(accessorCookie, "Integer value out of range"); + } + return false; + } + return true; + } + } + + if (stringToFloat(s, len, outValue)) { + if (outValue->dataType == Res_value::TYPE_DIMENSION) { + if ((attrType&ResTable_map::TYPE_DIMENSION) != 0) { + return true; + } + if (!canStringCoerce) { + if (accessor != NULL) { + accessor->reportError(accessorCookie, "Dimension types not allowed"); + } + return false; + } + } else if (outValue->dataType == Res_value::TYPE_FRACTION) { + if ((attrType&ResTable_map::TYPE_FRACTION) != 0) { + return true; + } + if (!canStringCoerce) { + if (accessor != NULL) { + accessor->reportError(accessorCookie, "Fraction types not allowed"); + } + return false; + } + } else if ((attrType&ResTable_map::TYPE_FLOAT) == 0) { + if (!canStringCoerce) { + if (accessor != NULL) { + accessor->reportError(accessorCookie, "Float types not allowed"); + } + return false; + } + } else { + return true; + } + } + + if (len == 4) { + if ((s[0] == 't' || s[0] == 'T') && + (s[1] == 'r' || s[1] == 'R') && + (s[2] == 'u' || s[2] == 'U') && + (s[3] == 'e' || s[3] == 'E')) { + if ((attrType&ResTable_map::TYPE_BOOLEAN) == 0) { + if (!canStringCoerce) { + if (accessor != NULL) { + accessor->reportError(accessorCookie, "Boolean types not allowed"); + } + return false; + } + } else { + outValue->dataType = outValue->TYPE_INT_BOOLEAN; + outValue->data = (uint32_t)-1; + return true; + } + } + } + + if (len == 5) { + if ((s[0] == 'f' || s[0] == 'F') && + (s[1] == 'a' || s[1] == 'A') && + (s[2] == 'l' || s[2] == 'L') && + (s[3] == 's' || s[3] == 'S') && + (s[4] == 'e' || s[4] == 'E')) { + if ((attrType&ResTable_map::TYPE_BOOLEAN) == 0) { + if (!canStringCoerce) { + if (accessor != NULL) { + accessor->reportError(accessorCookie, "Boolean types not allowed"); + } + return false; + } + } else { + outValue->dataType = outValue->TYPE_INT_BOOLEAN; + outValue->data = 0; + return true; + } + } + } + + if ((attrType&ResTable_map::TYPE_ENUM) != 0) { + const ssize_t p = getResourcePackageIndex(attrID); + const bag_entry* bag; + ssize_t cnt = p >= 0 ? lockBag(attrID, &bag) : -1; + //printf("Got %d for enum\n", cnt); + if (cnt >= 0) { + resource_name rname; + while (cnt > 0) { + if (!Res_INTERNALID(bag->map.name.ident)) { + //printf("Trying attr #%08x\n", bag->map.name.ident); + if (getResourceName(bag->map.name.ident, &rname)) { + #if 0 + printf("Matching %s against %s (0x%08x)\n", + String8(s, len).string(), + String8(rname.name, rname.nameLen).string(), + bag->map.name.ident); + #endif + if (strzcmp16(s, len, rname.name, rname.nameLen) == 0) { + outValue->dataType = bag->map.value.dataType; + outValue->data = bag->map.value.data; + unlockBag(bag); + return true; + } + } + + } + bag++; + cnt--; + } + unlockBag(bag); + } + + if (fromAccessor) { + if (accessor->getAttributeEnum(attrID, s, len, outValue)) { + return true; + } + } + } + + if ((attrType&ResTable_map::TYPE_FLAGS) != 0) { + const ssize_t p = getResourcePackageIndex(attrID); + const bag_entry* bag; + ssize_t cnt = p >= 0 ? lockBag(attrID, &bag) : -1; + //printf("Got %d for flags\n", cnt); + if (cnt >= 0) { + bool failed = false; + resource_name rname; + outValue->dataType = Res_value::TYPE_INT_HEX; + outValue->data = 0; + const char16_t* end = s + len; + const char16_t* pos = s; + while (pos < end && !failed) { + const char16_t* start = pos; + end++; + while (pos < end && *pos != '|') { + pos++; + } + //printf("Looking for: %s\n", String8(start, pos-start).string()); + const bag_entry* bagi = bag; + ssize_t i; + for (i=0; i<cnt; i++, bagi++) { + if (!Res_INTERNALID(bagi->map.name.ident)) { + //printf("Trying attr #%08x\n", bagi->map.name.ident); + if (getResourceName(bagi->map.name.ident, &rname)) { + #if 0 + printf("Matching %s against %s (0x%08x)\n", + String8(start,pos-start).string(), + String8(rname.name, rname.nameLen).string(), + bagi->map.name.ident); + #endif + if (strzcmp16(start, pos-start, rname.name, rname.nameLen) == 0) { + outValue->data |= bagi->map.value.data; + break; + } + } + } + } + if (i >= cnt) { + // Didn't find this flag identifier. + failed = true; + } + if (pos < end) { + pos++; + } + } + unlockBag(bag); + if (!failed) { + //printf("Final flag value: 0x%lx\n", outValue->data); + return true; + } + } + + + if (fromAccessor) { + if (accessor->getAttributeFlags(attrID, s, len, outValue)) { + //printf("Final flag value: 0x%lx\n", outValue->data); + return true; + } + } + } + + if ((attrType&ResTable_map::TYPE_STRING) == 0) { + if (accessor != NULL) { + accessor->reportError(accessorCookie, "String types not allowed"); + } + return false; + } + + // Generic string handling... + outValue->dataType = outValue->TYPE_STRING; + if (outString) { + bool failed = collectString(outString, s, len, preserveSpaces, &errorMsg); + if (accessor != NULL) { + accessor->reportError(accessorCookie, errorMsg); + } + return failed; + } + + return true; +} + +bool ResTable::collectString(String16* outString, + const char16_t* s, size_t len, + bool preserveSpaces, + const char** outErrorMsg, + bool append) +{ + String16 tmp; + + char quoted = 0; + const char16_t* p = s; + while (p < (s+len)) { + while (p < (s+len)) { + const char16_t c = *p; + if (c == '\\') { + break; + } + if (!preserveSpaces) { + if (quoted == 0 && isspace16(c) + && (c != ' ' || isspace16(*(p+1)))) { + break; + } + if (c == '"' && (quoted == 0 || quoted == '"')) { + break; + } + if (c == '\'' && (quoted == 0 || quoted == '\'')) { + break; + } + } + p++; + } + if (p < (s+len)) { + if (p > s) { + tmp.append(String16(s, p-s)); + } + if (!preserveSpaces && (*p == '"' || *p == '\'')) { + if (quoted == 0) { + quoted = *p; + } else { + quoted = 0; + } + p++; + } else if (!preserveSpaces && isspace16(*p)) { + // Space outside of a quote -- consume all spaces and + // leave a single plain space char. + tmp.append(String16(" ")); + p++; + while (p < (s+len) && isspace16(*p)) { + p++; + } + } else if (*p == '\\') { + p++; + if (p < (s+len)) { + switch (*p) { + case 't': + tmp.append(String16("\t")); + break; + case 'n': + tmp.append(String16("\n")); + break; + case '#': + tmp.append(String16("#")); + break; + case '@': + tmp.append(String16("@")); + break; + case '?': + tmp.append(String16("?")); + break; + case '"': + tmp.append(String16("\"")); + break; + case '\'': + tmp.append(String16("'")); + break; + case '\\': + tmp.append(String16("\\")); + break; + case 'u': + { + char16_t chr = 0; + int i = 0; + while (i < 4 && p[1] != 0) { + p++; + i++; + int c; + if (*p >= '0' && *p <= '9') { + c = *p - '0'; + } else if (*p >= 'a' && *p <= 'f') { + c = *p - 'a' + 10; + } else if (*p >= 'A' && *p <= 'F') { + c = *p - 'A' + 10; + } else { + if (outErrorMsg) { + *outErrorMsg = "Bad character in \\u unicode escape sequence"; + } + return false; + } + chr = (chr<<4) | c; + } + tmp.append(String16(&chr, 1)); + } break; + default: + // ignore unknown escape chars. + break; + } + p++; + } + } + len -= (p-s); + s = p; + } + } + + if (tmp.size() != 0) { + if (len > 0) { + tmp.append(String16(s, len)); + } + if (append) { + outString->append(tmp); + } else { + outString->setTo(tmp); + } + } else { + if (append) { + outString->append(String16(s, len)); + } else { + outString->setTo(s, len); + } + } + + return true; +} + +size_t ResTable::getBasePackageCount() const +{ + if (mError != NO_ERROR) { + return 0; + } + return mPackageGroups.size(); +} + +const char16_t* ResTable::getBasePackageName(size_t idx) const +{ + if (mError != NO_ERROR) { + return 0; + } + LOG_FATAL_IF(idx >= mPackageGroups.size(), + "Requested package index %d past package count %d", + (int)idx, (int)mPackageGroups.size()); + return mPackageGroups[idx]->name.string(); +} + +uint32_t ResTable::getBasePackageId(size_t idx) const +{ + if (mError != NO_ERROR) { + return 0; + } + LOG_FATAL_IF(idx >= mPackageGroups.size(), + "Requested package index %d past package count %d", + (int)idx, (int)mPackageGroups.size()); + return mPackageGroups[idx]->id; +} + +size_t ResTable::getTableCount() const +{ + return mHeaders.size(); +} + +const ResStringPool* ResTable::getTableStringBlock(size_t index) const +{ + return &mHeaders[index]->values; +} + +void* ResTable::getTableCookie(size_t index) const +{ + return mHeaders[index]->cookie; +} + +void ResTable::getConfigurations(Vector<ResTable_config>* configs) const +{ + const size_t I = mPackageGroups.size(); + for (size_t i=0; i<I; i++) { + const PackageGroup* packageGroup = mPackageGroups[i]; + const size_t J = packageGroup->packages.size(); + for (size_t j=0; j<J; j++) { + const Package* package = packageGroup->packages[j]; + const size_t K = package->types.size(); + for (size_t k=0; k<K; k++) { + const Type* type = package->types[k]; + if (type == NULL) continue; + const size_t L = type->configs.size(); + for (size_t l=0; l<L; l++) { + const ResTable_type* config = type->configs[l]; + const ResTable_config* cfg = &config->config; + // only insert unique + const size_t M = configs->size(); + size_t m; + for (m=0; m<M; m++) { + if (0 == (*configs)[m].compare(*cfg)) { + break; + } + } + // if we didn't find it + if (m == M) { + configs->add(*cfg); + } + } + } + } + } +} + +void ResTable::getLocales(Vector<String8>* locales) const +{ + Vector<ResTable_config> configs; + LOGD("calling getConfigurations"); + getConfigurations(&configs); + LOGD("called getConfigurations size=%d", (int)configs.size()); + const size_t I = configs.size(); + for (size_t i=0; i<I; i++) { + char locale[6]; + configs[i].getLocale(locale); + const size_t J = locales->size(); + size_t j; + for (j=0; j<J; j++) { + if (0 == strcmp(locale, (*locales)[j].string())) { + break; + } + } + if (j == J) { + locales->add(String8(locale)); + } + } +} + +ssize_t ResTable::getEntry( + const Package* package, int typeIndex, int entryIndex, + const ResTable_config* config, + const ResTable_type** outType, const ResTable_entry** outEntry, + const Type** outTypeClass) const +{ + LOGV("Getting entry from package %p\n", package); + const ResTable_package* const pkg = package->package; + + const Type* allTypes = package->getType(typeIndex); + LOGV("allTypes=%p\n", allTypes); + if (allTypes == NULL) { + LOGV("Skipping entry type index 0x%02x because type is NULL!\n", typeIndex); + return 0; + } + + if ((size_t)entryIndex >= allTypes->entryCount) { + LOGW("getEntry failing because entryIndex %d is beyond type entryCount %d", + entryIndex, (int)allTypes->entryCount); + return BAD_TYPE; + } + + const ResTable_type* type = NULL; + uint32_t offset = ResTable_type::NO_ENTRY; + ResTable_config bestConfig; + memset(&bestConfig, 0, sizeof(bestConfig)); // make the compiler shut up + + const size_t NT = allTypes->configs.size(); + for (size_t i=0; i<NT; i++) { + const ResTable_type* const thisType = allTypes->configs[i]; + if (thisType == NULL) continue; + + ResTable_config thisConfig; + thisConfig.copyFromDtoH(thisType->config); + + TABLE_GETENTRY(LOGI("Match entry 0x%x in type 0x%x (sz 0x%x): imsi:%d/%d=%d/%d lang:%c%c=%c%c cnt:%c%c=%c%c " + "orien:%d=%d touch:%d=%d density:%d=%d key:%d=%d inp:%d=%d nav:%d=%d w:%d=%d h:%d=%d\n", + entryIndex, typeIndex+1, dtohl(thisType->config.size), + thisConfig.mcc, thisConfig.mnc, + config ? config->mcc : 0, config ? config->mnc : 0, + thisConfig.language[0] ? thisConfig.language[0] : '-', + thisConfig.language[1] ? thisConfig.language[1] : '-', + config && config->language[0] ? config->language[0] : '-', + config && config->language[1] ? config->language[1] : '-', + thisConfig.country[0] ? thisConfig.country[0] : '-', + thisConfig.country[1] ? thisConfig.country[1] : '-', + config && config->country[0] ? config->country[0] : '-', + config && config->country[1] ? config->country[1] : '-', + thisConfig.orientation, + config ? config->orientation : 0, + thisConfig.touchscreen, + config ? config->touchscreen : 0, + thisConfig.density, + config ? config->density : 0, + thisConfig.keyboard, + config ? config->keyboard : 0, + thisConfig.inputFlags, + config ? config->inputFlags : 0, + thisConfig.navigation, + config ? config->navigation : 0, + thisConfig.screenWidth, + config ? config->screenWidth : 0, + thisConfig.screenHeight, + config ? config->screenHeight : 0)); + + // Check to make sure this one is valid for the current parameters. + if (config && !thisConfig.match(*config)) { + TABLE_GETENTRY(LOGI("Does not match config!\n")); + continue; + } + + // Check if there is the desired entry in this type. + + const uint8_t* const end = ((const uint8_t*)thisType) + + dtohl(thisType->header.size); + const uint32_t* const eindex = (const uint32_t*) + (((const uint8_t*)thisType) + dtohs(thisType->header.headerSize)); + + uint32_t thisOffset = dtohl(eindex[entryIndex]); + if (thisOffset == ResTable_type::NO_ENTRY) { + TABLE_GETENTRY(LOGI("Skipping because it is not defined!\n")); + continue; + } + + if (type != NULL) { + // Check if this one is less specific than the last found. If so, + // we will skip it. We check starting with things we most care + // about to those we least care about. + if (!thisConfig.isBetterThan(bestConfig, config)) { + TABLE_GETENTRY(LOGI("This config is worse than last!\n")); + continue; + } + } + + type = thisType; + offset = thisOffset; + bestConfig = thisConfig; + TABLE_GETENTRY(LOGI("Best entry so far -- using it!\n")); + if (!config) break; + } + + if (type == NULL) { + TABLE_GETENTRY(LOGI("No value found for requested entry!\n")); + return BAD_INDEX; + } + + offset += dtohl(type->entriesStart); + TABLE_NOISY(aout << "Looking in resource table " << package->header->header + << ", typeOff=" + << (void*)(((const char*)type)-((const char*)package->header->header)) + << ", offset=" << (void*)offset << endl); + + if (offset > (dtohl(type->header.size)-sizeof(ResTable_entry))) { + LOGW("ResTable_entry at 0x%x is beyond type chunk data 0x%x", + offset, dtohl(type->header.size)); + return BAD_TYPE; + } + if ((offset&0x3) != 0) { + LOGW("ResTable_entry at 0x%x is not on an integer boundary", + offset); + return BAD_TYPE; + } + + const ResTable_entry* const entry = (const ResTable_entry*) + (((const uint8_t*)type) + offset); + if (dtohs(entry->size) < sizeof(*entry)) { + LOGW("ResTable_entry size 0x%x is too small", dtohs(entry->size)); + return BAD_TYPE; + } + + *outType = type; + *outEntry = entry; + if (outTypeClass != NULL) { + *outTypeClass = allTypes; + } + return offset + dtohs(entry->size); +} + +status_t ResTable::parsePackage(const ResTable_package* const pkg, + const Header* const header) +{ + const uint8_t* base = (const uint8_t*)pkg; + status_t err = validate_chunk(&pkg->header, sizeof(*pkg), + header->dataEnd, "ResTable_package"); + if (err != NO_ERROR) { + return (mError=err); + } + + const size_t pkgSize = dtohl(pkg->header.size); + + if (dtohl(pkg->typeStrings) >= pkgSize) { + LOGW("ResTable_package type strings at %p are past chunk size %p.", + (void*)dtohl(pkg->typeStrings), (void*)pkgSize); + return (mError=BAD_TYPE); + } + if ((dtohl(pkg->typeStrings)&0x3) != 0) { + LOGW("ResTable_package type strings at %p is not on an integer boundary.", + (void*)dtohl(pkg->typeStrings)); + return (mError=BAD_TYPE); + } + if (dtohl(pkg->keyStrings) >= pkgSize) { + LOGW("ResTable_package key strings at %p are past chunk size %p.", + (void*)dtohl(pkg->keyStrings), (void*)pkgSize); + return (mError=BAD_TYPE); + } + if ((dtohl(pkg->keyStrings)&0x3) != 0) { + LOGW("ResTable_package key strings at %p is not on an integer boundary.", + (void*)dtohl(pkg->keyStrings)); + return (mError=BAD_TYPE); + } + + Package* package = NULL; + PackageGroup* group = NULL; + uint32_t id = dtohl(pkg->id); + if (id != 0 && id < 256) { + size_t idx = mPackageMap[id]; + if (idx == 0) { + idx = mPackageGroups.size()+1; + + char16_t tmpName[sizeof(pkg->name)/sizeof(char16_t)]; + strcpy16_dtoh(tmpName, pkg->name, sizeof(pkg->name)/sizeof(char16_t)); + group = new PackageGroup(String16(tmpName), id); + if (group == NULL) { + return (mError=NO_MEMORY); + } + + err = group->typeStrings.setTo(base+dtohl(pkg->typeStrings), + header->dataEnd-(base+dtohl(pkg->typeStrings))); + if (err != NO_ERROR) { + return (mError=err); + } + err = group->keyStrings.setTo(base+dtohl(pkg->keyStrings), + header->dataEnd-(base+dtohl(pkg->keyStrings))); + if (err != NO_ERROR) { + return (mError=err); + } + + //printf("Adding new package id %d at index %d\n", id, idx); + err = mPackageGroups.add(group); + if (err < NO_ERROR) { + return (mError=err); + } + mPackageMap[id] = (uint8_t)idx; + } else { + group = mPackageGroups.itemAt(idx-1); + if (group == NULL) { + return (mError=UNKNOWN_ERROR); + } + } + package = new Package(header, pkg); + if (package == NULL) { + return (mError=NO_MEMORY); + } + err = group->packages.add(package); + if (err < NO_ERROR) { + return (mError=err); + } + } else { + LOG_ALWAYS_FATAL("Skins not supported!"); + return NO_ERROR; + } + + + // Iterate through all chunks. + size_t curPackage = 0; + + const ResChunk_header* chunk = + (const ResChunk_header*)(((const uint8_t*)pkg) + + dtohs(pkg->header.headerSize)); + const uint8_t* endPos = ((const uint8_t*)pkg) + dtohs(pkg->header.size); + while (((const uint8_t*)chunk) <= (endPos-sizeof(ResChunk_header)) && + ((const uint8_t*)chunk) <= (endPos-dtohl(chunk->size))) { + TABLE_NOISY(LOGV("PackageChunk: type=0x%x, headerSize=0x%x, size=0x%x, pos=%p\n", + dtohs(chunk->type), dtohs(chunk->headerSize), dtohl(chunk->size), + (void*)(((const uint8_t*)chunk) - ((const uint8_t*)header->header)))); + const size_t csize = dtohl(chunk->size); + const uint16_t ctype = dtohs(chunk->type); + if (ctype == RES_TABLE_TYPE_SPEC_TYPE) { + const ResTable_typeSpec* typeSpec = (const ResTable_typeSpec*)(chunk); + err = validate_chunk(&typeSpec->header, sizeof(*typeSpec), + endPos, "ResTable_typeSpec"); + if (err != NO_ERROR) { + return (mError=err); + } + + const size_t typeSpecSize = dtohl(typeSpec->header.size); + + LOAD_TABLE_NOISY(printf("TypeSpec off %p: type=0x%x, headerSize=0x%x, size=%p\n", + (void*)(base-(const uint8_t*)chunk), + dtohs(typeSpec->header.type), + dtohs(typeSpec->header.headerSize), + (void*)typeSize)); + // look for block overrun or int overflow when multiplying by 4 + if ((dtohl(typeSpec->entryCount) > (INT32_MAX/sizeof(uint32_t)) + || dtohs(typeSpec->header.headerSize)+(sizeof(uint32_t)*dtohl(typeSpec->entryCount)) + > typeSpecSize)) { + LOGW("ResTable_typeSpec entry index to %p extends beyond chunk end %p.", + (void*)(dtohs(typeSpec->header.headerSize) + +(sizeof(uint32_t)*dtohl(typeSpec->entryCount))), + (void*)typeSpecSize); + return (mError=BAD_TYPE); + } + + if (typeSpec->id == 0) { + LOGW("ResTable_type has an id of 0."); + return (mError=BAD_TYPE); + } + + while (package->types.size() < typeSpec->id) { + package->types.add(NULL); + } + Type* t = package->types[typeSpec->id-1]; + if (t == NULL) { + t = new Type(header, package, dtohl(typeSpec->entryCount)); + package->types.editItemAt(typeSpec->id-1) = t; + } else if (dtohl(typeSpec->entryCount) != t->entryCount) { + LOGW("ResTable_typeSpec entry count inconsistent: given %d, previously %d", + (int)dtohl(typeSpec->entryCount), (int)t->entryCount); + return (mError=BAD_TYPE); + } + t->typeSpecFlags = (const uint32_t*)( + ((const uint8_t*)typeSpec) + dtohs(typeSpec->header.headerSize)); + t->typeSpec = typeSpec; + + } else if (ctype == RES_TABLE_TYPE_TYPE) { + const ResTable_type* type = (const ResTable_type*)(chunk); + err = validate_chunk(&type->header, sizeof(*type)-sizeof(ResTable_config)+4, + endPos, "ResTable_type"); + if (err != NO_ERROR) { + return (mError=err); + } + + const size_t typeSize = dtohl(type->header.size); + + LOAD_TABLE_NOISY(printf("Type off %p: type=0x%x, headerSize=0x%x, size=%p\n", + (void*)(base-(const uint8_t*)chunk), + dtohs(type->header.type), + dtohs(type->header.headerSize), + (void*)typeSize)); + if (dtohs(type->header.headerSize)+(sizeof(uint32_t)*dtohl(type->entryCount)) + > typeSize) { + LOGW("ResTable_type entry index to %p extends beyond chunk end %p.", + (void*)(dtohs(type->header.headerSize) + +(sizeof(uint32_t)*dtohl(type->entryCount))), + (void*)typeSize); + return (mError=BAD_TYPE); + } + if (dtohl(type->entryCount) != 0 + && dtohl(type->entriesStart) > (typeSize-sizeof(ResTable_entry))) { + LOGW("ResTable_type entriesStart at %p extends beyond chunk end %p.", + (void*)dtohl(type->entriesStart), (void*)typeSize); + return (mError=BAD_TYPE); + } + if (type->id == 0) { + LOGW("ResTable_type has an id of 0."); + return (mError=BAD_TYPE); + } + + while (package->types.size() < type->id) { + package->types.add(NULL); + } + Type* t = package->types[type->id-1]; + if (t == NULL) { + t = new Type(header, package, dtohl(type->entryCount)); + package->types.editItemAt(type->id-1) = t; + } else if (dtohl(type->entryCount) != t->entryCount) { + LOGW("ResTable_type entry count inconsistent: given %d, previously %d", + (int)dtohl(type->entryCount), (int)t->entryCount); + return (mError=BAD_TYPE); + } + + TABLE_GETENTRY( + ResTable_config thisConfig; + thisConfig.copyFromDtoH(type->config); + LOGI("Adding config to type %d: imsi:%d/%d lang:%c%c cnt:%c%c " + "orien:%d touch:%d density:%d key:%d inp:%d nav:%d w:%d h:%d\n", + type->id, + thisConfig.mcc, thisConfig.mnc, + thisConfig.language[0] ? thisConfig.language[0] : '-', + thisConfig.language[1] ? thisConfig.language[1] : '-', + thisConfig.country[0] ? thisConfig.country[0] : '-', + thisConfig.country[1] ? thisConfig.country[1] : '-', + thisConfig.orientation, + thisConfig.touchscreen, + thisConfig.density, + thisConfig.keyboard, + thisConfig.inputFlags, + thisConfig.navigation, + thisConfig.screenWidth, + thisConfig.screenHeight)); + t->configs.add(type); + } else { + status_t err = validate_chunk(chunk, sizeof(ResChunk_header), + endPos, "ResTable_package:unknown"); + if (err != NO_ERROR) { + return (mError=err); + } + } + chunk = (const ResChunk_header*) + (((const uint8_t*)chunk) + csize); + } + + if (group->typeCount == 0) { + group->typeCount = package->types.size(); + } + + return NO_ERROR; +} + +#ifndef HAVE_ANDROID_OS +#define CHAR16_TO_CSTR(c16, len) (String8(String16(c16,len)).string()) + +#define CHAR16_ARRAY_EQ(constant, var, len) \ + ((len == (sizeof(constant)/sizeof(constant[0]))) && (0 == memcmp((var), (constant), (len)))) + +void ResTable::print() const +{ + printf("mError=0x%x (%s)\n", mError, strerror(mError)); +#if 0 + printf("mParams=%c%c-%c%c,\n", + mParams.language[0], mParams.language[1], + mParams.country[0], mParams.country[1]); +#endif + size_t pgCount = mPackageGroups.size(); + printf("Package Groups (%d)\n", (int)pgCount); + for (size_t pgIndex=0; pgIndex<pgCount; pgIndex++) { + const PackageGroup* pg = mPackageGroups[pgIndex]; + printf("Package Group %d id=%d packageCount=%d name=%s\n", + (int)pgIndex, pg->id, (int)pg->packages.size(), + String8(pg->name).string()); + + size_t pkgCount = pg->packages.size(); + for (size_t pkgIndex=0; pkgIndex<pkgCount; pkgIndex++) { + const Package* pkg = pg->packages[pkgIndex]; + size_t typeCount = pkg->types.size(); + printf(" Package %d id=%d name=%s typeCount=%d\n", (int)pkgIndex, + pkg->package->id, String8(String16(pkg->package->name)).string(), + (int)typeCount); + for (size_t typeIndex=0; typeIndex<typeCount; typeIndex++) { + const Type* typeConfigs = pkg->getType(typeIndex); + if (typeConfigs == NULL) { + printf(" type %d NULL\n", (int)typeIndex); + continue; + } + const size_t NTC = typeConfigs->configs.size(); + printf(" type %d configCount=%d entryCount=%d\n", + (int)typeIndex, (int)NTC, (int)typeConfigs->entryCount); + if (typeConfigs->typeSpecFlags != NULL) { + for (size_t entryIndex=0; entryIndex<typeConfigs->entryCount; entryIndex++) { + uint32_t resID = (0xff000000 & ((pkg->package->id)<<24)) + | (0x00ff0000 & ((typeIndex+1)<<16)) + | (0x0000ffff & (entryIndex)); + resource_name resName; + this->getResourceName(resID, &resName); + printf(" spec resource 0x%08x %s:%s/%s: flags=0x%08x\n", + resID, + CHAR16_TO_CSTR(resName.package, resName.packageLen), + CHAR16_TO_CSTR(resName.type, resName.typeLen), + CHAR16_TO_CSTR(resName.name, resName.nameLen), + dtohl(typeConfigs->typeSpecFlags[entryIndex])); + } + } + for (size_t configIndex=0; configIndex<NTC; configIndex++) { + const ResTable_type* type = typeConfigs->configs[configIndex]; + if ((((int)type)&0x3) != 0) { + printf(" NON-INTEGER ResTable_type ADDRESS: %p\n", type); + continue; + } + printf(" config %d lang=%c%c cnt=%c%c orien=%d touch=%d density=%d key=%d infl=%d nav=%d w=%d h=%d\n", + (int)configIndex, + type->config.language[0] ? type->config.language[0] : '-', + type->config.language[1] ? type->config.language[1] : '-', + type->config.country[0] ? type->config.country[0] : '-', + type->config.country[1] ? type->config.country[1] : '-', + type->config.orientation, + type->config.touchscreen, + dtohs(type->config.density), + type->config.keyboard, + type->config.inputFlags, + type->config.navigation, + dtohs(type->config.screenWidth), + dtohs(type->config.screenHeight)); + size_t entryCount = dtohl(type->entryCount); + uint32_t entriesStart = dtohl(type->entriesStart); + if ((entriesStart&0x3) != 0) { + printf(" NON-INTEGER ResTable_type entriesStart OFFSET: %p\n", (void*)entriesStart); + continue; + } + uint32_t typeSize = dtohl(type->header.size); + if ((typeSize&0x3) != 0) { + printf(" NON-INTEGER ResTable_type header.size: %p\n", (void*)typeSize); + continue; + } + for (size_t entryIndex=0; entryIndex<entryCount; entryIndex++) { + + const uint8_t* const end = ((const uint8_t*)type) + + dtohl(type->header.size); + const uint32_t* const eindex = (const uint32_t*) + (((const uint8_t*)type) + dtohs(type->header.headerSize)); + + uint32_t thisOffset = dtohl(eindex[entryIndex]); + if (thisOffset == ResTable_type::NO_ENTRY) { + continue; + } + + uint32_t resID = (0xff000000 & ((pkg->package->id)<<24)) + | (0x00ff0000 & ((typeIndex+1)<<16)) + | (0x0000ffff & (entryIndex)); + resource_name resName; + this->getResourceName(resID, &resName); + printf(" resource 0x%08x %s:%s/%s: ", resID, + CHAR16_TO_CSTR(resName.package, resName.packageLen), + CHAR16_TO_CSTR(resName.type, resName.typeLen), + CHAR16_TO_CSTR(resName.name, resName.nameLen)); + if ((thisOffset&0x3) != 0) { + printf("NON-INTEGER OFFSET: %p\n", (void*)thisOffset); + continue; + } + if ((thisOffset+sizeof(ResTable_entry)) > typeSize) { + printf("OFFSET OUT OF BOUNDS: %p+%p (size is %p)\n", + (void*)entriesStart, (void*)thisOffset, + (void*)typeSize); + continue; + } + + const ResTable_entry* ent = (const ResTable_entry*) + (((const uint8_t*)type) + entriesStart + thisOffset); + if (((entriesStart + thisOffset)&0x3) != 0) { + printf("NON-INTEGER ResTable_entry OFFSET: %p\n", + (void*)(entriesStart + thisOffset)); + continue; + } + if ((dtohs(ent->flags)&ResTable_entry::FLAG_COMPLEX) != 0) { + printf("<bag>"); + } else { + uint16_t esize = dtohs(ent->size); + if ((esize&0x3) != 0) { + printf("NON-INTEGER ResTable_entry SIZE: %p\n", (void*)esize); + continue; + } + if ((thisOffset+esize) > typeSize) { + printf("ResTable_entry OUT OF BOUNDS: %p+%p+%p (size is %p)\n", + (void*)entriesStart, (void*)thisOffset, + (void*)esize, (void*)typeSize); + continue; + } + + const Res_value* value = (const Res_value*) + (((const uint8_t*)ent) + esize); + printf("t=0x%02x d=0x%08x (s=0x%04x r=0x%02x)", + (int)value->dataType, (int)dtohl(value->data), + (int)dtohs(value->size), (int)value->res0); + } + + if ((dtohs(ent->flags)&ResTable_entry::FLAG_PUBLIC) != 0) { + printf(" (PUBLIC)"); + } + printf("\n"); + } + } + } + } + } +} + +#endif // HAVE_ANDROID_OS + +} // namespace android diff --git a/libs/utils/SharedBuffer.cpp b/libs/utils/SharedBuffer.cpp new file mode 100644 index 0000000..3555fb7 --- /dev/null +++ b/libs/utils/SharedBuffer.cpp @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2005 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. + */ + +#include <stdlib.h> +#include <string.h> + +#include <utils/SharedBuffer.h> +#include <utils/Atomic.h> + +// --------------------------------------------------------------------------- + +namespace android { + +SharedBuffer* SharedBuffer::alloc(size_t size) +{ + SharedBuffer* sb = static_cast<SharedBuffer *>(malloc(sizeof(SharedBuffer) + size)); + if (sb) { + sb->mRefs = 1; + sb->mSize = size; + } + return sb; +} + + +ssize_t SharedBuffer::dealloc(const SharedBuffer* released) +{ + if (released->mRefs != 0) return -1; // XXX: invalid operation + free(const_cast<SharedBuffer*>(released)); + return 0; +} + +SharedBuffer* SharedBuffer::edit() const +{ + if (onlyOwner()) { + return const_cast<SharedBuffer*>(this); + } + SharedBuffer* sb = alloc(mSize); + if (sb) { + memcpy(sb->data(), data(), size()); + release(); + } + return sb; +} + +SharedBuffer* SharedBuffer::editResize(size_t newSize) const +{ + if (onlyOwner()) { + SharedBuffer* buf = const_cast<SharedBuffer*>(this); + if (buf->mSize == newSize) return buf; + buf = (SharedBuffer*)realloc(buf, sizeof(SharedBuffer) + newSize); + if (buf != NULL) { + buf->mSize = newSize; + return buf; + } + } + SharedBuffer* sb = alloc(newSize); + if (sb) { + const size_t mySize = mSize; + memcpy(sb->data(), data(), newSize < mySize ? newSize : mySize); + release(); + } + return sb; +} + +SharedBuffer* SharedBuffer::attemptEdit() const +{ + if (onlyOwner()) { + return const_cast<SharedBuffer*>(this); + } + return 0; +} + +SharedBuffer* SharedBuffer::reset(size_t new_size) const +{ + // cheap-o-reset. + SharedBuffer* sb = alloc(new_size); + if (sb) { + release(); + } + return sb; +} + +void SharedBuffer::acquire() const { + android_atomic_inc(&mRefs); +} + +int32_t SharedBuffer::release(uint32_t flags) const +{ + int32_t prev = 1; + if (onlyOwner() || ((prev = android_atomic_dec(&mRefs)) == 1)) { + mRefs = 0; + if ((flags & eKeepStorage) == 0) { + free(const_cast<SharedBuffer*>(this)); + } + } + return prev; +} + + +}; // namespace android diff --git a/libs/utils/Socket.cpp b/libs/utils/Socket.cpp new file mode 100644 index 0000000..51509a3 --- /dev/null +++ b/libs/utils/Socket.cpp @@ -0,0 +1,388 @@ +/* + * Copyright (C) 2005 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. + */ + +// +// Internet address class. +// + +#ifdef HAVE_WINSOCK +// This needs to come first, or Cygwin gets concerned about a potential +// clash between WinSock and <sys/types.h>. +# include <winsock2.h> +#endif + +#include <utils/Socket.h> +#include <utils/inet_address.h> +#include <utils/Log.h> +#include <utils/Timers.h> + +#ifndef HAVE_WINSOCK +# include <sys/types.h> +# include <sys/socket.h> +# include <netinet/in.h> +# include <arpa/inet.h> +#endif + +#include <stdlib.h> +#include <stdio.h> +#include <unistd.h> +#include <string.h> +#include <errno.h> +#include <assert.h> + +using namespace android; + + +/* + * =========================================================================== + * Socket + * =========================================================================== + */ + +#ifndef INVALID_SOCKET +# define INVALID_SOCKET (-1) +#endif +#define UNDEF_SOCKET ((unsigned long) INVALID_SOCKET) + +/*static*/ bool Socket::mBootInitialized = false; + +/* + * Extract system-dependent error code. + */ +static inline int getSocketError(void) { +#ifdef HAVE_WINSOCK + return WSAGetLastError(); +#else + return errno; +#endif +} + +/* + * One-time initialization for socket code. + */ +/*static*/ bool Socket::bootInit(void) +{ +#ifdef HAVE_WINSOCK + WSADATA wsaData; + int err; + + err = WSAStartup(MAKEWORD(2, 0), &wsaData); + if (err != 0) { + LOG(LOG_ERROR, "socket", "Unable to start WinSock\n"); + return false; + } + + LOG(LOG_INFO, "socket", "Using WinSock v%d.%d\n", + LOBYTE(wsaData.wVersion), HIBYTE(wsaData.wVersion)); +#endif + + mBootInitialized = true; + return true; +} + +/* + * One-time shutdown for socket code. + */ +/*static*/ void Socket::finalShutdown(void) +{ +#ifdef HAVE_WINSOCK + WSACleanup(); +#endif + mBootInitialized = false; +} + + +/* + * Simple constructor. Allow the application to create us and then make + * bind/connect calls. + */ +Socket::Socket(void) + : mSock(UNDEF_SOCKET) +{ + if (!mBootInitialized) + LOG(LOG_WARN, "socket", "WARNING: sockets not initialized\n"); +} + +/* + * Destructor. Closes the socket and resets our storage. + */ +Socket::~Socket(void) +{ + close(); +} + + +/* + * Create a socket and connect to the specified host and port. + */ +int Socket::connect(const char* host, int port) +{ + if (mSock != UNDEF_SOCKET) { + LOG(LOG_WARN, "socket", "Socket already connected\n"); + return -1; + } + + InetSocketAddress sockAddr; + if (!sockAddr.create(host, port)) + return -1; + + //return doConnect(sockAddr); + int foo; + foo = doConnect(sockAddr); + return foo; +} + +/* + * Create a socket and connect to the specified host and port. + */ +int Socket::connect(const InetAddress* addr, int port) +{ + if (mSock != UNDEF_SOCKET) { + LOG(LOG_WARN, "socket", "Socket already connected\n"); + return -1; + } + + InetSocketAddress sockAddr; + if (!sockAddr.create(addr, port)) + return -1; + + return doConnect(sockAddr); +} + +/* + * Finish creating a socket by connecting to the remote host. + * + * Returns 0 on success. + */ +int Socket::doConnect(const InetSocketAddress& sockAddr) +{ +#ifdef HAVE_WINSOCK + SOCKET sock; +#else + int sock; +#endif + const InetAddress* addr = sockAddr.getAddress(); + int port = sockAddr.getPort(); + struct sockaddr_in inaddr; + DurationTimer connectTimer; + + assert(sizeof(struct sockaddr_in) == addr->getAddressLength()); + memcpy(&inaddr, addr->getAddress(), addr->getAddressLength()); + inaddr.sin_port = htons(port); + + //fprintf(stderr, "--- connecting to %s:%d\n", + // sockAddr.getHostName(), port); + + sock = ::socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); + if (sock == INVALID_SOCKET) { + int err = getSocketError(); + LOG(LOG_ERROR, "socket", "Unable to create socket (err=%d)\n", err); + return (err != 0) ? err : -1; + } + + connectTimer.start(); + + if (::connect(sock, (struct sockaddr*) &inaddr, sizeof(inaddr)) != 0) { + int err = getSocketError(); + LOG(LOG_WARN, "socket", "Connect to %s:%d failed: %d\n", + sockAddr.getHostName(), port, err); + return (err != 0) ? err : -1; + } + + connectTimer.stop(); + if ((long) connectTimer.durationUsecs() > 100000) { + LOG(LOG_INFO, "socket", + "Connect to %s:%d took %.3fs\n", sockAddr.getHostName(), + port, ((long) connectTimer.durationUsecs()) / 1000000.0); + } + + mSock = (unsigned long) sock; + LOG(LOG_VERBOSE, "socket", + "--- connected to %s:%d\n", sockAddr.getHostName(), port); + return 0; +} + + +/* + * Close the socket if it needs closing. + */ +bool Socket::close(void) +{ + if (mSock != UNDEF_SOCKET) { + //fprintf(stderr, "--- closing socket %lu\n", mSock); +#ifdef HAVE_WINSOCK + if (::closesocket((SOCKET) mSock) != 0) + return false; +#else + if (::close((int) mSock) != 0) + return false; +#endif + } + + mSock = UNDEF_SOCKET; + + return true; +} + +/* + * Read data from socket. + * + * Standard semantics: read up to "len" bytes into "buf". Returns the + * number of bytes read, or less than zero on error. + */ +int Socket::read(void* buf, ssize_t len) const +{ + if (mSock == UNDEF_SOCKET) { + LOG(LOG_ERROR, "socket", "ERROR: read on invalid socket\n"); + return -500; + } + +#ifdef HAVE_WINSOCK + SOCKET sock = (SOCKET) mSock; +#else + int sock = (int) mSock; +#endif + int cc; + + cc = recv(sock, (char*)buf, len, 0); + if (cc < 0) { + int err = getSocketError(); + return (err > 0) ? -err : -1; + } + + return cc; +} + +/* + * Write data to a socket. + * + * Standard semantics: write up to "len" bytes into "buf". Returns the + * number of bytes written, or less than zero on error. + */ +int Socket::write(const void* buf, ssize_t len) const +{ + if (mSock == UNDEF_SOCKET) { + LOG(LOG_ERROR, "socket", "ERROR: write on invalid socket\n"); + return -500; + } + +#ifdef HAVE_WINSOCK + SOCKET sock = (SOCKET) mSock; +#else + int sock = (int) mSock; +#endif + int cc; + + cc = send(sock, (const char*)buf, len, 0); + if (cc < 0) { + int err = getSocketError(); + return (err > 0) ? -err : -1; + } + + return cc; +} + + +/* + * =========================================================================== + * Socket tests + * =========================================================================== + */ + +/* + * Read all data from the socket. The data is read into a buffer that + * expands as needed. + * + * On exit, the buffer is returned, and the length of the data is stored + * in "*sz". A null byte is added to the end, but is not included in + * the length. + */ +static char* socketReadAll(const Socket& s, int *sz) +{ + int max, r; + char *data, *ptr, *tmp; + + data = (char*) malloc(max = 32768); + if (data == NULL) + return NULL; + + ptr = data; + + for (;;) { + if ((ptr - data) == max) { + tmp = (char*) realloc(data, max *= 2); + if(tmp == 0) { + free(data); + return 0; + } + } + r = s.read(ptr, max - (ptr - data)); + if (r == 0) + break; + if (r < 0) { + LOG(LOG_WARN, "socket", "WARNING: socket read failed (res=%d)\n",r); + break; + } + ptr += r; + } + + if ((ptr - data) == max) { + tmp = (char*) realloc(data, max + 1); + if (tmp == NULL) { + free(data); + return NULL; + } + } + *ptr = '\0'; + *sz = (ptr - data); + return data; +} + +/* + * Exercise the Socket class. + */ +void android::TestSockets(void) +{ + printf("----- SOCKET TEST ------\n"); + Socket::bootInit(); + + char* buf = NULL; + int len, cc; + const char* kTestStr = + "GET / HTTP/1.0\n" + "Connection: close\n" + "\n"; + + Socket sock; + if (sock.connect("www.google.com", 80) != 0) { + fprintf(stderr, "socket connected failed\n"); + goto bail; + } + + cc = sock.write(kTestStr, strlen(kTestStr)); + if (cc != (int) strlen(kTestStr)) { + fprintf(stderr, "write failed, res=%d\n", cc); + goto bail; + } + buf = socketReadAll(sock, &len); + + printf("GOT '%s'\n", buf); + +bail: + sock.close(); + free(buf); +} + diff --git a/libs/utils/Static.cpp b/libs/utils/Static.cpp new file mode 100644 index 0000000..93f7e4f --- /dev/null +++ b/libs/utils/Static.cpp @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2008 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. + */ + +// All static variables go here, to control initialization and +// destruction order in the library. + +#include <private/utils/Static.h> + +#include <utils/BufferedTextOutput.h> +#include <utils/IPCThreadState.h> +#include <utils/Log.h> + +namespace android { + +class LibUtilsFirstStatics +{ +public: + LibUtilsFirstStatics() + { + initialize_string8(); + initialize_string16(); + } + + ~LibUtilsFirstStatics() + { + terminate_string16(); + terminate_string8(); + } +}; + +static LibUtilsFirstStatics gFirstStatics; +int gDarwinCantLoadAllObjects = 1; + +// ------------ Text output streams + +Vector<int32_t> gTextBuffers; + +class LogTextOutput : public BufferedTextOutput +{ +public: + LogTextOutput() : BufferedTextOutput(MULTITHREADED) { } + virtual ~LogTextOutput() { }; + +protected: + virtual status_t writeLines(const struct iovec& vec, size_t N) + { + android_writevLog(&vec, N); + return NO_ERROR; + } +}; + +class FdTextOutput : public BufferedTextOutput +{ +public: + FdTextOutput(int fd) : BufferedTextOutput(MULTITHREADED), mFD(fd) { } + virtual ~FdTextOutput() { }; + +protected: + virtual status_t writeLines(const struct iovec& vec, size_t N) + { + writev(mFD, &vec, N); + return NO_ERROR; + } + +private: + int mFD; +}; + +static LogTextOutput gLogTextOutput; +static FdTextOutput gStdoutTextOutput(STDOUT_FILENO); +static FdTextOutput gStderrTextOutput(STDERR_FILENO); + +TextOutput& alog(gLogTextOutput); +TextOutput& aout(gStdoutTextOutput); +TextOutput& aerr(gStderrTextOutput); + +#ifndef LIBUTILS_NATIVE + +// ------------ ProcessState.cpp + +Mutex gProcessMutex; +sp<ProcessState> gProcess; + +class LibUtilsIPCtStatics +{ +public: + LibUtilsIPCtStatics() + { + } + + ~LibUtilsIPCtStatics() + { + IPCThreadState::shutdown(); + } +}; + +static LibUtilsIPCtStatics gIPCStatics; + +// ------------ ServiceManager.cpp + +Mutex gDefaultServiceManagerLock; +sp<IServiceManager> gDefaultServiceManager; +sp<IPermissionController> gPermissionController; + +#endif + +} // namespace android diff --git a/libs/utils/StopWatch.cpp b/libs/utils/StopWatch.cpp new file mode 100644 index 0000000..68a1c52 --- /dev/null +++ b/libs/utils/StopWatch.cpp @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2005 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. + */ + +#define LOG_TAG "StopWatch" + +#include <string.h> +#include <stdlib.h> +#include <stdio.h> + +#include <utils/Log.h> +#include <utils/Errors.h> +#include <utils/StopWatch.h> + +/*****************************************************************************/ + +namespace android { + + +StopWatch::StopWatch(const char *name, int clock, uint32_t flags) + : mName(name), mClock(clock), mFlags(flags), + mStartTime(0), mNumLaps(0) +{ + mStartTime = systemTime(mClock); +} + +StopWatch::~StopWatch() +{ + nsecs_t elapsed = elapsedTime(); + const int n = mNumLaps; + LOGD("StopWatch %s (us): %lld ", mName, ns2us(elapsed)); + for (int i=0 ; i<n ; i++) { + const nsecs_t soFar = mLaps[i].soFar; + const nsecs_t thisLap = mLaps[i].thisLap; + LOGD(" [%d: %lld, %lld]", i, ns2us(soFar), ns2us(thisLap)); + } +} + +const char* StopWatch::name() const +{ + return mName; +} + +nsecs_t StopWatch::lap() +{ + nsecs_t elapsed = elapsedTime(); + if (mNumLaps >= 8) { + elapsed = 0; + } else { + const int n = mNumLaps; + mLaps[n].soFar = elapsed; + mLaps[n].thisLap = n ? (elapsed - mLaps[n-1].soFar) : elapsed; + mNumLaps = n+1; + } + return elapsed; +} + +nsecs_t StopWatch::elapsedTime() const +{ + return systemTime(mClock) - mStartTime; +} + + +/*****************************************************************************/ + +}; // namespace android + diff --git a/libs/utils/String16.cpp b/libs/utils/String16.cpp new file mode 100644 index 0000000..1f81cad --- /dev/null +++ b/libs/utils/String16.cpp @@ -0,0 +1,609 @@ +/* + * Copyright (C) 2005 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. + */ + +#include <utils/String16.h> + +#include <utils/Debug.h> +#include <utils/Log.h> +#include <utils/String8.h> +#include <utils/TextOutput.h> +#include <utils/threads.h> + +#include <private/utils/Static.h> + +#ifdef HAVE_WINSOCK +# undef nhtol +# undef htonl +# undef nhtos +# undef htons + +# ifdef HAVE_LITTLE_ENDIAN +# define ntohl(x) ( ((x) << 24) | (((x) >> 24) & 255) | (((x) << 8) & 0xff0000) | (((x) >> 8) & 0xff00) ) +# define htonl(x) ntohl(x) +# define ntohs(x) ( (((x) << 8) & 0xff00) | (((x) >> 8) & 255) ) +# define htons(x) ntohs(x) +# else +# define ntohl(x) (x) +# define htonl(x) (x) +# define ntohs(x) (x) +# define htons(x) (x) +# endif +#else +# include <netinet/in.h> +#endif + +#include <memory.h> +#include <stdio.h> +#include <ctype.h> + +// --------------------------------------------------------------------------- + +int strcmp16(const char16_t *s1, const char16_t *s2) +{ + char16_t ch; + int d = 0; + + while ( 1 ) { + d = (int)(ch = *s1++) - (int)*s2++; + if ( d || !ch ) + break; + } + + return d; +} + +int strncmp16(const char16_t *s1, const char16_t *s2, size_t n) +{ + char16_t ch; + int d = 0; + + while ( n-- ) { + d = (int)(ch = *s1++) - (int)*s2++; + if ( d || !ch ) + break; + } + + return d; +} + +char16_t *strcpy16(char16_t *dst, const char16_t *src) +{ + char16_t *q = dst; + const char16_t *p = src; + char16_t ch; + + do { + *q++ = ch = *p++; + } while ( ch ); + + return dst; +} + +size_t strlen16(const char16_t *s) +{ + const char16_t *ss = s; + while ( *ss ) + ss++; + return ss-s; +} + + +char16_t *strncpy16(char16_t *dst, const char16_t *src, size_t n) +{ + char16_t *q = dst; + const char16_t *p = src; + char ch; + + while (n) { + n--; + *q++ = ch = *p++; + if ( !ch ) + break; + } + + *q = 0; + + return dst; +} + +size_t strnlen16(const char16_t *s, size_t maxlen) +{ + const char16_t *ss = s; + + /* Important: the maxlen test must precede the reference through ss; + since the byte beyond the maximum may segfault */ + while ((maxlen > 0) && *ss) { + ss++; + maxlen--; + } + return ss-s; +} + +int strzcmp16(const char16_t *s1, size_t n1, const char16_t *s2, size_t n2) +{ + const char16_t* e1 = s1+n1; + const char16_t* e2 = s2+n2; + + while (s1 < e1 && s2 < e2) { + const int d = (int)*s1++ - (int)*s2++; + if (d) { + return d; + } + } + + return n1 < n2 + ? (0 - (int)*s2) + : (n1 > n2 + ? ((int)*s1 - 0) + : 0); +} + +int strzcmp16_h_n(const char16_t *s1H, size_t n1, const char16_t *s2N, size_t n2) +{ + const char16_t* e1 = s1H+n1; + const char16_t* e2 = s2N+n2; + + while (s1H < e1 && s2N < e2) { + const char16_t c2 = ntohs(*s2N); + const int d = (int)*s1H++ - (int)c2; + s2N++; + if (d) { + return d; + } + } + + return n1 < n2 + ? (0 - (int)ntohs(*s2N)) + : (n1 > n2 + ? ((int)*s1H - 0) + : 0); +} + +// --------------------------------------------------------------------------- + +namespace android { + +static inline size_t +utf8_char_len(uint8_t ch) +{ + return ((0xe5000000 >> ((ch >> 3) & 0x1e)) & 3) + 1; +} + +#define UTF8_SHIFT_AND_MASK(unicode, byte) (unicode)<<=6; (unicode) |= (0x3f & (byte)); + +static inline uint32_t +utf8_to_utf32(const uint8_t *src, size_t length) +{ + uint32_t unicode; + + switch (length) + { + case 1: + return src[0]; + case 2: + unicode = src[0] & 0x1f; + UTF8_SHIFT_AND_MASK(unicode, src[1]) + return unicode; + case 3: + unicode = src[0] & 0x0f; + UTF8_SHIFT_AND_MASK(unicode, src[1]) + UTF8_SHIFT_AND_MASK(unicode, src[2]) + return unicode; + case 4: + unicode = src[0] & 0x07; + UTF8_SHIFT_AND_MASK(unicode, src[1]) + UTF8_SHIFT_AND_MASK(unicode, src[2]) + UTF8_SHIFT_AND_MASK(unicode, src[3]) + return unicode; + default: + return 0xffff; + } + + //printf("Char at %p: len=%d, utf-16=%p\n", src, length, (void*)result); +} + +// --------------------------------------------------------------------------- + +static SharedBuffer* gEmptyStringBuf = NULL; +static char16_t* gEmptyString = NULL; + +static inline char16_t* getEmptyString() +{ + gEmptyStringBuf->acquire(); + return gEmptyString; +} + +void initialize_string16() +{ + SharedBuffer* buf = SharedBuffer::alloc(sizeof(char16_t)); + char16_t* str = (char16_t*)buf->data(); + *str = 0; + gEmptyStringBuf = buf; + gEmptyString = str; +} + +void terminate_string16() +{ + SharedBuffer::bufferFromData(gEmptyString)->release(); + gEmptyStringBuf = NULL; + gEmptyString = NULL; +} + +// --------------------------------------------------------------------------- + +// Note: not dealing with generating surrogate pairs. +static char16_t* allocFromUTF8(const char* in, size_t len) +{ + if (len == 0) return getEmptyString(); + + size_t chars = 0; + const char* end = in+len; + const char* p = in; + + while (p < end) { + chars++; + p += utf8_char_len(*p); + } + + SharedBuffer* buf = SharedBuffer::alloc((chars+1)*sizeof(char16_t)); + if (buf) { + p = in; + char16_t* str = (char16_t*)buf->data(); + char16_t* d = str; + while (p < end) { + size_t len = utf8_char_len(*p); + *d++ = (char16_t)utf8_to_utf32((const uint8_t*)p, len); + p += len; + } + *d = 0; + + //printf("Created UTF-16 string from UTF-8 \"%s\":", in); + //printHexData(1, str, buf->size(), 16, 1); + //printf("\n"); + + return str; + } + + return getEmptyString(); +} + +// --------------------------------------------------------------------------- + +String16::String16() + : mString(getEmptyString()) +{ +} + +String16::String16(const String16& o) + : mString(o.mString) +{ + SharedBuffer::bufferFromData(mString)->acquire(); +} + +String16::String16(const String16& o, size_t len, size_t begin) + : mString(getEmptyString()) +{ + setTo(o, len, begin); +} + +String16::String16(const char16_t* o) +{ + size_t len = strlen16(o); + SharedBuffer* buf = SharedBuffer::alloc((len+1)*sizeof(char16_t)); + LOG_ASSERT(buf, "Unable to allocate shared buffer"); + if (buf) { + char16_t* str = (char16_t*)buf->data(); + strcpy16(str, o); + mString = str; + return; + } + + mString = getEmptyString(); +} + +String16::String16(const char16_t* o, size_t len) +{ + SharedBuffer* buf = SharedBuffer::alloc((len+1)*sizeof(char16_t)); + LOG_ASSERT(buf, "Unable to allocate shared buffer"); + if (buf) { + char16_t* str = (char16_t*)buf->data(); + memcpy(str, o, len*sizeof(char16_t)); + str[len] = 0; + mString = str; + return; + } + + mString = getEmptyString(); +} + +String16::String16(const String8& o) + : mString(allocFromUTF8(o.string(), o.size())) +{ +} + +String16::String16(const char* o) + : mString(allocFromUTF8(o, strlen(o))) +{ +} + +String16::String16(const char* o, size_t len) + : mString(allocFromUTF8(o, len)) +{ +} + +String16::~String16() +{ + SharedBuffer::bufferFromData(mString)->release(); +} + +void String16::setTo(const String16& other) +{ + SharedBuffer::bufferFromData(other.mString)->acquire(); + SharedBuffer::bufferFromData(mString)->release(); + mString = other.mString; +} + +status_t String16::setTo(const String16& other, size_t len, size_t begin) +{ + const size_t N = other.size(); + if (begin >= N) { + SharedBuffer::bufferFromData(mString)->release(); + mString = getEmptyString(); + return NO_ERROR; + } + if ((begin+len) > N) len = N-begin; + if (begin == 0 && len == N) { + setTo(other); + return NO_ERROR; + } + + if (&other == this) { + LOG_ALWAYS_FATAL("Not implemented"); + } + + return setTo(other.string()+begin, len); +} + +status_t String16::setTo(const char16_t* other) +{ + return setTo(other, strlen16(other)); +} + +status_t String16::setTo(const char16_t* other, size_t len) +{ + SharedBuffer* buf = SharedBuffer::bufferFromData(mString) + ->editResize((len+1)*sizeof(char16_t)); + if (buf) { + char16_t* str = (char16_t*)buf->data(); + memcpy(str, other, len*sizeof(char16_t)); + str[len] = 0; + mString = str; + return NO_ERROR; + } + return NO_MEMORY; +} + +status_t String16::append(const String16& other) +{ + const size_t myLen = size(); + const size_t otherLen = other.size(); + if (myLen == 0) { + setTo(other); + return NO_ERROR; + } else if (otherLen == 0) { + return NO_ERROR; + } + + SharedBuffer* buf = SharedBuffer::bufferFromData(mString) + ->editResize((myLen+otherLen+1)*sizeof(char16_t)); + if (buf) { + char16_t* str = (char16_t*)buf->data(); + memcpy(str+myLen, other, (otherLen+1)*sizeof(char16_t)); + mString = str; + return NO_ERROR; + } + return NO_MEMORY; +} + +status_t String16::append(const char16_t* chrs, size_t otherLen) +{ + const size_t myLen = size(); + if (myLen == 0) { + setTo(chrs, otherLen); + return NO_ERROR; + } else if (otherLen == 0) { + return NO_ERROR; + } + + SharedBuffer* buf = SharedBuffer::bufferFromData(mString) + ->editResize((myLen+otherLen+1)*sizeof(char16_t)); + if (buf) { + char16_t* str = (char16_t*)buf->data(); + memcpy(str+myLen, chrs, otherLen*sizeof(char16_t)); + str[myLen+otherLen] = 0; + mString = str; + return NO_ERROR; + } + return NO_MEMORY; +} + +status_t String16::insert(size_t pos, const char16_t* chrs) +{ + return insert(pos, chrs, strlen16(chrs)); +} + +status_t String16::insert(size_t pos, const char16_t* chrs, size_t len) +{ + const size_t myLen = size(); + if (myLen == 0) { + return setTo(chrs, len); + return NO_ERROR; + } else if (len == 0) { + return NO_ERROR; + } + + if (pos > myLen) pos = myLen; + + #if 0 + printf("Insert in to %s: pos=%d, len=%d, myLen=%d, chrs=%s\n", + String8(*this).string(), pos, + len, myLen, String8(chrs, len).string()); + #endif + + SharedBuffer* buf = SharedBuffer::bufferFromData(mString) + ->editResize((myLen+len+1)*sizeof(char16_t)); + if (buf) { + char16_t* str = (char16_t*)buf->data(); + if (pos < myLen) { + memmove(str+pos+len, str+pos, (myLen-pos)*sizeof(char16_t)); + } + memcpy(str+pos, chrs, len*sizeof(char16_t)); + str[myLen+len] = 0; + mString = str; + #if 0 + printf("Result (%d chrs): %s\n", size(), String8(*this).string()); + #endif + return NO_ERROR; + } + return NO_MEMORY; +} + +ssize_t String16::findFirst(char16_t c) const +{ + const char16_t* str = string(); + const char16_t* p = str; + const char16_t* e = p + size(); + while (p < e) { + if (*p == c) { + return p-str; + } + p++; + } + return -1; +} + +ssize_t String16::findLast(char16_t c) const +{ + const char16_t* str = string(); + const char16_t* p = str; + const char16_t* e = p + size(); + while (p < e) { + e--; + if (*e == c) { + return e-str; + } + } + return -1; +} + +bool String16::startsWith(const String16& prefix) const +{ + const size_t ps = prefix.size(); + if (ps > size()) return false; + return strzcmp16(mString, ps, prefix.string(), ps) == 0; +} + +bool String16::startsWith(const char16_t* prefix) const +{ + const size_t ps = strlen16(prefix); + if (ps > size()) return false; + return strncmp16(mString, prefix, ps) == 0; +} + +status_t String16::makeLower() +{ + const size_t N = size(); + const char16_t* str = string(); + char16_t* edit = NULL; + for (size_t i=0; i<N; i++) { + const char16_t v = str[i]; + if (v >= 'A' && v <= 'Z') { + if (!edit) { + SharedBuffer* buf = SharedBuffer::bufferFromData(mString)->edit(); + if (!buf) { + return NO_MEMORY; + } + edit = (char16_t*)buf->data(); + mString = str = edit; + } + edit[i] = tolower((char)v); + } + } + return NO_ERROR; +} + +status_t String16::replaceAll(char16_t replaceThis, char16_t withThis) +{ + const size_t N = size(); + const char16_t* str = string(); + char16_t* edit = NULL; + for (size_t i=0; i<N; i++) { + if (str[i] == replaceThis) { + if (!edit) { + SharedBuffer* buf = SharedBuffer::bufferFromData(mString)->edit(); + if (!buf) { + return NO_MEMORY; + } + edit = (char16_t*)buf->data(); + mString = str = edit; + } + edit[i] = withThis; + } + } + return NO_ERROR; +} + +status_t String16::remove(size_t len, size_t begin) +{ + const size_t N = size(); + if (begin >= N) { + SharedBuffer::bufferFromData(mString)->release(); + mString = getEmptyString(); + return NO_ERROR; + } + if ((begin+len) > N) len = N-begin; + if (begin == 0 && len == N) { + return NO_ERROR; + } + + if (begin > 0) { + SharedBuffer* buf = SharedBuffer::bufferFromData(mString) + ->editResize((N+1)*sizeof(char16_t)); + if (!buf) { + return NO_MEMORY; + } + char16_t* str = (char16_t*)buf->data(); + memmove(str, str+begin, (N-begin+1)*sizeof(char16_t)); + mString = str; + } + SharedBuffer* buf = SharedBuffer::bufferFromData(mString) + ->editResize((len+1)*sizeof(char16_t)); + if (buf) { + char16_t* str = (char16_t*)buf->data(); + str[len] = 0; + mString = str; + return NO_ERROR; + } + return NO_MEMORY; +} + +TextOutput& operator<<(TextOutput& to, const String16& val) +{ + to << String8(val).string(); + return to; +} + +}; // namespace android diff --git a/libs/utils/String8.cpp b/libs/utils/String8.cpp new file mode 100644 index 0000000..ab843f6 --- /dev/null +++ b/libs/utils/String8.cpp @@ -0,0 +1,602 @@ +/* + * Copyright (C) 2005 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. + */ + +#include <utils/String8.h> + +#include <utils/Log.h> +#include <utils/String16.h> +#include <utils/TextOutput.h> +#include <utils/threads.h> + +#include <private/utils/Static.h> + +#include <ctype.h> + +namespace android { + +// --------------------------------------------------------------------------- + +static const uint32_t kByteMask = 0x000000BF; +static const uint32_t kByteMark = 0x00000080; + +// Surrogates aren't valid for UTF-32 characters, so define some +// constants that will let us screen them out. +static const uint32_t kUnicodeSurrogateHighStart = 0x0000D800; +static const uint32_t kUnicodeSurrogateHighEnd = 0x0000DBFF; +static const uint32_t kUnicodeSurrogateLowStart = 0x0000DC00; +static const uint32_t kUnicodeSurrogateLowEnd = 0x0000DFFF; +static const uint32_t kUnicodeSurrogateStart = kUnicodeSurrogateHighStart; +static const uint32_t kUnicodeSurrogateEnd = kUnicodeSurrogateLowEnd; + +// Mask used to set appropriate bits in first byte of UTF-8 sequence, +// indexed by number of bytes in the sequence. +static const uint32_t kFirstByteMark[] = { + 0x00000000, 0x00000000, 0x000000C0, 0x000000E0, 0x000000F0 +}; + +// Separator used by resource paths. This is not platform dependent contrary +// to OS_PATH_SEPARATOR. +#define RES_PATH_SEPARATOR '/' + +// Return number of utf8 bytes required for the character. +static size_t utf32_to_utf8_bytes(uint32_t srcChar) +{ + size_t bytesToWrite; + + // Figure out how many bytes the result will require. + if (srcChar < 0x00000080) + { + bytesToWrite = 1; + } + else if (srcChar < 0x00000800) + { + bytesToWrite = 2; + } + else if (srcChar < 0x00010000) + { + if ((srcChar < kUnicodeSurrogateStart) + || (srcChar > kUnicodeSurrogateEnd)) + { + bytesToWrite = 3; + } + else + { + // Surrogates are invalid UTF-32 characters. + return 0; + } + } + // Max code point for Unicode is 0x0010FFFF. + else if (srcChar < 0x00110000) + { + bytesToWrite = 4; + } + else + { + // Invalid UTF-32 character. + return 0; + } + + return bytesToWrite; +} + +// Write out the source character to <dstP>. + +static void utf32_to_utf8(uint8_t* dstP, uint32_t srcChar, size_t bytes) +{ + dstP += bytes; + switch (bytes) + { /* note: everything falls through. */ + case 4: *--dstP = (uint8_t)((srcChar | kByteMark) & kByteMask); srcChar >>= 6; + case 3: *--dstP = (uint8_t)((srcChar | kByteMark) & kByteMask); srcChar >>= 6; + case 2: *--dstP = (uint8_t)((srcChar | kByteMark) & kByteMask); srcChar >>= 6; + case 1: *--dstP = (uint8_t)(srcChar | kFirstByteMark[bytes]); + } +} + +// --------------------------------------------------------------------------- + +static SharedBuffer* gEmptyStringBuf = NULL; +static char* gEmptyString = NULL; + +extern int gDarwinCantLoadAllObjects; +int gDarwinIsReallyAnnoying; + +static inline char* getEmptyString() +{ + gEmptyStringBuf->acquire(); + return gEmptyString; +} + +void initialize_string8() +{ +#ifdef LIBUTILS_NATIVE + // Bite me, Darwin! + gDarwinIsReallyAnnoying = gDarwinCantLoadAllObjects; +#endif + + SharedBuffer* buf = SharedBuffer::alloc(1); + char* str = (char*)buf->data(); + *str = 0; + gEmptyStringBuf = buf; + gEmptyString = str; +} + +void terminate_string8() +{ + SharedBuffer::bufferFromData(gEmptyString)->release(); + gEmptyStringBuf = NULL; + gEmptyString = NULL; +} + +// --------------------------------------------------------------------------- + +static char* allocFromUTF8(const char* in, size_t len) +{ + if (len > 0) { + SharedBuffer* buf = SharedBuffer::alloc(len+1); + LOG_ASSERT(buf, "Unable to allocate shared buffer"); + if (buf) { + char* str = (char*)buf->data(); + memcpy(str, in, len); + str[len] = 0; + return str; + } + return NULL; + } + + return getEmptyString(); +} + +// Note: not dealing with expanding surrogate pairs. +static char* allocFromUTF16(const char16_t* in, size_t len) +{ + if (len == 0) return getEmptyString(); + + size_t bytes = 0; + const char16_t* end = in+len; + const char16_t* p = in; + + while (p < end) { + bytes += utf32_to_utf8_bytes(*p); + p++; + } + + SharedBuffer* buf = SharedBuffer::alloc(bytes+1); + LOG_ASSERT(buf, "Unable to allocate shared buffer"); + if (buf) { + p = in; + char* str = (char*)buf->data(); + char* d = str; + while (p < end) { + uint32_t c = *p++; + size_t len = utf32_to_utf8_bytes(c); + utf32_to_utf8((uint8_t*)d, c, len); + d += len; + } + *d = 0; + + return str; + } + + return getEmptyString(); +} + +// --------------------------------------------------------------------------- + +String8::String8() + : mString(getEmptyString()) +{ +} + +String8::String8(const String8& o) + : mString(o.mString) +{ + SharedBuffer::bufferFromData(mString)->acquire(); +} + +String8::String8(const char* o) + : mString(allocFromUTF8(o, strlen(o))) +{ + if (mString == NULL) { + mString = getEmptyString(); + } +} + +String8::String8(const char* o, size_t len) + : mString(allocFromUTF8(o, len)) +{ + if (mString == NULL) { + mString = getEmptyString(); + } +} + +String8::String8(const String16& o) + : mString(allocFromUTF16(o.string(), o.size())) +{ +} + +String8::String8(const char16_t* o) + : mString(allocFromUTF16(o, strlen16(o))) +{ +} + +String8::String8(const char16_t* o, size_t len) + : mString(allocFromUTF16(o, len)) +{ +} + +String8::~String8() +{ + SharedBuffer::bufferFromData(mString)->release(); +} + +void String8::setTo(const String8& other) +{ + SharedBuffer::bufferFromData(other.mString)->acquire(); + SharedBuffer::bufferFromData(mString)->release(); + mString = other.mString; +} + +status_t String8::setTo(const char* other) +{ + SharedBuffer::bufferFromData(mString)->release(); + mString = allocFromUTF8(other, strlen(other)); + if (mString) return NO_ERROR; + + mString = getEmptyString(); + return NO_MEMORY; +} + +status_t String8::setTo(const char* other, size_t len) +{ + SharedBuffer::bufferFromData(mString)->release(); + mString = allocFromUTF8(other, len); + if (mString) return NO_ERROR; + + mString = getEmptyString(); + return NO_MEMORY; +} + +status_t String8::setTo(const char16_t* other, size_t len) +{ + SharedBuffer::bufferFromData(mString)->release(); + mString = allocFromUTF16(other, len); + if (mString) return NO_ERROR; + + mString = getEmptyString(); + return NO_MEMORY; +} + +status_t String8::append(const String8& other) +{ + const size_t otherLen = other.bytes(); + if (bytes() == 0) { + setTo(other); + return NO_ERROR; + } else if (otherLen == 0) { + return NO_ERROR; + } + + return real_append(other.string(), otherLen); +} + +status_t String8::append(const char* other) +{ + return append(other, strlen(other)); +} + +status_t String8::append(const char* other, size_t otherLen) +{ + if (bytes() == 0) { + return setTo(other, otherLen); + } else if (otherLen == 0) { + return NO_ERROR; + } + + return real_append(other, otherLen); +} + +status_t String8::real_append(const char* other, size_t otherLen) +{ + const size_t myLen = bytes(); + + SharedBuffer* buf = SharedBuffer::bufferFromData(mString) + ->editResize(myLen+otherLen+1); + if (buf) { + char* str = (char*)buf->data(); + memcpy(str+myLen, other, otherLen+1); + mString = str; + return NO_ERROR; + } + return NO_MEMORY; +} + +char* String8::lockBuffer(size_t size) +{ + SharedBuffer* buf = SharedBuffer::bufferFromData(mString) + ->editResize(size+1); + if (buf) { + char* str = (char*)buf->data(); + mString = str; + return str; + } + return NULL; +} + +void String8::unlockBuffer() +{ + unlockBuffer(strlen(mString)); +} + +status_t String8::unlockBuffer(size_t size) +{ + if (size != this->size()) { + SharedBuffer* buf = SharedBuffer::bufferFromData(mString) + ->editResize(size+1); + if (buf) { + char* str = (char*)buf->data(); + str[size] = 0; + mString = str; + return NO_ERROR; + } + } + + return NO_MEMORY; +} + +ssize_t String8::find(const char* other, size_t start) const +{ + size_t len = size(); + if (start >= len) { + return -1; + } + const char* s = mString+start; + const char* p = strstr(s, other); + return p ? p-mString : -1; +} + +void String8::toLower() +{ + toLower(0, size()); +} + +void String8::toLower(size_t start, size_t length) +{ + const size_t len = size(); + if (start >= len) { + return; + } + if (start+length > len) { + length = len-start; + } + char* buf = lockBuffer(len); + buf += start; + while (length > 0) { + *buf = tolower(*buf); + buf++; + length--; + } + unlockBuffer(len); +} + +void String8::toUpper() +{ + toUpper(0, size()); +} + +void String8::toUpper(size_t start, size_t length) +{ + const size_t len = size(); + if (start >= len) { + return; + } + if (start+length > len) { + length = len-start; + } + char* buf = lockBuffer(len); + buf += start; + while (length > 0) { + *buf = toupper(*buf); + buf++; + length--; + } + unlockBuffer(len); +} + +TextOutput& operator<<(TextOutput& to, const String8& val) +{ + to << val.string(); + return to; +} + +// --------------------------------------------------------------------------- +// Path functions + + +void String8::setPathName(const char* name) +{ + setPathName(name, strlen(name)); +} + +void String8::setPathName(const char* name, size_t len) +{ + char* buf = lockBuffer(len); + + memcpy(buf, name, len); + + // remove trailing path separator, if present + if (len > 0 && buf[len-1] == OS_PATH_SEPARATOR) + len--; + + buf[len] = '\0'; + + unlockBuffer(len); +} + +String8 String8::getPathLeaf(void) const +{ + const char* cp; + const char*const buf = mString; + + cp = strrchr(buf, OS_PATH_SEPARATOR); + if (cp == NULL) + return String8(*this); + else + return String8(cp+1); +} + +String8 String8::getPathDir(void) const +{ + const char* cp; + const char*const str = mString; + + cp = strrchr(str, OS_PATH_SEPARATOR); + if (cp == NULL) + return String8(""); + else + return String8(str, cp - str); +} + +String8 String8::walkPath(String8* outRemains) const +{ + const char* cp; + const char*const str = mString; + const char* buf = str; + + cp = strchr(buf, OS_PATH_SEPARATOR); + if (cp == buf) { + // don't include a leading '/'. + buf = buf+1; + cp = strchr(buf, OS_PATH_SEPARATOR); + } + + if (cp == NULL) { + String8 res = buf != str ? String8(buf) : *this; + if (outRemains) *outRemains = String8(""); + return res; + } + + String8 res(buf, cp-buf); + if (outRemains) *outRemains = String8(cp+1); + return res; +} + +/* + * Helper function for finding the start of an extension in a pathname. + * + * Returns a pointer inside mString, or NULL if no extension was found. + */ +char* String8::find_extension(void) const +{ + const char* lastSlash; + const char* lastDot; + int extLen; + const char* const str = mString; + + // only look at the filename + lastSlash = strrchr(str, OS_PATH_SEPARATOR); + if (lastSlash == NULL) + lastSlash = str; + else + lastSlash++; + + // find the last dot + lastDot = strrchr(lastSlash, '.'); + if (lastDot == NULL) + return NULL; + + // looks good, ship it + return const_cast<char*>(lastDot); +} + +String8 String8::getPathExtension(void) const +{ + char* ext; + + ext = find_extension(); + if (ext != NULL) + return String8(ext); + else + return String8(""); +} + +String8 String8::getBasePath(void) const +{ + char* ext; + const char* const str = mString; + + ext = find_extension(); + if (ext == NULL) + return String8(*this); + else + return String8(str, ext - str); +} + +String8& String8::appendPath(const char* name) +{ + // TODO: The test below will fail for Win32 paths. Fix later or ignore. + if (name[0] != OS_PATH_SEPARATOR) { + if (*name == '\0') { + // nothing to do + return *this; + } + + size_t len = length(); + if (len == 0) { + // no existing filename, just use the new one + setPathName(name); + return *this; + } + + // make room for oldPath + '/' + newPath + int newlen = strlen(name); + + char* buf = lockBuffer(len+1+newlen); + + // insert a '/' if needed + if (buf[len-1] != OS_PATH_SEPARATOR) + buf[len++] = OS_PATH_SEPARATOR; + + memcpy(buf+len, name, newlen+1); + len += newlen; + + unlockBuffer(len); + + return *this; + } else { + setPathName(name); + return *this; + } +} + +String8& String8::convertToResPath() +{ +#if OS_PATH_SEPARATOR != RES_PATH_SEPARATOR + size_t len = length(); + if (len > 0) { + char * buf = lockBuffer(len); + for (char * end = buf + len; buf < end; ++buf) { + if (*buf == OS_PATH_SEPARATOR) + *buf = RES_PATH_SEPARATOR; + } + unlockBuffer(len); + } +#endif + return *this; +} + + +}; // namespace android diff --git a/libs/utils/SystemClock.cpp b/libs/utils/SystemClock.cpp new file mode 100644 index 0000000..2bdc0ce --- /dev/null +++ b/libs/utils/SystemClock.cpp @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2008 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. + */ + + +/* + * System clock functions. + */ + +#if HAVE_ANDROID_OS +#include <linux/ioctl.h> +#include <linux/rtc.h> +#include <utils/Atomic.h> +#include <linux/android_alarm.h> +#endif + +#include <sys/time.h> +#include <limits.h> +#include <fcntl.h> +#include <errno.h> +#include <string.h> + +#include <utils/SystemClock.h> +#include <utils/Timers.h> + +#define LOG_TAG "SystemClock" +#include "utils/Log.h" + +namespace android { + +/* + * Set the current time. This only works when running as root. + */ +int setCurrentTimeMillis(int64_t millis) +{ +#if WIN32 + // not implemented + return -1; +#else + struct timeval tv; +#if HAVE_ANDROID_OS + struct timespec ts; + int fd; + int res; +#endif + int ret = 0; + + if (millis <= 0 || millis / 1000LL >= INT_MAX) { + return -1; + } + + tv.tv_sec = (time_t) (millis / 1000LL); + tv.tv_usec = (suseconds_t) ((millis % 1000LL) * 1000LL); + + LOGD("Setting time of day to sec=%d\n", (int) tv.tv_sec); + +#if HAVE_ANDROID_OS + fd = open("/dev/alarm", O_RDWR); + if(fd < 0) { + LOGW("Unable to open alarm driver: %s\n", strerror(errno)); + return -1; + } + ts.tv_sec = tv.tv_sec; + ts.tv_nsec = tv.tv_usec * 1000; + res = ioctl(fd, ANDROID_ALARM_SET_RTC, &ts); + if(res < 0) { + LOGW("Unable to set rtc to %ld: %s\n", tv.tv_sec, strerror(errno)); + ret = -1; + } + close(fd); +#else + if (settimeofday(&tv, NULL) != 0) { + LOGW("Unable to set clock to %d.%d: %s\n", + (int) tv.tv_sec, (int) tv.tv_usec, strerror(errno)); + ret = -1; + } +#endif + + return ret; +#endif // WIN32 +} + +/* + * native public static long uptimeMillis(); + */ +int64_t uptimeMillis() +{ + int64_t when = systemTime(SYSTEM_TIME_MONOTONIC); + return (int64_t) nanoseconds_to_milliseconds(when); +} + +/* + * native public static long elapsedRealtime(); + */ +int64_t elapsedRealtime() +{ +#if HAVE_ANDROID_OS + static int s_fd = -1; + + if (s_fd == -1) { + int fd = open("/dev/alarm", O_RDONLY); + if (android_atomic_cmpxchg(-1, fd, &s_fd)) { + close(fd); + } + } + + struct timespec ts; + int result = ioctl(s_fd, + ANDROID_ALARM_GET_TIME(ANDROID_ALARM_ELAPSED_REALTIME), &ts); + + if (result == 0) { + int64_t when = seconds_to_nanoseconds(ts.tv_sec) + ts.tv_nsec; + return (int64_t) nanoseconds_to_milliseconds(when); + } else { + // XXX: there was an error, probably because the driver didn't + // exist ... this should return + // a real error, like an exception! + int64_t when = systemTime(SYSTEM_TIME_MONOTONIC); + return (int64_t) nanoseconds_to_milliseconds(when); + } +#else + int64_t when = systemTime(SYSTEM_TIME_MONOTONIC); + return (int64_t) nanoseconds_to_milliseconds(when); +#endif +} + +}; // namespace android diff --git a/libs/utils/TextOutput.cpp b/libs/utils/TextOutput.cpp new file mode 100644 index 0000000..cebee99 --- /dev/null +++ b/libs/utils/TextOutput.cpp @@ -0,0 +1,146 @@ +/* + * Copyright (C) 2005 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. + */ + +#include <utils/TextOutput.h> + +#include <utils/Debug.h> + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +// --------------------------------------------------------------------------- + +namespace android { + +TextOutput& operator<<(TextOutput& to, bool val) +{ + if (val) to.print("true", 4); + else to.print("false", 5); + return to; +} + +TextOutput& operator<<(TextOutput& to, int val) +{ + char buf[16]; + sprintf(buf, "%d", val); + to.print(buf, strlen(buf)); + return to; +} + +TextOutput& operator<<(TextOutput& to, long val) +{ + char buf[16]; + sprintf(buf, "%ld", val); + to.print(buf, strlen(buf)); + return to; +} + +TextOutput& operator<<(TextOutput& to, unsigned int val) +{ + char buf[16]; + sprintf(buf, "%u", val); + to.print(buf, strlen(buf)); + return to; +} + +TextOutput& operator<<(TextOutput& to, unsigned long val) +{ + char buf[16]; + sprintf(buf, "%lu", val); + to.print(buf, strlen(buf)); + return to; +} + +TextOutput& operator<<(TextOutput& to, long long val) +{ + char buf[32]; + sprintf(buf, "%Ld", val); + to.print(buf, strlen(buf)); + return to; +} + +TextOutput& operator<<(TextOutput& to, unsigned long long val) +{ + char buf[32]; + sprintf(buf, "%Lu", val); + to.print(buf, strlen(buf)); + return to; +} + +static TextOutput& print_float(TextOutput& to, double value) +{ + char buf[64]; + sprintf(buf, "%g", value); + if( !strchr(buf, '.') && !strchr(buf, 'e') && + !strchr(buf, 'E') ) { + strncat(buf, ".0", sizeof(buf)-1); + } + to.print(buf, strlen(buf)); + return to; +} + +TextOutput& operator<<(TextOutput& to, float val) +{ + return print_float(to,val); +} + +TextOutput& operator<<(TextOutput& to, double val) +{ + return print_float(to,val); +} + +TextOutput& operator<<(TextOutput& to, const void* val) +{ + char buf[16]; + sprintf(buf, "%p", val); + to.print(buf, strlen(buf)); + return to; +} + +static void textOutputPrinter(void* cookie, const char* txt) +{ + ((TextOutput*)cookie)->print(txt, strlen(txt)); +} + +TextOutput& operator<<(TextOutput& to, const TypeCode& val) +{ + printTypeCode(val.typeCode(), textOutputPrinter, (void*)&to); + return to; +} + +HexDump::HexDump(const void *buf, size_t size, size_t bytesPerLine) + : mBuffer(buf) + , mSize(size) + , mBytesPerLine(bytesPerLine) + , mSingleLineCutoff(16) + , mAlignment(4) + , mCArrayStyle(false) +{ + if (bytesPerLine >= 16) mAlignment = 4; + else if (bytesPerLine >= 8) mAlignment = 2; + else mAlignment = 1; +} + +TextOutput& operator<<(TextOutput& to, const HexDump& val) +{ + printHexData(0, val.buffer(), val.size(), val.bytesPerLine(), + val.singleLineCutoff(), val.alignment(), val.carrayStyle(), + textOutputPrinter, (void*)&to); + return to; +} + +}; // namespace android diff --git a/libs/utils/Threads.cpp b/libs/utils/Threads.cpp new file mode 100644 index 0000000..74271ba --- /dev/null +++ b/libs/utils/Threads.cpp @@ -0,0 +1,1126 @@ +/* + * Copyright (C) 2007 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. + */ + +#define LOG_TAG "libutils.threads" + +#include <utils/threads.h> +#include <utils/Log.h> + +#include <stdio.h> +#include <stdlib.h> +#include <memory.h> +#include <errno.h> +#include <assert.h> +#include <unistd.h> + +#if defined(HAVE_PTHREADS) +# include <pthread.h> +# include <sched.h> +# include <sys/resource.h> +#elif defined(HAVE_WIN32_THREADS) +# include <windows.h> +# include <stdint.h> +# include <process.h> +# define HAVE_CREATETHREAD // Cygwin, vs. HAVE__BEGINTHREADEX for MinGW +#endif + +#if defined(HAVE_FUTEX) +#include <private/utils/futex_synchro.h> +#endif + +#if defined(HAVE_PRCTL) +#include <sys/prctl.h> +#endif + +/* + * =========================================================================== + * Thread wrappers + * =========================================================================== + */ + +using namespace android; + +// ---------------------------------------------------------------------------- +#if defined(HAVE_PTHREADS) +#if 0 +#pragma mark - +#pragma mark PTHREAD +#endif +// ---------------------------------------------------------------------------- + +/* + * Create and run a new thead. + * + * We create it "detached", so it cleans up after itself. + */ + +typedef void* (*android_pthread_entry)(void*); + +struct thread_data_t { + thread_func_t entryFunction; + void* userData; + int priority; + char * threadName; + + // we use this trampoline when we need to set the priority with + // nice/setpriority. + static int trampoline(const thread_data_t* t) { + thread_func_t f = t->entryFunction; + void* u = t->userData; + int prio = t->priority; + char * name = t->threadName; + delete t; + setpriority(PRIO_PROCESS, 0, prio); + if (name) { +#if defined(HAVE_PRCTL) + // Mac OS doesn't have this, and we build libutil for the host too + int hasAt = 0; + int hasDot = 0; + char *s = name; + while (*s) { + if (*s == '.') hasDot = 1; + else if (*s == '@') hasAt = 1; + s++; + } + int len = s - name; + if (len < 15 || hasAt || !hasDot) { + s = name; + } else { + s = name + len - 15; + } + prctl(PR_SET_NAME, (unsigned long) s, 0, 0, 0); +#endif + free(name); + } + return f(u); + } +}; + +int androidCreateRawThreadEtc(android_thread_func_t entryFunction, + void *userData, + const char* threadName, + int32_t threadPriority, + size_t threadStackSize, + android_thread_id_t *threadId) +{ + pthread_attr_t attr; + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); + +#ifdef HAVE_ANDROID_OS /* valgrind is rejecting RT-priority create reqs */ + if (threadPriority != PRIORITY_DEFAULT || threadName != NULL) { + // We could avoid the trampoline if there was a way to get to the + // android_thread_id_t (pid) from pthread_t + thread_data_t* t = new thread_data_t; + t->priority = threadPriority; + t->threadName = threadName ? strdup(threadName) : NULL; + t->entryFunction = entryFunction; + t->userData = userData; + entryFunction = (android_thread_func_t)&thread_data_t::trampoline; + userData = t; + } +#endif + + if (threadStackSize) { + pthread_attr_setstacksize(&attr, threadStackSize); + } + + errno = 0; + pthread_t thread; + int result = pthread_create(&thread, &attr, + (android_pthread_entry)entryFunction, userData); + if (result != 0) { + LOGE("androidCreateRawThreadEtc failed (entry=%p, res=%d, errno=%d)\n" + "(android threadPriority=%d)", + entryFunction, result, errno, threadPriority); + return 0; + } + + if (threadId != NULL) { + *threadId = (android_thread_id_t)thread; // XXX: this is not portable + } + return 1; +} + +android_thread_id_t androidGetThreadId() +{ + return (android_thread_id_t)pthread_self(); +} + +// ---------------------------------------------------------------------------- +#elif defined(HAVE_WIN32_THREADS) +#if 0 +#pragma mark - +#pragma mark WIN32_THREADS +#endif +// ---------------------------------------------------------------------------- + +/* + * Trampoline to make us __stdcall-compliant. + * + * We're expected to delete "vDetails" when we're done. + */ +struct threadDetails { + int (*func)(void*); + void* arg; +}; +static __stdcall unsigned int threadIntermediary(void* vDetails) +{ + struct threadDetails* pDetails = (struct threadDetails*) vDetails; + int result; + + result = (*(pDetails->func))(pDetails->arg); + + delete pDetails; + + LOG(LOG_VERBOSE, "thread", "thread exiting\n"); + return (unsigned int) result; +} + +/* + * Create and run a new thread. + */ +static bool doCreateThread(android_thread_func_t fn, void* arg, android_thread_id_t *id) +{ + HANDLE hThread; + struct threadDetails* pDetails = new threadDetails; // must be on heap + unsigned int thrdaddr; + + pDetails->func = fn; + pDetails->arg = arg; + +#if defined(HAVE__BEGINTHREADEX) + hThread = (HANDLE) _beginthreadex(NULL, 0, threadIntermediary, pDetails, 0, + &thrdaddr); + if (hThread == 0) +#elif defined(HAVE_CREATETHREAD) + hThread = CreateThread(NULL, 0, + (LPTHREAD_START_ROUTINE) threadIntermediary, + (void*) pDetails, 0, (DWORD*) &thrdaddr); + if (hThread == NULL) +#endif + { + LOG(LOG_WARN, "thread", "WARNING: thread create failed\n"); + return false; + } + +#if defined(HAVE_CREATETHREAD) + /* close the management handle */ + CloseHandle(hThread); +#endif + + if (id != NULL) { + *id = (android_thread_id_t)thrdaddr; + } + + return true; +} + +int androidCreateRawThreadEtc(android_thread_func_t fn, + void *userData, + const char* threadName, + int32_t threadPriority, + size_t threadStackSize, + android_thread_id_t *threadId) +{ + return doCreateThread( fn, userData, threadId); +} + +android_thread_id_t androidGetThreadId() +{ + return (android_thread_id_t)GetCurrentThreadId(); +} + +// ---------------------------------------------------------------------------- +#else +#error "Threads not supported" +#endif + +// ---------------------------------------------------------------------------- + +#if 0 +#pragma mark - +#pragma mark Common Thread functions +#endif + +int androidCreateThread(android_thread_func_t fn, void* arg) +{ + return createThreadEtc(fn, arg); +} + +int androidCreateThreadGetID(android_thread_func_t fn, void *arg, android_thread_id_t *id) +{ + return createThreadEtc(fn, arg, "android:unnamed_thread", + PRIORITY_DEFAULT, 0, id); +} + +static android_create_thread_fn gCreateThreadFn = androidCreateRawThreadEtc; + +int androidCreateThreadEtc(android_thread_func_t entryFunction, + void *userData, + const char* threadName, + int32_t threadPriority, + size_t threadStackSize, + android_thread_id_t *threadId) +{ + return gCreateThreadFn(entryFunction, userData, threadName, + threadPriority, threadStackSize, threadId); +} + +void androidSetCreateThreadFunc(android_create_thread_fn func) +{ + gCreateThreadFn = func; +} + +namespace android { + +/* + * =========================================================================== + * Mutex class + * =========================================================================== + */ + +#if 0 +#pragma mark - +#pragma mark Mutex +#endif + +#if defined(HAVE_PTHREADS) && !defined(HAVE_FUTEX) +/* + * Simple pthread wrapper. + */ + +Mutex::Mutex() +{ + _init(); +} + +Mutex::Mutex(const char* name) +{ + // XXX: name not used for now + _init(); +} + +void Mutex::_init() +{ + pthread_mutex_t* pMutex = new pthread_mutex_t; + pthread_mutex_init(pMutex, NULL); + mState = pMutex; +} + +Mutex::~Mutex() +{ + delete (pthread_mutex_t*) mState; +} + +status_t Mutex::lock() +{ + int res; + while ((res=pthread_mutex_lock((pthread_mutex_t*) mState)) == EINTR) ; + return -res; +} + +void Mutex::unlock() +{ + pthread_mutex_unlock((pthread_mutex_t*) mState); +} + +status_t Mutex::tryLock() +{ + int res; + while ((res=pthread_mutex_trylock((pthread_mutex_t*) mState)) == EINTR) ; + return -res; +} + +#elif defined(HAVE_FUTEX) +#if 0 +#pragma mark - +#endif + +#define STATE ((futex_mutex_t*) (&mState)) + +Mutex::Mutex() +{ + _init(); +} + +Mutex::Mutex(const char* name) +{ + _init(); +} + +void +Mutex::_init() +{ + futex_mutex_init(STATE); +} + +Mutex::~Mutex() +{ +} + +status_t Mutex::lock() +{ + int res; + while ((res=futex_mutex_lock(STATE, FUTEX_WAIT_INFINITE)) == EINTR) ; + return -res; +} + +void Mutex::unlock() +{ + futex_mutex_unlock(STATE); +} + +status_t Mutex::tryLock() +{ + int res; + while ((res=futex_mutex_trylock(STATE)) == EINTR) ; + return -res; +} +#undef STATE + +#elif defined(HAVE_WIN32_THREADS) +#if 0 +#pragma mark - +#endif + +Mutex::Mutex() +{ + HANDLE hMutex; + + assert(sizeof(hMutex) == sizeof(mState)); + + hMutex = CreateMutex(NULL, FALSE, NULL); + mState = (void*) hMutex; +} + +Mutex::Mutex(const char* name) +{ + // XXX: name not used for now + HANDLE hMutex; + + hMutex = CreateMutex(NULL, FALSE, NULL); + mState = (void*) hMutex; +} + +Mutex::~Mutex() +{ + CloseHandle((HANDLE) mState); +} + +status_t Mutex::lock() +{ + DWORD dwWaitResult; + dwWaitResult = WaitForSingleObject((HANDLE) mState, INFINITE); + return dwWaitResult != WAIT_OBJECT_0 ? -1 : NO_ERROR; +} + +void Mutex::unlock() +{ + if (!ReleaseMutex((HANDLE) mState)) + LOG(LOG_WARN, "thread", "WARNING: bad result from unlocking mutex\n"); +} + +status_t Mutex::tryLock() +{ + DWORD dwWaitResult; + + dwWaitResult = WaitForSingleObject((HANDLE) mState, 0); + if (dwWaitResult != WAIT_OBJECT_0 && dwWaitResult != WAIT_TIMEOUT) + LOG(LOG_WARN, "thread", "WARNING: bad result from try-locking mutex\n"); + return (dwWaitResult == WAIT_OBJECT_0) ? 0 : -1; +} + +#else +#error "Somebody forgot to implement threads for this platform." +#endif + + +/* + * =========================================================================== + * Condition class + * =========================================================================== + */ + +#if 0 +#pragma mark - +#pragma mark Condition +#endif + +#if defined(HAVE_PTHREADS) && !defined(HAVE_FUTEX) + +/* + * Constructor. This is a simple pthread wrapper. + */ +Condition::Condition() +{ + pthread_cond_t* pCond = new pthread_cond_t; + + pthread_cond_init(pCond, NULL); + mState = pCond; +} + +/* + * Destructor. + */ +Condition::~Condition() +{ + pthread_cond_destroy((pthread_cond_t*) mState); + delete (pthread_cond_t*) mState; +} + +/* + * Wait on a condition variable. Lock the mutex before calling. + */ + +status_t Condition::wait(Mutex& mutex) +{ + assert(mutex.mState != NULL); + + int cc; + while ((cc = pthread_cond_wait((pthread_cond_t*)mState, + (pthread_mutex_t*) mutex.mState)) == EINTR) ; + return -cc; +} + +status_t Condition::wait(Mutex& mutex, nsecs_t abstime) +{ + assert(mutex.mState != NULL); + + struct timespec ts; + ts.tv_sec = abstime/1000000000; + ts.tv_nsec = abstime-(ts.tv_sec*1000000000); + + int cc; + while ((cc = pthread_cond_timedwait((pthread_cond_t*)mState, + (pthread_mutex_t*) mutex.mState, &ts)) == EINTR) ; + return -cc; +} + +status_t Condition::waitRelative(Mutex& mutex, nsecs_t reltime) +{ + return wait(mutex, systemTime()+reltime); +} + +/* + * Signal the condition variable, allowing one thread to continue. + */ +void Condition::signal() +{ + pthread_cond_signal((pthread_cond_t*) mState); +} + +/* + * Signal the condition variable, allowing all threads to continue. + */ +void Condition::broadcast() +{ + pthread_cond_broadcast((pthread_cond_t*) mState); +} + +#elif defined(HAVE_FUTEX) +#if 0 +#pragma mark - +#endif + +#define STATE ((futex_cond_t*) (&mState)) + +/* + * Constructor. This is a simple pthread wrapper. + */ +Condition::Condition() +{ + futex_cond_init(STATE); +} + +/* + * Destructor. + */ +Condition::~Condition() +{ +} + +/* + * Wait on a condition variable. Lock the mutex before calling. + */ + +status_t Condition::wait(Mutex& mutex) +{ + assert(mutex.mState != NULL); + + int res; + while ((res = futex_cond_wait(STATE, + (futex_mutex_t*)(&mutex.mState), FUTEX_WAIT_INFINITE)) == -EINTR) ; + + return -res; +} + +status_t Condition::wait(Mutex& mutex, nsecs_t abstime) +{ + nsecs_t reltime = abstime - systemTime(); + if (reltime <= 0) return true; + return waitRelative(mutex, reltime); +} + +status_t Condition::waitRelative(Mutex& mutex, nsecs_t reltime) +{ + assert(mutex.mState != NULL); + int res; + unsigned msec = ns2ms(reltime); + if(msec == 0) + return true; + // This code will not time out at the correct time if interrupted by signals + while ((res = futex_cond_wait(STATE, + (futex_mutex_t*)(&mutex.mState), msec)) == -EINTR) ; + return res; +} + +/* + * Signal the condition variable, allowing one thread to continue. + */ +void Condition::signal() +{ + futex_cond_signal(STATE); +} + +/* + * Signal the condition variable, allowing all threads to continue. + */ +void Condition::broadcast() +{ + futex_cond_broadcast(STATE); +} + +#undef STATE + +#elif defined(HAVE_WIN32_THREADS) +#if 0 +#pragma mark - +#endif + +/* + * Windows doesn't have a condition variable solution. It's possible + * to create one, but it's easy to get it wrong. For a discussion, and + * the origin of this implementation, see: + * + * http://www.cs.wustl.edu/~schmidt/win32-cv-1.html + * + * The implementation shown on the page does NOT follow POSIX semantics. + * As an optimization they require acquiring the external mutex before + * calling signal() and broadcast(), whereas POSIX only requires grabbing + * it before calling wait(). The implementation here has been un-optimized + * to have the correct behavior. + */ +typedef struct WinCondition { + // Number of waiting threads. + int waitersCount; + + // Serialize access to waitersCount. + CRITICAL_SECTION waitersCountLock; + + // Semaphore used to queue up threads waiting for the condition to + // become signaled. + HANDLE sema; + + // An auto-reset event used by the broadcast/signal thread to wait + // for all the waiting thread(s) to wake up and be released from + // the semaphore. + HANDLE waitersDone; + + // This mutex wouldn't be necessary if we required that the caller + // lock the external mutex before calling signal() and broadcast(). + // I'm trying to mimic pthread semantics though. + HANDLE internalMutex; + + // Keeps track of whether we were broadcasting or signaling. This + // allows us to optimize the code if we're just signaling. + bool wasBroadcast; + + status_t wait(WinCondition* condState, HANDLE hMutex, nsecs_t* abstime) + { + // Increment the wait count, avoiding race conditions. + EnterCriticalSection(&condState->waitersCountLock); + condState->waitersCount++; + //printf("+++ wait: incr waitersCount to %d (tid=%ld)\n", + // condState->waitersCount, getThreadId()); + LeaveCriticalSection(&condState->waitersCountLock); + + DWORD timeout = INFINITE; + if (abstime) { + nsecs_t reltime = *abstime - systemTime(); + if (reltime < 0) + reltime = 0; + timeout = reltime/1000000; + } + + // Atomically release the external mutex and wait on the semaphore. + DWORD res = + SignalObjectAndWait(hMutex, condState->sema, timeout, FALSE); + + //printf("+++ wait: awake (tid=%ld)\n", getThreadId()); + + // Reacquire lock to avoid race conditions. + EnterCriticalSection(&condState->waitersCountLock); + + // No longer waiting. + condState->waitersCount--; + + // Check to see if we're the last waiter after a broadcast. + bool lastWaiter = (condState->wasBroadcast && condState->waitersCount == 0); + + //printf("+++ wait: lastWaiter=%d (wasBc=%d wc=%d)\n", + // lastWaiter, condState->wasBroadcast, condState->waitersCount); + + LeaveCriticalSection(&condState->waitersCountLock); + + // If we're the last waiter thread during this particular broadcast + // then signal broadcast() that we're all awake. It'll drop the + // internal mutex. + if (lastWaiter) { + // Atomically signal the "waitersDone" event and wait until we + // can acquire the internal mutex. We want to do this in one step + // because it ensures that everybody is in the mutex FIFO before + // any thread has a chance to run. Without it, another thread + // could wake up, do work, and hop back in ahead of us. + SignalObjectAndWait(condState->waitersDone, condState->internalMutex, + INFINITE, FALSE); + } else { + // Grab the internal mutex. + WaitForSingleObject(condState->internalMutex, INFINITE); + } + + // Release the internal and grab the external. + ReleaseMutex(condState->internalMutex); + WaitForSingleObject(hMutex, INFINITE); + + return res == WAIT_OBJECT_0 ? NO_ERROR : -1; + } +} WinCondition; + +/* + * Constructor. Set up the WinCondition stuff. + */ +Condition::Condition() +{ + WinCondition* condState = new WinCondition; + + condState->waitersCount = 0; + condState->wasBroadcast = false; + // semaphore: no security, initial value of 0 + condState->sema = CreateSemaphore(NULL, 0, 0x7fffffff, NULL); + InitializeCriticalSection(&condState->waitersCountLock); + // auto-reset event, not signaled initially + condState->waitersDone = CreateEvent(NULL, FALSE, FALSE, NULL); + // used so we don't have to lock external mutex on signal/broadcast + condState->internalMutex = CreateMutex(NULL, FALSE, NULL); + + mState = condState; +} + +/* + * Destructor. Free Windows resources as well as our allocated storage. + */ +Condition::~Condition() +{ + WinCondition* condState = (WinCondition*) mState; + if (condState != NULL) { + CloseHandle(condState->sema); + CloseHandle(condState->waitersDone); + delete condState; + } +} + + +status_t Condition::wait(Mutex& mutex) +{ + WinCondition* condState = (WinCondition*) mState; + HANDLE hMutex = (HANDLE) mutex.mState; + + return ((WinCondition*)mState)->wait(condState, hMutex, NULL); +} + +status_t Condition::wait(Mutex& mutex, nsecs_t abstime) +{ + WinCondition* condState = (WinCondition*) mState; + HANDLE hMutex = (HANDLE) mutex.mState; + + return ((WinCondition*)mState)->wait(condState, hMutex, &abstime); +} + +status_t Condition::waitRelative(Mutex& mutex, nsecs_t reltime) +{ + return wait(mutex, systemTime()+reltime); +} + +/* + * Signal the condition variable, allowing one thread to continue. + */ +void Condition::signal() +{ + WinCondition* condState = (WinCondition*) mState; + + // Lock the internal mutex. This ensures that we don't clash with + // broadcast(). + WaitForSingleObject(condState->internalMutex, INFINITE); + + EnterCriticalSection(&condState->waitersCountLock); + bool haveWaiters = (condState->waitersCount > 0); + LeaveCriticalSection(&condState->waitersCountLock); + + // If no waiters, then this is a no-op. Otherwise, knock the semaphore + // down a notch. + if (haveWaiters) + ReleaseSemaphore(condState->sema, 1, 0); + + // Release internal mutex. + ReleaseMutex(condState->internalMutex); +} + +/* + * Signal the condition variable, allowing all threads to continue. + * + * First we have to wake up all threads waiting on the semaphore, then + * we wait until all of the threads have actually been woken before + * releasing the internal mutex. This ensures that all threads are woken. + */ +void Condition::broadcast() +{ + WinCondition* condState = (WinCondition*) mState; + + // Lock the internal mutex. This keeps the guys we're waking up + // from getting too far. + WaitForSingleObject(condState->internalMutex, INFINITE); + + EnterCriticalSection(&condState->waitersCountLock); + bool haveWaiters = false; + + if (condState->waitersCount > 0) { + haveWaiters = true; + condState->wasBroadcast = true; + } + + if (haveWaiters) { + // Wake up all the waiters. + ReleaseSemaphore(condState->sema, condState->waitersCount, 0); + + LeaveCriticalSection(&condState->waitersCountLock); + + // Wait for all awakened threads to acquire the counting semaphore. + // The last guy who was waiting sets this. + WaitForSingleObject(condState->waitersDone, INFINITE); + + // Reset wasBroadcast. (No crit section needed because nobody + // else can wake up to poke at it.) + condState->wasBroadcast = 0; + } else { + // nothing to do + LeaveCriticalSection(&condState->waitersCountLock); + } + + // Release internal mutex. + ReleaseMutex(condState->internalMutex); +} + +#else +#error "condition variables not supported on this platform" +#endif + + +/* + * =========================================================================== + * ReadWriteLock class + * =========================================================================== + */ + +#if 0 +#pragma mark - +#pragma mark ReadWriteLock +#endif + +/* + * Add a reader. Readers are nice. They share. + */ +void ReadWriteLock::lockForRead() +{ + mLock.lock(); + while (mNumWriters > 0) { + LOG(LOG_DEBUG, "thread", "+++ lockForRead: waiting\n"); + mReadWaiter.wait(mLock); + } + assert(mNumWriters == 0); + mNumReaders++; +#if defined(PRINT_RENDER_TIMES) + if (mNumReaders == 1) + mDebugTimer.start(); +#endif + mLock.unlock(); +} + +/* + * Try to add a reader. If it doesn't work right away, return "false". + */ +bool ReadWriteLock::tryLockForRead() +{ + mLock.lock(); + if (mNumWriters > 0) { + mLock.unlock(); + return false; + } + assert(mNumWriters == 0); + mNumReaders++; +#if defined(PRINT_RENDER_TIMES) + if (mNumReaders == 1) + mDebugTimer.start(); +#endif + mLock.unlock(); + return true; +} + +/* + * Remove a reader. + */ +void ReadWriteLock::unlockForRead() +{ + mLock.lock(); + if (mNumReaders == 0) { + LOG(LOG_WARN, "thread", + "WARNING: unlockForRead requested, but not locked\n"); + return; + } + assert(mNumReaders > 0); + assert(mNumWriters == 0); + mNumReaders--; + if (mNumReaders == 0) { // last reader? +#if defined(PRINT_RENDER_TIMES) + mDebugTimer.stop(); + printf(" rdlk held %.3f msec\n", + (double) mDebugTimer.durationUsecs() / 1000.0); +#endif + //printf("+++ signaling writers (if any)\n"); + mWriteWaiter.signal(); // wake one writer (if any) + } + mLock.unlock(); +} + +/* + * Add a writer. This requires exclusive access to the object. + */ +void ReadWriteLock::lockForWrite() +{ + mLock.lock(); + while (mNumReaders > 0 || mNumWriters > 0) { + LOG(LOG_DEBUG, "thread", "+++ lockForWrite: waiting\n"); + mWriteWaiter.wait(mLock); + } + assert(mNumReaders == 0); + assert(mNumWriters == 0); + mNumWriters++; +#if defined(PRINT_RENDER_TIMES) + mDebugTimer.start(); +#endif + mLock.unlock(); +} + +/* + * Try to add a writer. If it doesn't work right away, return "false". + */ +bool ReadWriteLock::tryLockForWrite() +{ + mLock.lock(); + if (mNumReaders > 0 || mNumWriters > 0) { + mLock.unlock(); + return false; + } + assert(mNumReaders == 0); + assert(mNumWriters == 0); + mNumWriters++; +#if defined(PRINT_RENDER_TIMES) + mDebugTimer.start(); +#endif + mLock.unlock(); + return true; +} + +/* + * Remove a writer. + */ +void ReadWriteLock::unlockForWrite() +{ + mLock.lock(); + if (mNumWriters == 0) { + LOG(LOG_WARN, "thread", + "WARNING: unlockForWrite requested, but not locked\n"); + return; + } + assert(mNumWriters == 1); + mNumWriters--; +#if defined(PRINT_RENDER_TIMES) + mDebugTimer.stop(); + //printf(" wrlk held %.3f msec\n", + // (double) mDebugTimer.durationUsecs() / 1000.0); +#endif + // mWriteWaiter.signal(); // should other writers get first dibs? + //printf("+++ signaling readers (if any)\n"); + mReadWaiter.broadcast(); // wake all readers (if any) + mLock.unlock(); +} + +// ---------------------------------------------------------------------------- + +#if 0 +#pragma mark - +#pragma mark Thread::Thread +#endif + +/* + * This is our thread object! + */ + +Thread::Thread(bool canCallJava) + : mCanCallJava(canCallJava), + mThread(thread_id_t(-1)), + mLock("Thread::mLock"), + mStatus(NO_ERROR), + mExitPending(false), mRunning(false) +{ +} + +Thread::~Thread() +{ +} + +status_t Thread::readyToRun() +{ + return NO_ERROR; +} + +status_t Thread::run(const char* name, int32_t priority, size_t stack) +{ + Mutex::Autolock _l(mLock); + + if (mRunning) { + // thread already started + return INVALID_OPERATION; + } + + // reset status and exitPending to their default value, so we can + // try again after an error happened (either below, or in readyToRun()) + mStatus = NO_ERROR; + mExitPending = false; + mThread = thread_id_t(-1); + + // hold a strong reference on ourself + mHoldSelf = this; + + bool res; + if (mCanCallJava) { + res = createThreadEtc(_threadLoop, + this, name, priority, stack, &mThread); + } else { + res = androidCreateRawThreadEtc(_threadLoop, + this, name, priority, stack, &mThread); + } + + if (res == false) { + mStatus = UNKNOWN_ERROR; // something happened! + mRunning = false; + mThread = thread_id_t(-1); + } + + if (mStatus < 0) { + // something happened, don't leak + mHoldSelf.clear(); + } + + return mStatus; +} + +int Thread::_threadLoop(void* user) +{ + Thread* const self = static_cast<Thread*>(user); + sp<Thread> strong(self->mHoldSelf); + wp<Thread> weak(strong); + self->mHoldSelf.clear(); + + // we're about to run... + self->mStatus = self->readyToRun(); + if (self->mStatus!=NO_ERROR || self->mExitPending) { + // pretend the thread never started... + self->mExitPending = false; + self->mRunning = false; + return 0; + } + + // thread is running now + self->mRunning = true; + + do { + bool result = self->threadLoop(); + if (result == false || self->mExitPending) { + self->mExitPending = true; + self->mLock.lock(); + self->mRunning = false; + self->mThreadExitedCondition.signal(); + self->mLock.unlock(); + break; + } + + // Release our strong reference, to let a chance to the thread + // to die a peaceful death. + strong.clear(); + // And immediately, reacquire a strong reference for the next loop + strong = weak.promote(); + } while(strong != 0); + + return 0; +} + +void Thread::requestExit() +{ + mExitPending = true; +} + +status_t Thread::requestExitAndWait() +{ + if (mStatus == OK) { + + if (mThread == getThreadId()) { + LOGW( + "Thread (this=%p): don't call waitForExit() from this " + "Thread object's thread. It's a guaranteed deadlock!", + this); + return WOULD_BLOCK; + } + + requestExit(); + + Mutex::Autolock _l(mLock); + while (mRunning == true) { + mThreadExitedCondition.wait(mLock); + } + mExitPending = false; + } + return mStatus; +} + +bool Thread::exitPending() const +{ + return mExitPending; +} + + + +}; // namespace android diff --git a/libs/utils/TimerProbe.cpp b/libs/utils/TimerProbe.cpp new file mode 100644 index 0000000..835480d --- /dev/null +++ b/libs/utils/TimerProbe.cpp @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2008 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. + */ + +#include <utils/TimerProbe.h> + +#if ENABLE_TIMER_PROBE + +#ifdef LOG_TAG +#undef LOG_TAG +#endif +#define LOG_TAG "time" + +namespace android { + +Vector<TimerProbe::Bucket> TimerProbe::gBuckets; +TimerProbe* TimerProbe::gExecuteChain; +int TimerProbe::gIndent; +timespec TimerProbe::gRealBase; + +TimerProbe::TimerProbe(const char tag[], int* slot) : mTag(tag) +{ + mNext = gExecuteChain; + gExecuteChain = this; + mIndent = gIndent; + gIndent += 1; + if (mIndent > 0) { + if (*slot == 0) { + int count = gBuckets.add(); + *slot = count; + Bucket& bucket = gBuckets.editItemAt(count); + memset(&bucket, 0, sizeof(Bucket)); + bucket.mTag = tag; + bucket.mSlotPtr = slot; + bucket.mIndent = mIndent; + } + mBucket = *slot; + } + clock_gettime(CLOCK_REALTIME, &mRealStart); + if (gRealBase.tv_sec == 0) + gRealBase = mRealStart; + clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &mPStart); + clock_gettime(CLOCK_THREAD_CPUTIME_ID, &mTStart); +} + +void TimerProbe::end() +{ + timespec realEnd, pEnd, tEnd; + clock_gettime(CLOCK_REALTIME, &realEnd); + clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &pEnd); + clock_gettime(CLOCK_THREAD_CPUTIME_ID, &tEnd); + print(realEnd, pEnd, tEnd); + mTag = NULL; +} + +TimerProbe::~TimerProbe() +{ + if (mTag != NULL) + end(); + gExecuteChain = mNext; + gIndent--; +} + + +uint32_t TimerProbe::ElapsedTime(const timespec& start, const timespec& end) +{ + int sec = end.tv_sec - start.tv_sec; + int nsec = end.tv_nsec - start.tv_nsec; + if (nsec < 0) { + sec--; + nsec += 1000000000; + } + return sec * 1000000 + nsec / 1000; +} + +void TimerProbe::print(const timespec& r, const timespec& p, + const timespec& t) const +{ + uint32_t es = ElapsedTime(gRealBase, mRealStart); + uint32_t er = ElapsedTime(mRealStart, r); + uint32_t ep = ElapsedTime(mPStart, p); + uint32_t et = ElapsedTime(mTStart, t); + if (mIndent > 0) { + Bucket& bucket = gBuckets.editItemAt(mBucket); + if (bucket.mStart == 0) + bucket.mStart = es; + bucket.mReal += er; + bucket.mProcess += ep; + bucket.mThread += et; + bucket.mCount++; + return; + } + int index = 0; + int buckets = gBuckets.size(); + int count = 1; + const char* tag = mTag; + int indent = mIndent; + do { + LOGD("%-30.30s: (%3d) %-5.*s time=%-10.3f real=%7dus process=%7dus (%3d%%) thread=%7dus (%3d%%)\n", + tag, count, indent > 5 ? 5 : indent, "+++++", es / 1000000.0, + er, ep, ep * 100 / er, et, et * 100 / er); + if (index >= buckets) + break; + Bucket& bucket = gBuckets.editItemAt(index); + count = bucket.mCount; + es = bucket.mStart; + er = bucket.mReal; + ep = bucket.mProcess; + et = bucket.mThread; + tag = bucket.mTag; + indent = bucket.mIndent; + *bucket.mSlotPtr = 0; + } while (++index); // always true + gBuckets.clear(); +} + +}; // namespace android + +#endif diff --git a/libs/utils/Timers.cpp b/libs/utils/Timers.cpp new file mode 100644 index 0000000..2abc811 --- /dev/null +++ b/libs/utils/Timers.cpp @@ -0,0 +1,240 @@ +/* + * Copyright (C) 2005 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. + */ + +// +// Timer functions. +// +#include <utils/Timers.h> +#include <utils/ported.h> // may need usleep +#include <utils/Log.h> + +#include <stdlib.h> +#include <stdio.h> +#include <unistd.h> +#include <sys/time.h> +#include <time.h> +#include <errno.h> + +#ifdef HAVE_WIN32_THREADS +#include <windows.h> +#endif + +nsecs_t systemTime(int clock) +{ +#if defined(HAVE_POSIX_CLOCKS) + static const clockid_t clocks[] = { + CLOCK_REALTIME, + CLOCK_MONOTONIC, + CLOCK_PROCESS_CPUTIME_ID, + CLOCK_THREAD_CPUTIME_ID + }; + struct timespec t; + t.tv_sec = t.tv_nsec = 0; + clock_gettime(clocks[clock], &t); + return nsecs_t(t.tv_sec)*1000000000LL + t.tv_nsec; +#else + // we don't support the clocks here. + struct timeval t; + t.tv_sec = t.tv_usec = 0; + gettimeofday(&t, NULL); + return nsecs_t(t.tv_sec)*1000000000LL + nsecs_t(t.tv_usec)*1000LL; +#endif +} + +//#define MONITOR_USLEEP + +/* + * Sleep long enough that we'll wake up "interval" milliseconds after + * the previous snooze. + * + * The "nextTick" argument is updated on each call, and should be passed + * in every time. Set its fields to zero on the first call. + * + * Returns the #of intervals we have overslept, which will be zero if we're + * on time. [Currently just returns 0 or 1.] + */ +int sleepForInterval(long interval, struct timeval* pNextTick) +{ + struct timeval now; + long long timeBeforeNext; + long sleepTime = 0; + bool overSlept = false; + //int usleepBias = 0; + +#ifdef USLEEP_BIAS + /* + * Linux likes to add 9000ms or so. + * [not using this for now] + */ + //usleepBias = USLEEP_BIAS; +#endif + + gettimeofday(&now, NULL); + + if (pNextTick->tv_sec == 0) { + /* special-case for first time through */ + *pNextTick = now; + sleepTime = interval; + android::DurationTimer::addToTimeval(pNextTick, interval); + } else { + /* + * Compute how much time there is before the next tick. If this + * value is negative, we've run over. If we've run over a little + * bit we can shorten the next frame to keep the pace steady, but + * if we've dramatically overshot we need to re-sync. + */ + timeBeforeNext = android::DurationTimer::subtractTimevals(pNextTick, &now); + //printf("TOP: now=%ld.%ld next=%ld.%ld diff=%ld\n", + // now.tv_sec, now.tv_usec, pNextTick->tv_sec, pNextTick->tv_usec, + // (long) timeBeforeNext); + if (timeBeforeNext < -interval) { + /* way over */ + overSlept = true; + sleepTime = 0; + *pNextTick = now; + } else if (timeBeforeNext <= 0) { + /* slightly over, keep the pace steady */ + overSlept = true; + sleepTime = 0; + } else if (timeBeforeNext <= interval) { + /* right on schedule */ + sleepTime = timeBeforeNext; + } else if (timeBeforeNext > interval && timeBeforeNext <= 2*interval) { + /* sleep call returned early; do a longer sleep this time */ + sleepTime = timeBeforeNext; + } else if (timeBeforeNext > interval) { + /* we went back in time -- somebody updated system clock? */ + /* (could also be a *seriously* broken usleep()) */ + LOG(LOG_DEBUG, "", + " Impossible: timeBeforeNext = %ld\n", (long)timeBeforeNext); + sleepTime = 0; + *pNextTick = now; + } + android::DurationTimer::addToTimeval(pNextTick, interval); + } + //printf(" Before sleep: now=%ld.%ld next=%ld.%ld sleepTime=%ld\n", + // now.tv_sec, now.tv_usec, pNextTick->tv_sec, pNextTick->tv_usec, + // sleepTime); + + /* + * Sleep for the designated period of time. + * + * Linux tends to sleep for longer than requested, often by 17-18ms. + * MinGW tends to sleep for less than requested, by as much as 14ms, + * but occasionally oversleeps for 40+ms (looks like some external + * factors plus round-off on a 64Hz clock). Cygwin is pretty steady. + * + * If you start the MinGW version, and then launch the Cygwin version, + * the MinGW clock becomes more erratic. Not entirely sure why. + * + * (There's a lot of stuff here; it's really just a usleep() call with + * a bunch of instrumentation.) + */ + if (sleepTime > 0) { +#if defined(MONITOR_USLEEP) + struct timeval before, after; + long long actual; + + gettimeofday(&before, NULL); + usleep((long) sleepTime); + gettimeofday(&after, NULL); + + /* check usleep() accuracy; default Linux threads are pretty sloppy */ + actual = android::DurationTimer::subtractTimevals(&after, &before); + if ((long) actual < sleepTime - 14000 /*(sleepTime/10)*/ || + (long) actual > sleepTime + 20000 /*(sleepTime/10)*/) + { + LOG(LOG_DEBUG, "", " Odd usleep: req=%ld, actual=%ld\n", sleepTime, + (long) actual); + } +#else +#ifdef HAVE_WIN32_THREADS + Sleep( sleepTime/1000 ); +#else + usleep((long) sleepTime); +#endif +#endif + } + + //printf("slept %d\n", sleepTime); + + if (overSlept) + return 1; // close enough + else + return 0; +} + + +/* + * =========================================================================== + * DurationTimer + * =========================================================================== + */ + +using namespace android; + +// Start the timer. +void DurationTimer::start(void) +{ + gettimeofday(&mStartWhen, NULL); +} + +// Stop the timer. +void DurationTimer::stop(void) +{ + gettimeofday(&mStopWhen, NULL); +} + +// Get the duration in microseconds. +long long DurationTimer::durationUsecs(void) const +{ + return (long) subtractTimevals(&mStopWhen, &mStartWhen); +} + +// Subtract two timevals. Returns the difference (ptv1-ptv2) in +// microseconds. +/*static*/ long long DurationTimer::subtractTimevals(const struct timeval* ptv1, + const struct timeval* ptv2) +{ + long long stop = ((long long) ptv1->tv_sec) * 1000000LL + + ((long long) ptv1->tv_usec); + long long start = ((long long) ptv2->tv_sec) * 1000000LL + + ((long long) ptv2->tv_usec); + return stop - start; +} + +// Add the specified amount of time to the timeval. +/*static*/ void DurationTimer::addToTimeval(struct timeval* ptv, long usec) +{ + if (usec < 0) { + LOG(LOG_WARN, "", "Negative values not supported in addToTimeval\n"); + return; + } + + // normalize tv_usec if necessary + if (ptv->tv_usec >= 1000000) { + ptv->tv_sec += ptv->tv_usec / 1000000; + ptv->tv_usec %= 1000000; + } + + ptv->tv_usec += usec % 1000000; + if (ptv->tv_usec >= 1000000) { + ptv->tv_usec -= 1000000; + ptv->tv_sec++; + } + ptv->tv_sec += usec / 1000000; +} + diff --git a/libs/utils/Unicode.cpp b/libs/utils/Unicode.cpp new file mode 100644 index 0000000..33f535f --- /dev/null +++ b/libs/utils/Unicode.cpp @@ -0,0 +1,193 @@ +/* + * Copyright (C) 2008 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. + */ + +#include "utils/AndroidUnicode.h" +#include "characterData.h" + +#define LOG_TAG "Unicode" +#include "utils/Log.h" + +// ICU headers for using macros +#include <unicode/utf16.h> + +#define MIN_RADIX 2 +#define MAX_RADIX 36 + +#define TYPE_SHIFT 0 +#define TYPE_MASK ((1<<5)-1) + +#define DIRECTION_SHIFT (TYPE_SHIFT+5) +#define DIRECTION_MASK ((1<<5)-1) + +#define MIRRORED_SHIFT (DIRECTION_SHIFT+5) +#define MIRRORED_MASK ((1<<1)-1) + +#define TOUPPER_SHIFT (MIRRORED_SHIFT+1) +#define TOUPPER_MASK ((1<<6)-1) + +#define TOLOWER_SHIFT (TOUPPER_SHIFT+6) +#define TOLOWER_MASK ((1<<6)-1) + +#define TOTITLE_SHIFT (TOLOWER_SHIFT+6) +#define TOTITLE_MASK ((1<<2)-1) + +#define MIRROR_SHIFT (TOTITLE_SHIFT+2) +#define MIRROR_MASK ((1<<5)-1) + +#define NUMERIC_SHIFT (TOTITLE_SHIFT+2) +#define NUMERIC_MASK ((1<<7)-1) + +#define DECOMPOSITION_SHIFT (11) +#define DECOMPOSITION_MASK ((1<<5)-1) + +/* + * Returns the value stored in the CharacterData tables that contains + * an index into the packed data table and the decomposition type. + */ +static uint16_t findCharacterValue(UChar32 c) +{ + LOG_ASSERT(c >= 0 && c <= 0x10FFFF, "findCharacterValue received an invalid codepoint"); + if (c < 256) + return CharacterData::LATIN1_DATA[c]; + + // Rotate the bits because the tables are separated into even and odd codepoints + c = (c >> 1) | ((c & 1) << 20); + + CharacterData::Range search = CharacterData::FULL_DATA[c >> 16]; + const uint32_t* array = search.array; + + // This trick is so that that compare in the while loop does not + // need to shift the array entry down by 16 + c <<= 16; + c |= 0xFFFF; + + int high = (int)search.length - 1; + int low = 0; + + if (high < 0) + return 0; + + while (low < high - 1) + { + int probe = (high + low) >> 1; + + // The entries contain the codepoint in the high 16 bits and the index + // into PACKED_DATA in the low 16. + if (array[probe] > (unsigned)c) + high = probe; + else + low = probe; + } + + LOG_ASSERT((array[low] <= (unsigned)c), "A suitable range was not found"); + return array[low] & 0xFFFF; +} + +uint32_t android::Unicode::getPackedData(UChar32 c) +{ + // findCharacterValue returns a 16-bit value with the top 5 bits containing a decomposition type + // and the remaining bits containing an index. + return CharacterData::PACKED_DATA[findCharacterValue(c) & 0x7FF]; +} + +android::Unicode::CharType android::Unicode::getType(UChar32 c) +{ + if (c < 0 || c >= 0x10FFFF) + return CHARTYPE_UNASSIGNED; + return (CharType)((getPackedData(c) >> TYPE_SHIFT) & TYPE_MASK); +} + +android::Unicode::DecompositionType android::Unicode::getDecompositionType(UChar32 c) +{ + // findCharacterValue returns a 16-bit value with the top 5 bits containing a decomposition type + // and the remaining bits containing an index. + return (DecompositionType)((findCharacterValue(c) >> DECOMPOSITION_SHIFT) & DECOMPOSITION_MASK); +} + +int android::Unicode::getDigitValue(UChar32 c, int radix) +{ + if (radix < MIN_RADIX || radix > MAX_RADIX) + return -1; + + int tempValue = radix; + + if (c >= '0' && c <= '9') + tempValue = c - '0'; + else if (c >= 'a' && c <= 'z') + tempValue = c - 'a' + 10; + else if (c >= 'A' && c <= 'Z') + tempValue = c - 'A' + 10; + + return tempValue < radix ? tempValue : -1; +} + +int android::Unicode::getNumericValue(UChar32 c) +{ + if (isMirrored(c)) + return -1; + + return (int) CharacterData::NUMERICS[((getPackedData(c) >> NUMERIC_SHIFT) & NUMERIC_MASK)]; +} + +UChar32 android::Unicode::toLower(UChar32 c) +{ + return c + CharacterData::LCDIFF[(getPackedData(c) >> TOLOWER_SHIFT) & TOLOWER_MASK]; +} + +UChar32 android::Unicode::toUpper(UChar32 c) +{ + return c + CharacterData::UCDIFF[(getPackedData(c) >> TOUPPER_SHIFT) & TOUPPER_MASK]; +} + +android::Unicode::Direction android::Unicode::getDirectionality(UChar32 c) +{ + uint32_t data = getPackedData(c); + + if (0 == data) + return DIRECTIONALITY_UNDEFINED; + + Direction d = (Direction) ((data >> DIRECTION_SHIFT) & DIRECTION_MASK); + + if (DIRECTION_MASK == d) + return DIRECTIONALITY_UNDEFINED; + + return d; +} + +bool android::Unicode::isMirrored(UChar32 c) +{ + return ((getPackedData(c) >> MIRRORED_SHIFT) & MIRRORED_MASK) != 0; +} + +UChar32 android::Unicode::toMirror(UChar32 c) +{ + if (!isMirrored(c)) + return c; + + return c + CharacterData::MIRROR_DIFF[(getPackedData(c) >> MIRROR_SHIFT) & MIRROR_MASK]; +} + +UChar32 android::Unicode::toTitle(UChar32 c) +{ + int32_t diff = CharacterData::TCDIFF[(getPackedData(c) >> TOTITLE_SHIFT) & TOTITLE_MASK]; + + if (TOTITLE_MASK == diff) + return toUpper(c); + + return c + diff; +} + + diff --git a/libs/utils/VectorImpl.cpp b/libs/utils/VectorImpl.cpp new file mode 100644 index 0000000..2c2d667 --- /dev/null +++ b/libs/utils/VectorImpl.cpp @@ -0,0 +1,611 @@ +/* + * Copyright (C) 2005 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. + */ + +#define LOG_TAG "Vector" + +#include <string.h> +#include <stdlib.h> +#include <stdio.h> + +#include <utils/Log.h> +#include <utils/Errors.h> +#include <utils/SharedBuffer.h> +#include <utils/VectorImpl.h> + +/*****************************************************************************/ + + +namespace android { + +// ---------------------------------------------------------------------------- + +const size_t kMinVectorCapacity = 4; + +static inline size_t max(size_t a, size_t b) { + return a>b ? a : b; +} + +// ---------------------------------------------------------------------------- + +VectorImpl::VectorImpl(size_t itemSize, uint32_t flags) + : mStorage(0), mCount(0), mFlags(flags), mItemSize(itemSize) +{ +} + +VectorImpl::VectorImpl(const VectorImpl& rhs) + : mStorage(rhs.mStorage), mCount(rhs.mCount), + mFlags(rhs.mFlags), mItemSize(rhs.mItemSize) +{ + if (mStorage) { + SharedBuffer::sharedBuffer(mStorage)->acquire(); + } +} + +VectorImpl::~VectorImpl() +{ + LOG_ASSERT(!mCount, + "[%p] " + "subclasses of VectorImpl must call finish_vector()" + " in their destructor. Leaking %d bytes.", + this, (int)(mCount*mItemSize)); + // We can't call _do_destroy() here because the vtable is already gone. +} + +VectorImpl& VectorImpl::operator = (const VectorImpl& rhs) +{ + LOG_ASSERT(mItemSize == rhs.mItemSize, + "Vector<> have different types (this=%p, rhs=%p)", this, &rhs); + if (this != &rhs) { + release_storage(); + if (rhs.mCount) { + mStorage = rhs.mStorage; + mCount = rhs.mCount; + SharedBuffer::sharedBuffer(mStorage)->acquire(); + } else { + mStorage = 0; + mCount = 0; + } + } + return *this; +} + +void* VectorImpl::editArrayImpl() +{ + if (mStorage) { + SharedBuffer* sb = SharedBuffer::sharedBuffer(mStorage)->attemptEdit(); + if (sb == 0) { + sb = SharedBuffer::alloc(capacity() * mItemSize); + if (sb) { + _do_copy(sb->data(), mStorage, mCount); + release_storage(); + mStorage = sb->data(); + } + } + } + return mStorage; +} + +size_t VectorImpl::capacity() const +{ + if (mStorage) { + return SharedBuffer::sharedBuffer(mStorage)->size() / mItemSize; + } + return 0; +} + +ssize_t VectorImpl::insertVectorAt(const VectorImpl& vector, size_t index) +{ + if (index > size()) + return BAD_INDEX; + void* where = _grow(index, vector.size()); + if (where) { + _do_copy(where, vector.arrayImpl(), vector.size()); + } + return where ? index : (ssize_t)NO_MEMORY; +} + +ssize_t VectorImpl::appendVector(const VectorImpl& vector) +{ + return insertVectorAt(vector, size()); +} + +ssize_t VectorImpl::insertAt(size_t index, size_t numItems) +{ + return insertAt(0, index, numItems); +} + +ssize_t VectorImpl::insertAt(const void* item, size_t index, size_t numItems) +{ + if (index > size()) + return BAD_INDEX; + void* where = _grow(index, numItems); + if (where) { + if (item) { + _do_splat(where, item, numItems); + } else { + _do_construct(where, numItems); + } + } + return where ? index : (ssize_t)NO_MEMORY; +} + +static int sortProxy(const void* lhs, const void* rhs, void* func) +{ + return (*(VectorImpl::compar_t)func)(lhs, rhs); +} + +status_t VectorImpl::sort(VectorImpl::compar_t cmp) +{ + return sort(sortProxy, (void*)cmp); +} + +status_t VectorImpl::sort(VectorImpl::compar_r_t cmp, void* state) +{ + // the sort must be stable. we're using insertion sort which + // is well suited for small and already sorted arrays + // for big arrays, it could be better to use mergesort + const ssize_t count = size(); + if (count > 1) { + void* array = const_cast<void*>(arrayImpl()); + void* temp = 0; + ssize_t i = 1; + while (i < count) { + void* item = reinterpret_cast<char*>(array) + mItemSize*(i); + void* curr = reinterpret_cast<char*>(array) + mItemSize*(i-1); + if (cmp(curr, item, state) > 0) { + + if (!temp) { + // we're going to have to modify the array... + array = editArrayImpl(); + if (!array) return NO_MEMORY; + temp = malloc(mItemSize); + if (!temp) return NO_MEMORY; + _do_construct(temp, 1); + item = reinterpret_cast<char*>(array) + mItemSize*(i); + curr = reinterpret_cast<char*>(array) + mItemSize*(i-1); + } + + _do_copy(temp, item, 1); + + ssize_t j = i-1; + void* next = reinterpret_cast<char*>(array) + mItemSize*(i); + do { + _do_copy(next, curr, 1); + next = curr; + --j; + curr = reinterpret_cast<char*>(array) + mItemSize*(j); + } while (j>=0 && (cmp(curr, temp, state) > 0)); + + _do_copy(next, temp, 1); + } + i++; + } + + if (temp) { + _do_destroy(temp, 1); + free(temp); + } + } + return NO_ERROR; +} + +void VectorImpl::pop() +{ + if (size()) + removeItemsAt(size()-1, 1); +} + +void VectorImpl::push() +{ + push(0); +} + +void VectorImpl::push(const void* item) +{ + insertAt(item, size()); +} + +ssize_t VectorImpl::add() +{ + return add(0); +} + +ssize_t VectorImpl::add(const void* item) +{ + return insertAt(item, size()); +} + +ssize_t VectorImpl::replaceAt(size_t index) +{ + return replaceAt(0, index); +} + +ssize_t VectorImpl::replaceAt(const void* prototype, size_t index) +{ + LOG_ASSERT(index<size(), + "[%p] replace: index=%d, size=%d", this, (int)index, (int)size()); + + void* item = editItemLocation(index); + if (item == 0) + return NO_MEMORY; + _do_destroy(item, 1); + if (prototype == 0) { + _do_construct(item, 1); + } else { + _do_copy(item, prototype, 1); + } + return ssize_t(index); +} + +ssize_t VectorImpl::removeItemsAt(size_t index, size_t count) +{ + LOG_ASSERT((index+count)<=size(), + "[%p] remove: index=%d, count=%d, size=%d", + this, (int)index, (int)count, (int)size()); + + if ((index+count) > size()) + return BAD_VALUE; + _shrink(index, count); + return index; +} + +void VectorImpl::finish_vector() +{ + release_storage(); + mStorage = 0; + mCount = 0; +} + +void VectorImpl::clear() +{ + _shrink(0, mCount); +} + +void* VectorImpl::editItemLocation(size_t index) +{ + LOG_ASSERT(index<capacity(), + "[%p] itemLocation: index=%d, capacity=%d, count=%d", + this, (int)index, (int)capacity(), (int)mCount); + + void* buffer = editArrayImpl(); + if (buffer) + return reinterpret_cast<char*>(buffer) + index*mItemSize; + return 0; +} + +const void* VectorImpl::itemLocation(size_t index) const +{ + LOG_ASSERT(index<capacity(), + "[%p] editItemLocation: index=%d, capacity=%d, count=%d", + this, (int)index, (int)capacity(), (int)mCount); + + const void* buffer = arrayImpl(); + if (buffer) + return reinterpret_cast<const char*>(buffer) + index*mItemSize; + return 0; +} + +ssize_t VectorImpl::setCapacity(size_t new_capacity) +{ + size_t current_capacity = capacity(); + ssize_t amount = new_capacity - size(); + if (amount <= 0) { + // we can't reduce the capacity + return current_capacity; + } + SharedBuffer* sb = SharedBuffer::alloc(new_capacity * mItemSize); + if (sb) { + void* array = sb->data(); + _do_copy(array, mStorage, size()); + release_storage(); + mStorage = const_cast<void*>(array); + } else { + return NO_MEMORY; + } + return new_capacity; +} + +void VectorImpl::release_storage() +{ + if (mStorage) { + const SharedBuffer* sb = SharedBuffer::sharedBuffer(mStorage); + if (sb->release(SharedBuffer::eKeepStorage) == 1) { + _do_destroy(mStorage, mCount); + SharedBuffer::dealloc(sb); + } + } +} + +void* VectorImpl::_grow(size_t where, size_t amount) +{ +// LOGV("_grow(this=%p, where=%d, amount=%d) count=%d, capacity=%d", +// this, (int)where, (int)amount, (int)mCount, (int)capacity()); + + if (where > mCount) + where = mCount; + + const size_t new_size = mCount + amount; + if (capacity() < new_size) { + const size_t new_capacity = max(kMinVectorCapacity, ((new_size*3)+1)/2); +// LOGV("grow vector %p, new_capacity=%d", this, (int)new_capacity); + if ((mStorage) && + (mCount==where) && + (mFlags & HAS_TRIVIAL_COPY) && + (mFlags & HAS_TRIVIAL_DTOR)) + { + const SharedBuffer* cur_sb = SharedBuffer::sharedBuffer(mStorage); + SharedBuffer* sb = cur_sb->editResize(new_capacity * mItemSize); + mStorage = sb->data(); + } else { + SharedBuffer* sb = SharedBuffer::alloc(new_capacity * mItemSize); + if (sb) { + void* array = sb->data(); + if (where>0) { + _do_copy(array, mStorage, where); + } + if (mCount>where) { + const void* from = reinterpret_cast<const uint8_t *>(mStorage) + where*mItemSize; + void* dest = reinterpret_cast<uint8_t *>(array) + (where+amount)*mItemSize; + _do_copy(dest, from, mCount-where); + } + release_storage(); + mStorage = const_cast<void*>(array); + } + } + } else { + ssize_t s = mCount-where; + if (s>0) { + void* array = editArrayImpl(); + void* to = reinterpret_cast<uint8_t *>(array) + (where+amount)*mItemSize; + const void* from = reinterpret_cast<const uint8_t *>(array) + where*mItemSize; + _do_move_forward(to, from, s); + } + } + mCount += amount; + void* free_space = const_cast<void*>(itemLocation(where)); + return free_space; +} + +void VectorImpl::_shrink(size_t where, size_t amount) +{ + if (!mStorage) + return; + +// LOGV("_shrink(this=%p, where=%d, amount=%d) count=%d, capacity=%d", +// this, (int)where, (int)amount, (int)mCount, (int)capacity()); + + if (where >= mCount) + where = mCount - amount; + + const size_t new_size = mCount - amount; + if (new_size*3 < capacity()) { + const size_t new_capacity = max(kMinVectorCapacity, new_size*2); +// LOGV("shrink vector %p, new_capacity=%d", this, (int)new_capacity); + if ((where == mCount-amount) && + (mFlags & HAS_TRIVIAL_COPY) && + (mFlags & HAS_TRIVIAL_DTOR)) + { + const SharedBuffer* cur_sb = SharedBuffer::sharedBuffer(mStorage); + SharedBuffer* sb = cur_sb->editResize(new_capacity * mItemSize); + mStorage = sb->data(); + } else { + SharedBuffer* sb = SharedBuffer::alloc(new_capacity * mItemSize); + if (sb) { + void* array = sb->data(); + if (where>0) { + _do_copy(array, mStorage, where); + } + if (mCount > where+amount) { + const void* from = reinterpret_cast<const uint8_t *>(mStorage) + (where+amount)*mItemSize; + void* dest = reinterpret_cast<uint8_t *>(array) + where*mItemSize; + _do_copy(dest, from, mCount-(where+amount)); + } + release_storage(); + mStorage = const_cast<void*>(array); + } + } + } else { + void* array = editArrayImpl(); + void* to = reinterpret_cast<uint8_t *>(array) + where*mItemSize; + _do_destroy(to, amount); + ssize_t s = mCount-(where+amount); + if (s>0) { + const void* from = reinterpret_cast<uint8_t *>(array) + (where+amount)*mItemSize; + _do_move_backward(to, from, s); + } + } + + // adjust the number of items... + mCount -= amount; +} + +size_t VectorImpl::itemSize() const { + return mItemSize; +} + +void VectorImpl::_do_construct(void* storage, size_t num) const +{ + if (!(mFlags & HAS_TRIVIAL_CTOR)) { + do_construct(storage, num); + } +} + +void VectorImpl::_do_destroy(void* storage, size_t num) const +{ + if (!(mFlags & HAS_TRIVIAL_DTOR)) { + do_destroy(storage, num); + } +} + +void VectorImpl::_do_copy(void* dest, const void* from, size_t num) const +{ + if (!(mFlags & HAS_TRIVIAL_COPY)) { + do_copy(dest, from, num); + } else { + memcpy(dest, from, num*itemSize()); + } +} + +void VectorImpl::_do_splat(void* dest, const void* item, size_t num) const { + do_splat(dest, item, num); +} + +void VectorImpl::_do_move_forward(void* dest, const void* from, size_t num) const { + do_move_forward(dest, from, num); +} + +void VectorImpl::_do_move_backward(void* dest, const void* from, size_t num) const { + do_move_backward(dest, from, num); +} + +void VectorImpl::reservedVectorImpl1() { } +void VectorImpl::reservedVectorImpl2() { } +void VectorImpl::reservedVectorImpl3() { } +void VectorImpl::reservedVectorImpl4() { } +void VectorImpl::reservedVectorImpl5() { } +void VectorImpl::reservedVectorImpl6() { } +void VectorImpl::reservedVectorImpl7() { } +void VectorImpl::reservedVectorImpl8() { } + +/*****************************************************************************/ + +SortedVectorImpl::SortedVectorImpl(size_t itemSize, uint32_t flags) + : VectorImpl(itemSize, flags) +{ +} + +SortedVectorImpl::SortedVectorImpl(const VectorImpl& rhs) +: VectorImpl(rhs) +{ +} + +SortedVectorImpl::~SortedVectorImpl() +{ +} + +SortedVectorImpl& SortedVectorImpl::operator = (const SortedVectorImpl& rhs) +{ + return static_cast<SortedVectorImpl&>( VectorImpl::operator = (static_cast<const VectorImpl&>(rhs)) ); +} + +ssize_t SortedVectorImpl::indexOf(const void* item) const +{ + return _indexOrderOf(item); +} + +size_t SortedVectorImpl::orderOf(const void* item) const +{ + size_t o; + _indexOrderOf(item, &o); + return o; +} + +ssize_t SortedVectorImpl::_indexOrderOf(const void* item, size_t* order) const +{ + // binary search + ssize_t err = NAME_NOT_FOUND; + ssize_t l = 0; + ssize_t h = size()-1; + ssize_t mid; + const void* a = arrayImpl(); + const size_t s = itemSize(); + while (l <= h) { + mid = l + (h - l)/2; + const void* const curr = reinterpret_cast<const char *>(a) + (mid*s); + const int c = do_compare(curr, item); + if (c == 0) { + err = l = mid; + break; + } else if (c < 0) { + l = mid + 1; + } else { + h = mid - 1; + } + } + if (order) *order = l; + return err; +} + +ssize_t SortedVectorImpl::add(const void* item) +{ + size_t order; + ssize_t index = _indexOrderOf(item, &order); + if (index < 0) { + index = VectorImpl::insertAt(item, order, 1); + } else { + index = VectorImpl::replaceAt(item, index); + } + return index; +} + +ssize_t SortedVectorImpl::merge(const VectorImpl& vector) +{ + // naive merge... + if (!vector.isEmpty()) { + const void* buffer = vector.arrayImpl(); + const size_t is = itemSize(); + size_t s = vector.size(); + for (size_t i=0 ; i<s ; i++) { + ssize_t err = add( reinterpret_cast<const char*>(buffer) + i*is ); + if (err<0) { + return err; + } + } + } + return NO_ERROR; +} + +ssize_t SortedVectorImpl::merge(const SortedVectorImpl& vector) +{ + // we've merging a sorted vector... nice! + ssize_t err = NO_ERROR; + if (!vector.isEmpty()) { + // first take care of the case where the vectors are sorted together + if (do_compare(vector.itemLocation(vector.size()-1), arrayImpl()) <= 0) { + err = VectorImpl::insertVectorAt(static_cast<const VectorImpl&>(vector), 0); + } else if (do_compare(vector.arrayImpl(), itemLocation(size()-1)) >= 0) { + err = VectorImpl::appendVector(static_cast<const VectorImpl&>(vector)); + } else { + // this could be made a little better + err = merge(static_cast<const VectorImpl&>(vector)); + } + } + return err; +} + +ssize_t SortedVectorImpl::remove(const void* item) +{ + ssize_t i = indexOf(item); + if (i>=0) { + VectorImpl::removeItemsAt(i, 1); + } + return i; +} + +void SortedVectorImpl::reservedSortedVectorImpl1() { }; +void SortedVectorImpl::reservedSortedVectorImpl2() { }; +void SortedVectorImpl::reservedSortedVectorImpl3() { }; +void SortedVectorImpl::reservedSortedVectorImpl4() { }; +void SortedVectorImpl::reservedSortedVectorImpl5() { }; +void SortedVectorImpl::reservedSortedVectorImpl6() { }; +void SortedVectorImpl::reservedSortedVectorImpl7() { }; +void SortedVectorImpl::reservedSortedVectorImpl8() { }; + + +/*****************************************************************************/ + +}; // namespace android + diff --git a/libs/utils/ZipEntry.cpp b/libs/utils/ZipEntry.cpp new file mode 100644 index 0000000..fbc9e67 --- /dev/null +++ b/libs/utils/ZipEntry.cpp @@ -0,0 +1,696 @@ +/* + * 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. + */ + +// +// Access to entries in a Zip archive. +// + +#define LOG_TAG "zip" + +#include "utils/ZipEntry.h" +#include "utils/Log.h" + +#include <stdio.h> +#include <string.h> +#include <assert.h> + +using namespace android; + +/* + * Initialize a new ZipEntry structure from a FILE* positioned at a + * CentralDirectoryEntry. + * + * On exit, the file pointer will be at the start of the next CDE or + * at the EOCD. + */ +status_t ZipEntry::initFromCDE(FILE* fp) +{ + status_t result; + long posn; + bool hasDD; + + //LOGV("initFromCDE ---\n"); + + /* read the CDE */ + result = mCDE.read(fp); + if (result != NO_ERROR) { + LOGD("mCDE.read failed\n"); + return result; + } + + //mCDE.dump(); + + /* using the info in the CDE, go load up the LFH */ + posn = ftell(fp); + if (fseek(fp, mCDE.mLocalHeaderRelOffset, SEEK_SET) != 0) { + LOGD("local header seek failed (%ld)\n", + mCDE.mLocalHeaderRelOffset); + return UNKNOWN_ERROR; + } + + result = mLFH.read(fp); + if (result != NO_ERROR) { + LOGD("mLFH.read failed\n"); + return result; + } + + if (fseek(fp, posn, SEEK_SET) != 0) + return UNKNOWN_ERROR; + + //mLFH.dump(); + + /* + * We *might* need to read the Data Descriptor at this point and + * integrate it into the LFH. If this bit is set, the CRC-32, + * compressed size, and uncompressed size will be zero. In practice + * these seem to be rare. + */ + hasDD = (mLFH.mGPBitFlag & kUsesDataDescr) != 0; + if (hasDD) { + // do something clever + //LOGD("+++ has data descriptor\n"); + } + + /* + * Sanity-check the LFH. Note that this will fail if the "kUsesDataDescr" + * flag is set, because the LFH is incomplete. (Not a problem, since we + * prefer the CDE values.) + */ + if (!hasDD && !compareHeaders()) { + LOGW("WARNING: header mismatch\n"); + // keep going? + } + + /* + * If the mVersionToExtract is greater than 20, we may have an + * issue unpacking the record -- could be encrypted, compressed + * with something we don't support, or use Zip64 extensions. We + * can defer worrying about that to when we're extracting data. + */ + + return NO_ERROR; +} + +/* + * Initialize a new entry. Pass in the file name and an optional comment. + * + * Initializes the CDE and the LFH. + */ +void ZipEntry::initNew(const char* fileName, const char* comment) +{ + assert(fileName != NULL && *fileName != '\0'); // name required + + /* most fields are properly initialized by constructor */ + mCDE.mVersionMadeBy = kDefaultMadeBy; + mCDE.mVersionToExtract = kDefaultVersion; + mCDE.mCompressionMethod = kCompressStored; + mCDE.mFileNameLength = strlen(fileName); + if (comment != NULL) + mCDE.mFileCommentLength = strlen(comment); + mCDE.mExternalAttrs = 0x81b60020; // matches what WinZip does + + if (mCDE.mFileNameLength > 0) { + mCDE.mFileName = new unsigned char[mCDE.mFileNameLength+1]; + strcpy((char*) mCDE.mFileName, fileName); + } + if (mCDE.mFileCommentLength > 0) { + /* TODO: stop assuming null-terminated ASCII here? */ + mCDE.mFileComment = new unsigned char[mCDE.mFileCommentLength+1]; + strcpy((char*) mCDE.mFileComment, comment); + } + + copyCDEtoLFH(); +} + +/* + * Initialize a new entry, starting with the ZipEntry from a different + * archive. + * + * Initializes the CDE and the LFH. + */ +status_t ZipEntry::initFromExternal(const ZipFile* pZipFile, + const ZipEntry* pEntry) +{ + /* + * Copy everything in the CDE over, then fix up the hairy bits. + */ + memcpy(&mCDE, &pEntry->mCDE, sizeof(mCDE)); + + if (mCDE.mFileNameLength > 0) { + mCDE.mFileName = new unsigned char[mCDE.mFileNameLength+1]; + if (mCDE.mFileName == NULL) + return NO_MEMORY; + strcpy((char*) mCDE.mFileName, (char*)pEntry->mCDE.mFileName); + } + if (mCDE.mFileCommentLength > 0) { + mCDE.mFileComment = new unsigned char[mCDE.mFileCommentLength+1]; + if (mCDE.mFileComment == NULL) + return NO_MEMORY; + strcpy((char*) mCDE.mFileComment, (char*)pEntry->mCDE.mFileComment); + } + if (mCDE.mExtraFieldLength > 0) { + /* we null-terminate this, though it may not be a string */ + mCDE.mExtraField = new unsigned char[mCDE.mExtraFieldLength+1]; + if (mCDE.mExtraField == NULL) + return NO_MEMORY; + memcpy(mCDE.mExtraField, pEntry->mCDE.mExtraField, + mCDE.mExtraFieldLength+1); + } + + /* construct the LFH from the CDE */ + copyCDEtoLFH(); + + /* + * The LFH "extra" field is independent of the CDE "extra", so we + * handle it here. + */ + assert(mLFH.mExtraField == NULL); + mLFH.mExtraFieldLength = pEntry->mLFH.mExtraFieldLength; + if (mLFH.mExtraFieldLength > 0) { + mLFH.mExtraField = new unsigned char[mLFH.mExtraFieldLength+1]; + if (mLFH.mExtraField == NULL) + return NO_MEMORY; + memcpy(mLFH.mExtraField, pEntry->mLFH.mExtraField, + mLFH.mExtraFieldLength+1); + } + + return NO_ERROR; +} + +/* + * Insert pad bytes in the LFH by tweaking the "extra" field. This will + * potentially confuse something that put "extra" data in here earlier, + * but I can't find an actual problem. + */ +status_t ZipEntry::addPadding(int padding) +{ + if (padding <= 0) + return INVALID_OPERATION; + + //LOGI("HEY: adding %d pad bytes to existing %d in %s\n", + // padding, mLFH.mExtraFieldLength, mCDE.mFileName); + + if (mLFH.mExtraFieldLength > 0) { + /* extend existing field */ + unsigned char* newExtra; + + newExtra = new unsigned char[mLFH.mExtraFieldLength + padding]; + if (newExtra == NULL) + return NO_MEMORY; + memset(newExtra + mLFH.mExtraFieldLength, 0, padding); + memcpy(newExtra, mLFH.mExtraField, mLFH.mExtraFieldLength); + + delete[] mLFH.mExtraField; + mLFH.mExtraField = newExtra; + mLFH.mExtraFieldLength += padding; + } else { + /* create new field */ + mLFH.mExtraField = new unsigned char[padding]; + memset(mLFH.mExtraField, 0, padding); + mLFH.mExtraFieldLength = padding; + } + + return NO_ERROR; +} + +/* + * Set the fields in the LFH equal to the corresponding fields in the CDE. + * + * This does not touch the LFH "extra" field. + */ +void ZipEntry::copyCDEtoLFH(void) +{ + mLFH.mVersionToExtract = mCDE.mVersionToExtract; + mLFH.mGPBitFlag = mCDE.mGPBitFlag; + mLFH.mCompressionMethod = mCDE.mCompressionMethod; + mLFH.mLastModFileTime = mCDE.mLastModFileTime; + mLFH.mLastModFileDate = mCDE.mLastModFileDate; + mLFH.mCRC32 = mCDE.mCRC32; + mLFH.mCompressedSize = mCDE.mCompressedSize; + mLFH.mUncompressedSize = mCDE.mUncompressedSize; + mLFH.mFileNameLength = mCDE.mFileNameLength; + // the "extra field" is independent + + delete[] mLFH.mFileName; + if (mLFH.mFileNameLength > 0) { + mLFH.mFileName = new unsigned char[mLFH.mFileNameLength+1]; + strcpy((char*) mLFH.mFileName, (const char*) mCDE.mFileName); + } else { + mLFH.mFileName = NULL; + } +} + +/* + * Set some information about a file after we add it. + */ +void ZipEntry::setDataInfo(long uncompLen, long compLen, unsigned long crc32, + int compressionMethod) +{ + mCDE.mCompressionMethod = compressionMethod; + mCDE.mCRC32 = crc32; + mCDE.mCompressedSize = compLen; + mCDE.mUncompressedSize = uncompLen; + mCDE.mCompressionMethod = compressionMethod; + if (compressionMethod == kCompressDeflated) { + mCDE.mGPBitFlag |= 0x0002; // indicates maximum compression used + } + copyCDEtoLFH(); +} + +/* + * See if the data in mCDE and mLFH match up. This is mostly useful for + * debugging these classes, but it can be used to identify damaged + * archives. + * + * Returns "false" if they differ. + */ +bool ZipEntry::compareHeaders(void) const +{ + if (mCDE.mVersionToExtract != mLFH.mVersionToExtract) { + LOGV("cmp: VersionToExtract\n"); + return false; + } + if (mCDE.mGPBitFlag != mLFH.mGPBitFlag) { + LOGV("cmp: GPBitFlag\n"); + return false; + } + if (mCDE.mCompressionMethod != mLFH.mCompressionMethod) { + LOGV("cmp: CompressionMethod\n"); + return false; + } + if (mCDE.mLastModFileTime != mLFH.mLastModFileTime) { + LOGV("cmp: LastModFileTime\n"); + return false; + } + if (mCDE.mLastModFileDate != mLFH.mLastModFileDate) { + LOGV("cmp: LastModFileDate\n"); + return false; + } + if (mCDE.mCRC32 != mLFH.mCRC32) { + LOGV("cmp: CRC32\n"); + return false; + } + if (mCDE.mCompressedSize != mLFH.mCompressedSize) { + LOGV("cmp: CompressedSize\n"); + return false; + } + if (mCDE.mUncompressedSize != mLFH.mUncompressedSize) { + LOGV("cmp: UncompressedSize\n"); + return false; + } + if (mCDE.mFileNameLength != mLFH.mFileNameLength) { + LOGV("cmp: FileNameLength\n"); + return false; + } +#if 0 // this seems to be used for padding, not real data + if (mCDE.mExtraFieldLength != mLFH.mExtraFieldLength) { + LOGV("cmp: ExtraFieldLength\n"); + return false; + } +#endif + if (mCDE.mFileName != NULL) { + if (strcmp((char*) mCDE.mFileName, (char*) mLFH.mFileName) != 0) { + LOGV("cmp: FileName\n"); + return false; + } + } + + return true; +} + + +/* + * Convert the DOS date/time stamp into a UNIX time stamp. + */ +time_t ZipEntry::getModWhen(void) const +{ + struct tm parts; + + parts.tm_sec = (mCDE.mLastModFileTime & 0x001f) << 1; + parts.tm_min = (mCDE.mLastModFileTime & 0x07e0) >> 5; + parts.tm_hour = (mCDE.mLastModFileTime & 0xf800) >> 11; + parts.tm_mday = (mCDE.mLastModFileDate & 0x001f); + parts.tm_mon = ((mCDE.mLastModFileDate & 0x01e0) >> 5) -1; + parts.tm_year = ((mCDE.mLastModFileDate & 0xfe00) >> 9) + 80; + parts.tm_wday = parts.tm_yday = 0; + parts.tm_isdst = -1; // DST info "not available" + + return mktime(&parts); +} + +/* + * Set the CDE/LFH timestamp from UNIX time. + */ +void ZipEntry::setModWhen(time_t when) +{ +#ifdef HAVE_LOCALTIME_R + struct tm tmResult; +#endif + time_t even; + unsigned short zdate, ztime; + + struct tm* ptm; + + /* round up to an even number of seconds */ + even = (time_t)(((unsigned long)(when) + 1) & (~1)); + + /* expand */ +#ifdef HAVE_LOCALTIME_R + ptm = localtime_r(&even, &tmResult); +#else + ptm = localtime(&even); +#endif + + int year; + year = ptm->tm_year; + if (year < 80) + year = 80; + + zdate = (year - 80) << 9 | (ptm->tm_mon+1) << 5 | ptm->tm_mday; + ztime = ptm->tm_hour << 11 | ptm->tm_min << 5 | ptm->tm_sec >> 1; + + mCDE.mLastModFileTime = mLFH.mLastModFileTime = ztime; + mCDE.mLastModFileDate = mLFH.mLastModFileDate = zdate; +} + + +/* + * =========================================================================== + * ZipEntry::LocalFileHeader + * =========================================================================== + */ + +/* + * Read a local file header. + * + * On entry, "fp" points to the signature at the start of the header. + * On exit, "fp" points to the start of data. + */ +status_t ZipEntry::LocalFileHeader::read(FILE* fp) +{ + status_t result = NO_ERROR; + unsigned char buf[kLFHLen]; + + assert(mFileName == NULL); + assert(mExtraField == NULL); + + if (fread(buf, 1, kLFHLen, fp) != kLFHLen) { + result = UNKNOWN_ERROR; + goto bail; + } + + if (ZipEntry::getLongLE(&buf[0x00]) != kSignature) { + LOGD("whoops: didn't find expected signature\n"); + result = UNKNOWN_ERROR; + goto bail; + } + + mVersionToExtract = ZipEntry::getShortLE(&buf[0x04]); + mGPBitFlag = ZipEntry::getShortLE(&buf[0x06]); + mCompressionMethod = ZipEntry::getShortLE(&buf[0x08]); + mLastModFileTime = ZipEntry::getShortLE(&buf[0x0a]); + mLastModFileDate = ZipEntry::getShortLE(&buf[0x0c]); + mCRC32 = ZipEntry::getLongLE(&buf[0x0e]); + mCompressedSize = ZipEntry::getLongLE(&buf[0x12]); + mUncompressedSize = ZipEntry::getLongLE(&buf[0x16]); + mFileNameLength = ZipEntry::getShortLE(&buf[0x1a]); + mExtraFieldLength = ZipEntry::getShortLE(&buf[0x1c]); + + // TODO: validate sizes + + /* grab filename */ + if (mFileNameLength != 0) { + mFileName = new unsigned char[mFileNameLength+1]; + if (mFileName == NULL) { + result = NO_MEMORY; + goto bail; + } + if (fread(mFileName, 1, mFileNameLength, fp) != mFileNameLength) { + result = UNKNOWN_ERROR; + goto bail; + } + mFileName[mFileNameLength] = '\0'; + } + + /* grab extra field */ + if (mExtraFieldLength != 0) { + mExtraField = new unsigned char[mExtraFieldLength+1]; + if (mExtraField == NULL) { + result = NO_MEMORY; + goto bail; + } + if (fread(mExtraField, 1, mExtraFieldLength, fp) != mExtraFieldLength) { + result = UNKNOWN_ERROR; + goto bail; + } + mExtraField[mExtraFieldLength] = '\0'; + } + +bail: + return result; +} + +/* + * Write a local file header. + */ +status_t ZipEntry::LocalFileHeader::write(FILE* fp) +{ + unsigned char buf[kLFHLen]; + + ZipEntry::putLongLE(&buf[0x00], kSignature); + ZipEntry::putShortLE(&buf[0x04], mVersionToExtract); + ZipEntry::putShortLE(&buf[0x06], mGPBitFlag); + ZipEntry::putShortLE(&buf[0x08], mCompressionMethod); + ZipEntry::putShortLE(&buf[0x0a], mLastModFileTime); + ZipEntry::putShortLE(&buf[0x0c], mLastModFileDate); + ZipEntry::putLongLE(&buf[0x0e], mCRC32); + ZipEntry::putLongLE(&buf[0x12], mCompressedSize); + ZipEntry::putLongLE(&buf[0x16], mUncompressedSize); + ZipEntry::putShortLE(&buf[0x1a], mFileNameLength); + ZipEntry::putShortLE(&buf[0x1c], mExtraFieldLength); + + if (fwrite(buf, 1, kLFHLen, fp) != kLFHLen) + return UNKNOWN_ERROR; + + /* write filename */ + if (mFileNameLength != 0) { + if (fwrite(mFileName, 1, mFileNameLength, fp) != mFileNameLength) + return UNKNOWN_ERROR; + } + + /* write "extra field" */ + if (mExtraFieldLength != 0) { + if (fwrite(mExtraField, 1, mExtraFieldLength, fp) != mExtraFieldLength) + return UNKNOWN_ERROR; + } + + return NO_ERROR; +} + + +/* + * Dump the contents of a LocalFileHeader object. + */ +void ZipEntry::LocalFileHeader::dump(void) const +{ + LOGD(" LocalFileHeader contents:\n"); + LOGD(" versToExt=%u gpBits=0x%04x compression=%u\n", + mVersionToExtract, mGPBitFlag, mCompressionMethod); + LOGD(" modTime=0x%04x modDate=0x%04x crc32=0x%08lx\n", + mLastModFileTime, mLastModFileDate, mCRC32); + LOGD(" compressedSize=%lu uncompressedSize=%lu\n", + mCompressedSize, mUncompressedSize); + LOGD(" filenameLen=%u extraLen=%u\n", + mFileNameLength, mExtraFieldLength); + if (mFileName != NULL) + LOGD(" filename: '%s'\n", mFileName); +} + + +/* + * =========================================================================== + * ZipEntry::CentralDirEntry + * =========================================================================== + */ + +/* + * Read the central dir entry that appears next in the file. + * + * On entry, "fp" should be positioned on the signature bytes for the + * entry. On exit, "fp" will point at the signature word for the next + * entry or for the EOCD. + */ +status_t ZipEntry::CentralDirEntry::read(FILE* fp) +{ + status_t result = NO_ERROR; + unsigned char buf[kCDELen]; + + /* no re-use */ + assert(mFileName == NULL); + assert(mExtraField == NULL); + assert(mFileComment == NULL); + + if (fread(buf, 1, kCDELen, fp) != kCDELen) { + result = UNKNOWN_ERROR; + goto bail; + } + + if (ZipEntry::getLongLE(&buf[0x00]) != kSignature) { + LOGD("Whoops: didn't find expected signature\n"); + result = UNKNOWN_ERROR; + goto bail; + } + + mVersionMadeBy = ZipEntry::getShortLE(&buf[0x04]); + mVersionToExtract = ZipEntry::getShortLE(&buf[0x06]); + mGPBitFlag = ZipEntry::getShortLE(&buf[0x08]); + mCompressionMethod = ZipEntry::getShortLE(&buf[0x0a]); + mLastModFileTime = ZipEntry::getShortLE(&buf[0x0c]); + mLastModFileDate = ZipEntry::getShortLE(&buf[0x0e]); + mCRC32 = ZipEntry::getLongLE(&buf[0x10]); + mCompressedSize = ZipEntry::getLongLE(&buf[0x14]); + mUncompressedSize = ZipEntry::getLongLE(&buf[0x18]); + mFileNameLength = ZipEntry::getShortLE(&buf[0x1c]); + mExtraFieldLength = ZipEntry::getShortLE(&buf[0x1e]); + mFileCommentLength = ZipEntry::getShortLE(&buf[0x20]); + mDiskNumberStart = ZipEntry::getShortLE(&buf[0x22]); + mInternalAttrs = ZipEntry::getShortLE(&buf[0x24]); + mExternalAttrs = ZipEntry::getLongLE(&buf[0x26]); + mLocalHeaderRelOffset = ZipEntry::getLongLE(&buf[0x2a]); + + // TODO: validate sizes and offsets + + /* grab filename */ + if (mFileNameLength != 0) { + mFileName = new unsigned char[mFileNameLength+1]; + if (mFileName == NULL) { + result = NO_MEMORY; + goto bail; + } + if (fread(mFileName, 1, mFileNameLength, fp) != mFileNameLength) { + result = UNKNOWN_ERROR; + goto bail; + } + mFileName[mFileNameLength] = '\0'; + } + + /* read "extra field" */ + if (mExtraFieldLength != 0) { + mExtraField = new unsigned char[mExtraFieldLength+1]; + if (mExtraField == NULL) { + result = NO_MEMORY; + goto bail; + } + if (fread(mExtraField, 1, mExtraFieldLength, fp) != mExtraFieldLength) { + result = UNKNOWN_ERROR; + goto bail; + } + mExtraField[mExtraFieldLength] = '\0'; + } + + + /* grab comment, if any */ + if (mFileCommentLength != 0) { + mFileComment = new unsigned char[mFileCommentLength+1]; + if (mFileComment == NULL) { + result = NO_MEMORY; + goto bail; + } + if (fread(mFileComment, 1, mFileCommentLength, fp) != mFileCommentLength) + { + result = UNKNOWN_ERROR; + goto bail; + } + mFileComment[mFileCommentLength] = '\0'; + } + +bail: + return result; +} + +/* + * Write a central dir entry. + */ +status_t ZipEntry::CentralDirEntry::write(FILE* fp) +{ + unsigned char buf[kCDELen]; + + ZipEntry::putLongLE(&buf[0x00], kSignature); + ZipEntry::putShortLE(&buf[0x04], mVersionMadeBy); + ZipEntry::putShortLE(&buf[0x06], mVersionToExtract); + ZipEntry::putShortLE(&buf[0x08], mGPBitFlag); + ZipEntry::putShortLE(&buf[0x0a], mCompressionMethod); + ZipEntry::putShortLE(&buf[0x0c], mLastModFileTime); + ZipEntry::putShortLE(&buf[0x0e], mLastModFileDate); + ZipEntry::putLongLE(&buf[0x10], mCRC32); + ZipEntry::putLongLE(&buf[0x14], mCompressedSize); + ZipEntry::putLongLE(&buf[0x18], mUncompressedSize); + ZipEntry::putShortLE(&buf[0x1c], mFileNameLength); + ZipEntry::putShortLE(&buf[0x1e], mExtraFieldLength); + ZipEntry::putShortLE(&buf[0x20], mFileCommentLength); + ZipEntry::putShortLE(&buf[0x22], mDiskNumberStart); + ZipEntry::putShortLE(&buf[0x24], mInternalAttrs); + ZipEntry::putLongLE(&buf[0x26], mExternalAttrs); + ZipEntry::putLongLE(&buf[0x2a], mLocalHeaderRelOffset); + + if (fwrite(buf, 1, kCDELen, fp) != kCDELen) + return UNKNOWN_ERROR; + + /* write filename */ + if (mFileNameLength != 0) { + if (fwrite(mFileName, 1, mFileNameLength, fp) != mFileNameLength) + return UNKNOWN_ERROR; + } + + /* write "extra field" */ + if (mExtraFieldLength != 0) { + if (fwrite(mExtraField, 1, mExtraFieldLength, fp) != mExtraFieldLength) + return UNKNOWN_ERROR; + } + + /* write comment */ + if (mFileCommentLength != 0) { + if (fwrite(mFileComment, 1, mFileCommentLength, fp) != mFileCommentLength) + return UNKNOWN_ERROR; + } + + return NO_ERROR; +} + +/* + * Dump the contents of a CentralDirEntry object. + */ +void ZipEntry::CentralDirEntry::dump(void) const +{ + LOGD(" CentralDirEntry contents:\n"); + LOGD(" versMadeBy=%u versToExt=%u gpBits=0x%04x compression=%u\n", + mVersionMadeBy, mVersionToExtract, mGPBitFlag, mCompressionMethod); + LOGD(" modTime=0x%04x modDate=0x%04x crc32=0x%08lx\n", + mLastModFileTime, mLastModFileDate, mCRC32); + LOGD(" compressedSize=%lu uncompressedSize=%lu\n", + mCompressedSize, mUncompressedSize); + LOGD(" filenameLen=%u extraLen=%u commentLen=%u\n", + mFileNameLength, mExtraFieldLength, mFileCommentLength); + LOGD(" diskNumStart=%u intAttr=0x%04x extAttr=0x%08lx relOffset=%lu\n", + mDiskNumberStart, mInternalAttrs, mExternalAttrs, + mLocalHeaderRelOffset); + + if (mFileName != NULL) + LOGD(" filename: '%s'\n", mFileName); + if (mFileComment != NULL) + LOGD(" comment: '%s'\n", mFileComment); +} + diff --git a/libs/utils/ZipFile.cpp b/libs/utils/ZipFile.cpp new file mode 100644 index 0000000..89aa874 --- /dev/null +++ b/libs/utils/ZipFile.cpp @@ -0,0 +1,1296 @@ +/* + * 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. + */ + +// +// Access to Zip archives. +// + +#define LOG_TAG "zip" + +#include "utils/ZipFile.h" +#include "utils/ZipUtils.h" +#include "utils/Log.h" + +#include <zlib.h> +#define DEF_MEM_LEVEL 8 // normally in zutil.h? + +#include <memory.h> +#include <sys/stat.h> +#include <errno.h> +#include <assert.h> + +using namespace android; + +/* + * Some environments require the "b", some choke on it. + */ +#define FILE_OPEN_RO "rb" +#define FILE_OPEN_RW "r+b" +#define FILE_OPEN_RW_CREATE "w+b" + +/* should live somewhere else? */ +static status_t errnoToStatus(int err) +{ + if (err == ENOENT) + return NAME_NOT_FOUND; + else if (err == EACCES) + return PERMISSION_DENIED; + else + return UNKNOWN_ERROR; +} + +/* + * Open a file and parse its guts. + */ +status_t ZipFile::open(const char* zipFileName, int flags) +{ + bool newArchive = false; + + assert(mZipFp == NULL); // no reopen + + if ((flags & kOpenTruncate)) + flags |= kOpenCreate; // trunc implies create + + if ((flags & kOpenReadOnly) && (flags & kOpenReadWrite)) + return INVALID_OPERATION; // not both + if (!((flags & kOpenReadOnly) || (flags & kOpenReadWrite))) + return INVALID_OPERATION; // not neither + if ((flags & kOpenCreate) && !(flags & kOpenReadWrite)) + return INVALID_OPERATION; // create requires write + + if (flags & kOpenTruncate) { + newArchive = true; + } else { + newArchive = (access(zipFileName, F_OK) != 0); + if (!(flags & kOpenCreate) && newArchive) { + /* not creating, must already exist */ + LOGD("File %s does not exist", zipFileName); + return NAME_NOT_FOUND; + } + } + + /* open the file */ + const char* openflags; + if (flags & kOpenReadWrite) { + if (newArchive) + openflags = FILE_OPEN_RW_CREATE; + else + openflags = FILE_OPEN_RW; + } else { + openflags = FILE_OPEN_RO; + } + mZipFp = fopen(zipFileName, openflags); + if (mZipFp == NULL) { + int err = errno; + LOGD("fopen failed: %d\n", err); + return errnoToStatus(err); + } + + status_t result; + if (!newArchive) { + /* + * Load the central directory. If that fails, then this probably + * isn't a Zip archive. + */ + result = readCentralDir(); + } else { + /* + * Newly-created. The EndOfCentralDir constructor actually + * sets everything to be the way we want it (all zeroes). We + * set mNeedCDRewrite so that we create *something* if the + * caller doesn't add any files. (We could also just unlink + * the file if it's brand new and nothing was added, but that's + * probably doing more than we really should -- the user might + * have a need for empty zip files.) + */ + mNeedCDRewrite = true; + result = NO_ERROR; + } + + if (flags & kOpenReadOnly) + mReadOnly = true; + else + assert(!mReadOnly); + + return result; +} + +/* + * Return the Nth entry in the archive. + */ +ZipEntry* ZipFile::getEntryByIndex(int idx) const +{ + if (idx < 0 || idx >= (int) mEntries.size()) + return NULL; + + return mEntries[idx]; +} + +/* + * Find an entry by name. + */ +ZipEntry* ZipFile::getEntryByName(const char* fileName) const +{ + /* + * Do a stupid linear string-compare search. + * + * There are various ways to speed this up, especially since it's rare + * to intermingle changes to the archive with "get by name" calls. We + * don't want to sort the mEntries vector itself, however, because + * it's used to recreate the Central Directory. + * + * (Hash table works, parallel list of pointers in sorted order is good.) + */ + int idx; + + for (idx = mEntries.size()-1; idx >= 0; idx--) { + ZipEntry* pEntry = mEntries[idx]; + if (!pEntry->getDeleted() && + strcmp(fileName, pEntry->getFileName()) == 0) + { + return pEntry; + } + } + + return NULL; +} + +/* + * Empty the mEntries vector. + */ +void ZipFile::discardEntries(void) +{ + int count = mEntries.size(); + + while (--count >= 0) + delete mEntries[count]; + + mEntries.clear(); +} + + +/* + * Find the central directory and read the contents. + * + * The fun thing about ZIP archives is that they may or may not be + * readable from start to end. In some cases, notably for archives + * that were written to stdout, the only length information is in the + * central directory at the end of the file. + * + * Of course, the central directory can be followed by a variable-length + * comment field, so we have to scan through it backwards. The comment + * is at most 64K, plus we have 18 bytes for the end-of-central-dir stuff + * itself, plus apparently sometimes people throw random junk on the end + * just for the fun of it. + * + * This is all a little wobbly. If the wrong value ends up in the EOCD + * area, we're hosed. This appears to be the way that everbody handles + * it though, so we're in pretty good company if this fails. + */ +status_t ZipFile::readCentralDir(void) +{ + status_t result = NO_ERROR; + unsigned char* buf = NULL; + off_t fileLength, seekStart; + long readAmount; + int i; + + fseek(mZipFp, 0, SEEK_END); + fileLength = ftell(mZipFp); + rewind(mZipFp); + + /* too small to be a ZIP archive? */ + if (fileLength < EndOfCentralDir::kEOCDLen) { + LOGD("Length is %ld -- too small\n", (long)fileLength); + result = INVALID_OPERATION; + goto bail; + } + + buf = new unsigned char[EndOfCentralDir::kMaxEOCDSearch]; + if (buf == NULL) { + LOGD("Failure allocating %d bytes for EOCD search", + EndOfCentralDir::kMaxEOCDSearch); + result = NO_MEMORY; + goto bail; + } + + if (fileLength > EndOfCentralDir::kMaxEOCDSearch) { + seekStart = fileLength - EndOfCentralDir::kMaxEOCDSearch; + readAmount = EndOfCentralDir::kMaxEOCDSearch; + } else { + seekStart = 0; + readAmount = (long) fileLength; + } + if (fseek(mZipFp, seekStart, SEEK_SET) != 0) { + LOGD("Failure seeking to end of zip at %ld", (long) seekStart); + result = UNKNOWN_ERROR; + goto bail; + } + + /* read the last part of the file into the buffer */ + if (fread(buf, 1, readAmount, mZipFp) != (size_t) readAmount) { + LOGD("short file? wanted %ld\n", readAmount); + result = UNKNOWN_ERROR; + goto bail; + } + + /* find the end-of-central-dir magic */ + for (i = readAmount - 4; i >= 0; i--) { + if (buf[i] == 0x50 && + ZipEntry::getLongLE(&buf[i]) == EndOfCentralDir::kSignature) + { + LOGV("+++ Found EOCD at buf+%d\n", i); + break; + } + } + if (i < 0) { + LOGD("EOCD not found, not Zip\n"); + result = INVALID_OPERATION; + goto bail; + } + + /* extract eocd values */ + result = mEOCD.readBuf(buf + i, readAmount - i); + if (result != NO_ERROR) { + LOGD("Failure reading %ld bytes of EOCD values", readAmount - i); + goto bail; + } + //mEOCD.dump(); + + if (mEOCD.mDiskNumber != 0 || mEOCD.mDiskWithCentralDir != 0 || + mEOCD.mNumEntries != mEOCD.mTotalNumEntries) + { + LOGD("Archive spanning not supported\n"); + result = INVALID_OPERATION; + goto bail; + } + + /* + * So far so good. "mCentralDirSize" is the size in bytes of the + * central directory, so we can just seek back that far to find it. + * We can also seek forward mCentralDirOffset bytes from the + * start of the file. + * + * We're not guaranteed to have the rest of the central dir in the + * buffer, nor are we guaranteed that the central dir will have any + * sort of convenient size. We need to skip to the start of it and + * read the header, then the other goodies. + * + * The only thing we really need right now is the file comment, which + * we're hoping to preserve. + */ + if (fseek(mZipFp, mEOCD.mCentralDirOffset, SEEK_SET) != 0) { + LOGD("Failure seeking to central dir offset %ld\n", + mEOCD.mCentralDirOffset); + result = UNKNOWN_ERROR; + goto bail; + } + + /* + * Loop through and read the central dir entries. + */ + LOGV("Scanning %d entries...\n", mEOCD.mTotalNumEntries); + int entry; + for (entry = 0; entry < mEOCD.mTotalNumEntries; entry++) { + ZipEntry* pEntry = new ZipEntry; + + result = pEntry->initFromCDE(mZipFp); + if (result != NO_ERROR) { + LOGD("initFromCDE failed\n"); + delete pEntry; + goto bail; + } + + mEntries.add(pEntry); + } + + + /* + * If all went well, we should now be back at the EOCD. + */ + { + unsigned char checkBuf[4]; + if (fread(checkBuf, 1, 4, mZipFp) != 4) { + LOGD("EOCD check read failed\n"); + result = INVALID_OPERATION; + goto bail; + } + if (ZipEntry::getLongLE(checkBuf) != EndOfCentralDir::kSignature) { + LOGD("EOCD read check failed\n"); + result = UNKNOWN_ERROR; + goto bail; + } + LOGV("+++ EOCD read check passed\n"); + } + +bail: + delete[] buf; + return result; +} + + +/* + * Add a new file to the archive. + * + * This requires creating and populating a ZipEntry structure, and copying + * the data into the file at the appropriate position. The "appropriate + * position" is the current location of the central directory, which we + * casually overwrite (we can put it back later). + * + * If we were concerned about safety, we would want to make all changes + * in a temp file and then overwrite the original after everything was + * safely written. Not really a concern for us. + */ +status_t ZipFile::addCommon(const char* fileName, const void* data, size_t size, + const char* storageName, int sourceType, int compressionMethod, + ZipEntry** ppEntry) +{ + ZipEntry* pEntry = NULL; + status_t result = NO_ERROR; + long lfhPosn, startPosn, endPosn, uncompressedLen; + FILE* inputFp = NULL; + unsigned long crc; + time_t modWhen; + + if (mReadOnly) + return INVALID_OPERATION; + + assert(compressionMethod == ZipEntry::kCompressDeflated || + compressionMethod == ZipEntry::kCompressStored); + + /* make sure we're in a reasonable state */ + assert(mZipFp != NULL); + assert(mEntries.size() == mEOCD.mTotalNumEntries); + + /* make sure it doesn't already exist */ + if (getEntryByName(storageName) != NULL) + return ALREADY_EXISTS; + + if (!data) { + inputFp = fopen(fileName, FILE_OPEN_RO); + if (inputFp == NULL) + return errnoToStatus(errno); + } + + if (fseek(mZipFp, mEOCD.mCentralDirOffset, SEEK_SET) != 0) { + result = UNKNOWN_ERROR; + goto bail; + } + + pEntry = new ZipEntry; + pEntry->initNew(storageName, NULL); + + /* + * From here on out, failures are more interesting. + */ + mNeedCDRewrite = true; + + /* + * Write the LFH, even though it's still mostly blank. We need it + * as a place-holder. In theory the LFH isn't necessary, but in + * practice some utilities demand it. + */ + lfhPosn = ftell(mZipFp); + pEntry->mLFH.write(mZipFp); + startPosn = ftell(mZipFp); + + /* + * Copy the data in, possibly compressing it as we go. + */ + if (sourceType == ZipEntry::kCompressStored) { + if (compressionMethod == ZipEntry::kCompressDeflated) { + bool failed = false; + result = compressFpToFp(mZipFp, inputFp, data, size, &crc); + if (result != NO_ERROR) { + LOGD("compression failed, storing\n"); + failed = true; + } else { + /* + * Make sure it has compressed "enough". This probably ought + * to be set through an API call, but I don't expect our + * criteria to change over time. + */ + long src = inputFp ? ftell(inputFp) : size; + long dst = ftell(mZipFp) - startPosn; + if (dst + (dst / 10) > src) { + LOGD("insufficient compression (src=%ld dst=%ld), storing\n", + src, dst); + failed = true; + } + } + + if (failed) { + compressionMethod = ZipEntry::kCompressStored; + if (inputFp) rewind(inputFp); + fseek(mZipFp, startPosn, SEEK_SET); + /* fall through to kCompressStored case */ + } + } + /* handle "no compression" request, or failed compression from above */ + if (compressionMethod == ZipEntry::kCompressStored) { + if (inputFp) { + result = copyFpToFp(mZipFp, inputFp, &crc); + } else { + result = copyDataToFp(mZipFp, data, size, &crc); + } + if (result != NO_ERROR) { + // don't need to truncate; happens in CDE rewrite + LOGD("failed copying data in\n"); + goto bail; + } + } + + // currently seeked to end of file + uncompressedLen = inputFp ? ftell(inputFp) : size; + } else if (sourceType == ZipEntry::kCompressDeflated) { + /* we should support uncompressed-from-compressed, but it's not + * important right now */ + assert(compressionMethod == ZipEntry::kCompressDeflated); + + bool scanResult; + int method; + long compressedLen; + + scanResult = ZipUtils::examineGzip(inputFp, &method, &uncompressedLen, + &compressedLen, &crc); + if (!scanResult || method != ZipEntry::kCompressDeflated) { + LOGD("this isn't a deflated gzip file?"); + result = UNKNOWN_ERROR; + goto bail; + } + + result = copyPartialFpToFp(mZipFp, inputFp, compressedLen, NULL); + if (result != NO_ERROR) { + LOGD("failed copying gzip data in\n"); + goto bail; + } + } else { + assert(false); + result = UNKNOWN_ERROR; + goto bail; + } + + /* + * We could write the "Data Descriptor", but there doesn't seem to + * be any point since we're going to go back and write the LFH. + * + * Update file offsets. + */ + endPosn = ftell(mZipFp); // seeked to end of compressed data + + /* + * Success! Fill out new values. + */ + pEntry->setDataInfo(uncompressedLen, endPosn - startPosn, crc, + compressionMethod); + modWhen = getModTime(inputFp ? fileno(inputFp) : fileno(mZipFp)); + pEntry->setModWhen(modWhen); + pEntry->setLFHOffset(lfhPosn); + mEOCD.mNumEntries++; + mEOCD.mTotalNumEntries++; + mEOCD.mCentralDirSize = 0; // mark invalid; set by flush() + mEOCD.mCentralDirOffset = endPosn; + + /* + * Go back and write the LFH. + */ + if (fseek(mZipFp, lfhPosn, SEEK_SET) != 0) { + result = UNKNOWN_ERROR; + goto bail; + } + pEntry->mLFH.write(mZipFp); + + /* + * Add pEntry to the list. + */ + mEntries.add(pEntry); + if (ppEntry != NULL) + *ppEntry = pEntry; + pEntry = NULL; + +bail: + if (inputFp != NULL) + fclose(inputFp); + delete pEntry; + return result; +} + +/* + * Add an entry by copying it from another zip file. If "padding" is + * nonzero, the specified number of bytes will be added to the "extra" + * field in the header. + * + * If "ppEntry" is non-NULL, a pointer to the new entry will be returned. + */ +status_t ZipFile::add(const ZipFile* pSourceZip, const ZipEntry* pSourceEntry, + int padding, ZipEntry** ppEntry) +{ + ZipEntry* pEntry = NULL; + status_t result; + long lfhPosn, endPosn; + + if (mReadOnly) + return INVALID_OPERATION; + + /* make sure we're in a reasonable state */ + assert(mZipFp != NULL); + assert(mEntries.size() == mEOCD.mTotalNumEntries); + + if (fseek(mZipFp, mEOCD.mCentralDirOffset, SEEK_SET) != 0) { + result = UNKNOWN_ERROR; + goto bail; + } + + pEntry = new ZipEntry; + if (pEntry == NULL) { + result = NO_MEMORY; + goto bail; + } + + result = pEntry->initFromExternal(pSourceZip, pSourceEntry); + if (result != NO_ERROR) + goto bail; + if (padding != 0) { + result = pEntry->addPadding(padding); + if (result != NO_ERROR) + goto bail; + } + + /* + * From here on out, failures are more interesting. + */ + mNeedCDRewrite = true; + + /* + * Write the LFH. Since we're not recompressing the data, we already + * have all of the fields filled out. + */ + lfhPosn = ftell(mZipFp); + pEntry->mLFH.write(mZipFp); + + /* + * Copy the data over. + * + * If the "has data descriptor" flag is set, we want to copy the DD + * fields as well. This is a fixed-size area immediately following + * the data. + */ + if (fseek(pSourceZip->mZipFp, pSourceEntry->getFileOffset(), SEEK_SET) != 0) + { + result = UNKNOWN_ERROR; + goto bail; + } + + off_t copyLen; + copyLen = pSourceEntry->getCompressedLen(); + if ((pSourceEntry->mLFH.mGPBitFlag & ZipEntry::kUsesDataDescr) != 0) + copyLen += ZipEntry::kDataDescriptorLen; + + if (copyPartialFpToFp(mZipFp, pSourceZip->mZipFp, copyLen, NULL) + != NO_ERROR) + { + LOGW("copy of '%s' failed\n", pEntry->mCDE.mFileName); + result = UNKNOWN_ERROR; + goto bail; + } + + /* + * Update file offsets. + */ + endPosn = ftell(mZipFp); + + /* + * Success! Fill out new values. + */ + pEntry->setLFHOffset(lfhPosn); // sets mCDE.mLocalHeaderRelOffset + mEOCD.mNumEntries++; + mEOCD.mTotalNumEntries++; + mEOCD.mCentralDirSize = 0; // mark invalid; set by flush() + mEOCD.mCentralDirOffset = endPosn; + + /* + * Add pEntry to the list. + */ + mEntries.add(pEntry); + if (ppEntry != NULL) + *ppEntry = pEntry; + pEntry = NULL; + + result = NO_ERROR; + +bail: + delete pEntry; + return result; +} + +/* + * Copy all of the bytes in "src" to "dst". + * + * On exit, "srcFp" will be seeked to the end of the file, and "dstFp" + * will be seeked immediately past the data. + */ +status_t ZipFile::copyFpToFp(FILE* dstFp, FILE* srcFp, unsigned long* pCRC32) +{ + unsigned char tmpBuf[32768]; + size_t count; + + *pCRC32 = crc32(0L, Z_NULL, 0); + + while (1) { + count = fread(tmpBuf, 1, sizeof(tmpBuf), srcFp); + if (ferror(srcFp) || ferror(dstFp)) + return errnoToStatus(errno); + if (count == 0) + break; + + *pCRC32 = crc32(*pCRC32, tmpBuf, count); + + if (fwrite(tmpBuf, 1, count, dstFp) != count) { + LOGD("fwrite %d bytes failed\n", (int) count); + return UNKNOWN_ERROR; + } + } + + return NO_ERROR; +} + +/* + * Copy all of the bytes in "src" to "dst". + * + * On exit, "dstFp" will be seeked immediately past the data. + */ +status_t ZipFile::copyDataToFp(FILE* dstFp, + const void* data, size_t size, unsigned long* pCRC32) +{ + size_t count; + + *pCRC32 = crc32(0L, Z_NULL, 0); + if (size > 0) { + *pCRC32 = crc32(*pCRC32, (const unsigned char*)data, size); + if (fwrite(data, 1, size, dstFp) != size) { + LOGD("fwrite %d bytes failed\n", (int) size); + return UNKNOWN_ERROR; + } + } + + return NO_ERROR; +} + +/* + * Copy some of the bytes in "src" to "dst". + * + * If "pCRC32" is NULL, the CRC will not be computed. + * + * On exit, "srcFp" will be seeked to the end of the file, and "dstFp" + * will be seeked immediately past the data just written. + */ +status_t ZipFile::copyPartialFpToFp(FILE* dstFp, FILE* srcFp, long length, + unsigned long* pCRC32) +{ + unsigned char tmpBuf[32768]; + size_t count; + + if (pCRC32 != NULL) + *pCRC32 = crc32(0L, Z_NULL, 0); + + while (length) { + long readSize; + + readSize = sizeof(tmpBuf); + if (readSize > length) + readSize = length; + + count = fread(tmpBuf, 1, readSize, srcFp); + if ((long) count != readSize) { // error or unexpected EOF + LOGD("fread %d bytes failed\n", (int) readSize); + return UNKNOWN_ERROR; + } + + if (pCRC32 != NULL) + *pCRC32 = crc32(*pCRC32, tmpBuf, count); + + if (fwrite(tmpBuf, 1, count, dstFp) != count) { + LOGD("fwrite %d bytes failed\n", (int) count); + return UNKNOWN_ERROR; + } + + length -= readSize; + } + + return NO_ERROR; +} + +/* + * Compress all of the data in "srcFp" and write it to "dstFp". + * + * On exit, "srcFp" will be seeked to the end of the file, and "dstFp" + * will be seeked immediately past the compressed data. + */ +status_t ZipFile::compressFpToFp(FILE* dstFp, FILE* srcFp, + const void* data, size_t size, unsigned long* pCRC32) +{ + status_t result = NO_ERROR; + const size_t kBufSize = 32768; + unsigned char* inBuf = NULL; + unsigned char* outBuf = NULL; + z_stream zstream; + bool atEof = false; // no feof() aviailable yet + unsigned long crc; + int zerr; + + /* + * Create an input buffer and an output buffer. + */ + inBuf = new unsigned char[kBufSize]; + outBuf = new unsigned char[kBufSize]; + if (inBuf == NULL || outBuf == NULL) { + result = NO_MEMORY; + goto bail; + } + + /* + * Initialize the zlib stream. + */ + memset(&zstream, 0, sizeof(zstream)); + zstream.zalloc = Z_NULL; + zstream.zfree = Z_NULL; + zstream.opaque = Z_NULL; + zstream.next_in = NULL; + zstream.avail_in = 0; + zstream.next_out = outBuf; + zstream.avail_out = kBufSize; + zstream.data_type = Z_UNKNOWN; + + zerr = deflateInit2(&zstream, Z_BEST_COMPRESSION, + Z_DEFLATED, -MAX_WBITS, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY); + if (zerr != Z_OK) { + result = UNKNOWN_ERROR; + if (zerr == Z_VERSION_ERROR) { + LOGE("Installed zlib is not compatible with linked version (%s)\n", + ZLIB_VERSION); + } else { + LOGD("Call to deflateInit2 failed (zerr=%d)\n", zerr); + } + goto bail; + } + + crc = crc32(0L, Z_NULL, 0); + + /* + * Loop while we have data. + */ + do { + size_t getSize; + int flush; + + /* only read if the input buffer is empty */ + if (zstream.avail_in == 0 && !atEof) { + LOGV("+++ reading %d bytes\n", (int)kBufSize); + if (data) { + getSize = size > kBufSize ? kBufSize : size; + memcpy(inBuf, data, getSize); + data = ((const char*)data) + getSize; + size -= getSize; + } else { + getSize = fread(inBuf, 1, kBufSize, srcFp); + if (ferror(srcFp)) { + LOGD("deflate read failed (errno=%d)\n", errno); + goto z_bail; + } + } + if (getSize < kBufSize) { + LOGV("+++ got %d bytes, EOF reached\n", + (int)getSize); + atEof = true; + } + + crc = crc32(crc, inBuf, getSize); + + zstream.next_in = inBuf; + zstream.avail_in = getSize; + } + + if (atEof) + flush = Z_FINISH; /* tell zlib that we're done */ + else + flush = Z_NO_FLUSH; /* more to come! */ + + zerr = deflate(&zstream, flush); + if (zerr != Z_OK && zerr != Z_STREAM_END) { + LOGD("zlib deflate call failed (zerr=%d)\n", zerr); + result = UNKNOWN_ERROR; + goto z_bail; + } + + /* write when we're full or when we're done */ + if (zstream.avail_out == 0 || + (zerr == Z_STREAM_END && zstream.avail_out != (uInt) kBufSize)) + { + LOGV("+++ writing %d bytes\n", (int) (zstream.next_out - outBuf)); + if (fwrite(outBuf, 1, zstream.next_out - outBuf, dstFp) != + (size_t)(zstream.next_out - outBuf)) + { + LOGD("write %d failed in deflate\n", + (int) (zstream.next_out - outBuf)); + goto z_bail; + } + + zstream.next_out = outBuf; + zstream.avail_out = kBufSize; + } + } while (zerr == Z_OK); + + assert(zerr == Z_STREAM_END); /* other errors should've been caught */ + + *pCRC32 = crc; + +z_bail: + deflateEnd(&zstream); /* free up any allocated structures */ + +bail: + delete[] inBuf; + delete[] outBuf; + + return result; +} + +/* + * Mark an entry as deleted. + * + * We will eventually need to crunch the file down, but if several files + * are being removed (perhaps as part of an "update" process) we can make + * things considerably faster by deferring the removal to "flush" time. + */ +status_t ZipFile::remove(ZipEntry* pEntry) +{ + /* + * Should verify that pEntry is actually part of this archive, and + * not some stray ZipEntry from a different file. + */ + + /* mark entry as deleted, and mark archive as dirty */ + pEntry->setDeleted(); + mNeedCDRewrite = true; + return NO_ERROR; +} + +/* + * Flush any pending writes. + * + * In particular, this will crunch out deleted entries, and write the + * Central Directory and EOCD if we have stomped on them. + */ +status_t ZipFile::flush(void) +{ + status_t result = NO_ERROR; + long eocdPosn; + int i, count; + + if (mReadOnly) + return INVALID_OPERATION; + if (!mNeedCDRewrite) + return NO_ERROR; + + assert(mZipFp != NULL); + + result = crunchArchive(); + if (result != NO_ERROR) + return result; + + if (fseek(mZipFp, mEOCD.mCentralDirOffset, SEEK_SET) != 0) + return UNKNOWN_ERROR; + + count = mEntries.size(); + for (i = 0; i < count; i++) { + ZipEntry* pEntry = mEntries[i]; + pEntry->mCDE.write(mZipFp); + } + + eocdPosn = ftell(mZipFp); + mEOCD.mCentralDirSize = eocdPosn - mEOCD.mCentralDirOffset; + + mEOCD.write(mZipFp); + + /* + * If we had some stuff bloat up during compression and get replaced + * with plain files, or if we deleted some entries, there's a lot + * of wasted space at the end of the file. Remove it now. + */ + if (ftruncate(fileno(mZipFp), ftell(mZipFp)) != 0) { + LOGW("ftruncate failed %ld: %s\n", ftell(mZipFp), strerror(errno)); + // not fatal + } + + /* should we clear the "newly added" flag in all entries now? */ + + mNeedCDRewrite = false; + return NO_ERROR; +} + +/* + * Crunch deleted files out of an archive by shifting the later files down. + * + * Because we're not using a temp file, we do the operation inside the + * current file. + */ +status_t ZipFile::crunchArchive(void) +{ + status_t result = NO_ERROR; + int i, count; + long delCount, adjust; + +#if 0 + printf("CONTENTS:\n"); + for (i = 0; i < (int) mEntries.size(); i++) { + printf(" %d: lfhOff=%ld del=%d\n", + i, mEntries[i]->getLFHOffset(), mEntries[i]->getDeleted()); + } + printf(" END is %ld\n", (long) mEOCD.mCentralDirOffset); +#endif + + /* + * Roll through the set of files, shifting them as appropriate. We + * could probably get a slight performance improvement by sliding + * multiple files down at once (because we could use larger reads + * when operating on batches of small files), but it's not that useful. + */ + count = mEntries.size(); + delCount = adjust = 0; + for (i = 0; i < count; i++) { + ZipEntry* pEntry = mEntries[i]; + long span; + + if (pEntry->getLFHOffset() != 0) { + long nextOffset; + + /* Get the length of this entry by finding the offset + * of the next entry. Directory entries don't have + * file offsets, so we need to find the next non-directory + * entry. + */ + nextOffset = 0; + for (int ii = i+1; nextOffset == 0 && ii < count; ii++) + nextOffset = mEntries[ii]->getLFHOffset(); + if (nextOffset == 0) + nextOffset = mEOCD.mCentralDirOffset; + span = nextOffset - pEntry->getLFHOffset(); + + assert(span >= ZipEntry::LocalFileHeader::kLFHLen); + } else { + /* This is a directory entry. It doesn't have + * any actual file contents, so there's no need to + * move anything. + */ + span = 0; + } + + //printf("+++ %d: off=%ld span=%ld del=%d [count=%d]\n", + // i, pEntry->getLFHOffset(), span, pEntry->getDeleted(), count); + + if (pEntry->getDeleted()) { + adjust += span; + delCount++; + + delete pEntry; + mEntries.removeAt(i); + + /* adjust loop control */ + count--; + i--; + } else if (span != 0 && adjust > 0) { + /* shuffle this entry back */ + //printf("+++ Shuffling '%s' back %ld\n", + // pEntry->getFileName(), adjust); + result = filemove(mZipFp, pEntry->getLFHOffset() - adjust, + pEntry->getLFHOffset(), span); + if (result != NO_ERROR) { + /* this is why you use a temp file */ + LOGE("error during crunch - archive is toast\n"); + return result; + } + + pEntry->setLFHOffset(pEntry->getLFHOffset() - adjust); + } + } + + /* + * Fix EOCD info. We have to wait until the end to do some of this + * because we use mCentralDirOffset to determine "span" for the + * last entry. + */ + mEOCD.mCentralDirOffset -= adjust; + mEOCD.mNumEntries -= delCount; + mEOCD.mTotalNumEntries -= delCount; + mEOCD.mCentralDirSize = 0; // mark invalid; set by flush() + + assert(mEOCD.mNumEntries == mEOCD.mTotalNumEntries); + assert(mEOCD.mNumEntries == count); + + return result; +} + +/* + * Works like memmove(), but on pieces of a file. + */ +status_t ZipFile::filemove(FILE* fp, off_t dst, off_t src, size_t n) +{ + if (dst == src || n <= 0) + return NO_ERROR; + + unsigned char readBuf[32768]; + + if (dst < src) { + /* shift stuff toward start of file; must read from start */ + while (n != 0) { + size_t getSize = sizeof(readBuf); + if (getSize > n) + getSize = n; + + if (fseek(fp, (long) src, SEEK_SET) != 0) { + LOGD("filemove src seek %ld failed\n", (long) src); + return UNKNOWN_ERROR; + } + + if (fread(readBuf, 1, getSize, fp) != getSize) { + LOGD("filemove read %ld off=%ld failed\n", + (long) getSize, (long) src); + return UNKNOWN_ERROR; + } + + if (fseek(fp, (long) dst, SEEK_SET) != 0) { + LOGD("filemove dst seek %ld failed\n", (long) dst); + return UNKNOWN_ERROR; + } + + if (fwrite(readBuf, 1, getSize, fp) != getSize) { + LOGD("filemove write %ld off=%ld failed\n", + (long) getSize, (long) dst); + return UNKNOWN_ERROR; + } + + src += getSize; + dst += getSize; + n -= getSize; + } + } else { + /* shift stuff toward end of file; must read from end */ + assert(false); // write this someday, maybe + return UNKNOWN_ERROR; + } + + return NO_ERROR; +} + + +/* + * Get the modification time from a file descriptor. + */ +time_t ZipFile::getModTime(int fd) +{ + struct stat sb; + + if (fstat(fd, &sb) < 0) { + LOGD("HEY: fstat on fd %d failed\n", fd); + return (time_t) -1; + } + + return sb.st_mtime; +} + + +#if 0 /* this is a bad idea */ +/* + * Get a copy of the Zip file descriptor. + * + * We don't allow this if the file was opened read-write because we tend + * to leave the file contents in an uncertain state between calls to + * flush(). The duplicated file descriptor should only be valid for reads. + */ +int ZipFile::getZipFd(void) const +{ + if (!mReadOnly) + return INVALID_OPERATION; + assert(mZipFp != NULL); + + int fd; + fd = dup(fileno(mZipFp)); + if (fd < 0) { + LOGD("didn't work, errno=%d\n", errno); + } + + return fd; +} +#endif + + +#if 0 +/* + * Expand data. + */ +bool ZipFile::uncompress(const ZipEntry* pEntry, void* buf) const +{ + return false; +} +#endif + +// free the memory when you're done +void* ZipFile::uncompress(const ZipEntry* entry) +{ + size_t unlen = entry->getUncompressedLen(); + size_t clen = entry->getCompressedLen(); + + void* buf = malloc(unlen); + if (buf == NULL) { + return NULL; + } + + fseek(mZipFp, 0, SEEK_SET); + + off_t offset = entry->getFileOffset(); + if (fseek(mZipFp, offset, SEEK_SET) != 0) { + goto bail; + } + + switch (entry->getCompressionMethod()) + { + case ZipEntry::kCompressStored: { + ssize_t amt = fread(buf, 1, unlen, mZipFp); + if (amt != (ssize_t)unlen) { + goto bail; + } +#if 0 + printf("data...\n"); + const unsigned char* p = (unsigned char*)buf; + const unsigned char* end = p+unlen; + for (int i=0; i<32 && p < end; i++) { + printf("0x%08x ", (int)(offset+(i*0x10))); + for (int j=0; j<0x10 && p < end; j++) { + printf(" %02x", *p); + p++; + } + printf("\n"); + } +#endif + + } + break; + case ZipEntry::kCompressDeflated: { + if (!ZipUtils::inflateToBuffer(mZipFp, buf, unlen, clen)) { + goto bail; + } + } + break; + default: + goto bail; + } + return buf; + +bail: + free(buf); + return NULL; +} + + +/* + * =========================================================================== + * ZipFile::EndOfCentralDir + * =========================================================================== + */ + +/* + * Read the end-of-central-dir fields. + * + * "buf" should be positioned at the EOCD signature, and should contain + * the entire EOCD area including the comment. + */ +status_t ZipFile::EndOfCentralDir::readBuf(const unsigned char* buf, int len) +{ + /* don't allow re-use */ + assert(mComment == NULL); + + if (len < kEOCDLen) { + /* looks like ZIP file got truncated */ + LOGD(" Zip EOCD: expected >= %d bytes, found %d\n", + kEOCDLen, len); + return INVALID_OPERATION; + } + + /* this should probably be an assert() */ + if (ZipEntry::getLongLE(&buf[0x00]) != kSignature) + return UNKNOWN_ERROR; + + mDiskNumber = ZipEntry::getShortLE(&buf[0x04]); + mDiskWithCentralDir = ZipEntry::getShortLE(&buf[0x06]); + mNumEntries = ZipEntry::getShortLE(&buf[0x08]); + mTotalNumEntries = ZipEntry::getShortLE(&buf[0x0a]); + mCentralDirSize = ZipEntry::getLongLE(&buf[0x0c]); + mCentralDirOffset = ZipEntry::getLongLE(&buf[0x10]); + mCommentLen = ZipEntry::getShortLE(&buf[0x14]); + + // TODO: validate mCentralDirOffset + + if (mCommentLen > 0) { + if (kEOCDLen + mCommentLen > len) { + LOGD("EOCD(%d) + comment(%d) exceeds len (%d)\n", + kEOCDLen, mCommentLen, len); + return UNKNOWN_ERROR; + } + mComment = new unsigned char[mCommentLen]; + memcpy(mComment, buf + kEOCDLen, mCommentLen); + } + + return NO_ERROR; +} + +/* + * Write an end-of-central-directory section. + */ +status_t ZipFile::EndOfCentralDir::write(FILE* fp) +{ + unsigned char buf[kEOCDLen]; + + ZipEntry::putLongLE(&buf[0x00], kSignature); + ZipEntry::putShortLE(&buf[0x04], mDiskNumber); + ZipEntry::putShortLE(&buf[0x06], mDiskWithCentralDir); + ZipEntry::putShortLE(&buf[0x08], mNumEntries); + ZipEntry::putShortLE(&buf[0x0a], mTotalNumEntries); + ZipEntry::putLongLE(&buf[0x0c], mCentralDirSize); + ZipEntry::putLongLE(&buf[0x10], mCentralDirOffset); + ZipEntry::putShortLE(&buf[0x14], mCommentLen); + + if (fwrite(buf, 1, kEOCDLen, fp) != kEOCDLen) + return UNKNOWN_ERROR; + if (mCommentLen > 0) { + assert(mComment != NULL); + if (fwrite(mComment, mCommentLen, 1, fp) != mCommentLen) + return UNKNOWN_ERROR; + } + + return NO_ERROR; +} + +/* + * Dump the contents of an EndOfCentralDir object. + */ +void ZipFile::EndOfCentralDir::dump(void) const +{ + LOGD(" EndOfCentralDir contents:\n"); + LOGD(" diskNum=%u diskWCD=%u numEnt=%u totalNumEnt=%u\n", + mDiskNumber, mDiskWithCentralDir, mNumEntries, mTotalNumEntries); + LOGD(" centDirSize=%lu centDirOff=%lu commentLen=%u\n", + mCentralDirSize, mCentralDirOffset, mCommentLen); +} + diff --git a/libs/utils/ZipFileCRO.cpp b/libs/utils/ZipFileCRO.cpp new file mode 100644 index 0000000..d312daf --- /dev/null +++ b/libs/utils/ZipFileCRO.cpp @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2008 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. + */ + +#include "utils/ZipFileCRO.h" +#include "utils/ZipFileRO.h" + +using namespace android; + +ZipFileCRO ZipFileXRO_open(const char* path) { + ZipFileRO* zip = new ZipFileRO(); + if (zip->open(path) == NO_ERROR) { + return (ZipFileCRO)zip; + } + return NULL; +} + +void ZipFileCRO_destroy(ZipFileCRO zipToken) { + ZipFileRO* zip = (ZipFileRO*)zipToken; + delete zip; +} + +ZipEntryCRO ZipFileCRO_findEntryByName(ZipFileCRO zipToken, + const char* fileName) { + ZipFileRO* zip = (ZipFileRO*)zipToken; + return (ZipEntryCRO)zip->findEntryByName(fileName); +} + +bool ZipFileCRO_getEntryInfo(ZipFileCRO zipToken, ZipEntryRO entryToken, + int* pMethod, long* pUncompLen, + long* pCompLen, off_t* pOffset, long* pModWhen, long* pCrc32) { + ZipFileRO* zip = (ZipFileRO*)zipToken; + ZipEntryRO entry = (ZipEntryRO)entryToken; + return zip->getEntryInfo(entry, pMethod, pUncompLen, pCompLen, pOffset, + pModWhen, pCrc32); +} + +bool ZipFileCRO_uncompressEntry(ZipFileCRO zipToken, ZipEntryRO entryToken, int fd) { + ZipFileRO* zip = (ZipFileRO*)zipToken; + ZipEntryRO entry = (ZipEntryRO)entryToken; + return zip->uncompressEntry(entry, fd); +} diff --git a/libs/utils/ZipFileRO.cpp b/libs/utils/ZipFileRO.cpp new file mode 100644 index 0000000..ae8c719 --- /dev/null +++ b/libs/utils/ZipFileRO.cpp @@ -0,0 +1,724 @@ +/* + * Copyright (C) 2007 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. + */ + +// +// Read-only access to Zip archives, with minimal heap allocation. +// +#define LOG_TAG "zipro" +//#define LOG_NDEBUG 0 +#include "utils/ZipFileRO.h" +#include "utils/Log.h" +#include "utils/misc.h" + +#include <zlib.h> + +#include <string.h> +#include <fcntl.h> +#include <errno.h> +#include <assert.h> + +using namespace android; + +/* + * Zip file constants. + */ +#define kEOCDSignature 0x06054b50 +#define kEOCDLen 22 +#define kEOCDNumEntries 8 // offset to #of entries in file +#define kEOCDFileOffset 16 // offset to central directory + +#define kMaxCommentLen 65535 // longest possible in ushort +#define kMaxEOCDSearch (kMaxCommentLen + kEOCDLen) + +#define kLFHSignature 0x04034b50 +#define kLFHLen 30 // excluding variable-len fields +#define kLFHNameLen 26 // offset to filename length +#define kLFHExtraLen 28 // offset to extra length + +#define kCDESignature 0x02014b50 +#define kCDELen 46 // excluding variable-len fields +#define kCDEMethod 10 // offset to compression method +#define kCDEModWhen 12 // offset to modification timestamp +#define kCDECRC 16 // offset to entry CRC +#define kCDECompLen 20 // offset to compressed length +#define kCDEUncompLen 24 // offset to uncompressed length +#define kCDENameLen 28 // offset to filename length +#define kCDEExtraLen 30 // offset to extra length +#define kCDECommentLen 32 // offset to comment length +#define kCDELocalOffset 42 // offset to local hdr + +/* + * The values we return for ZipEntryRO use 0 as an invalid value, so we + * want to adjust the hash table index by a fixed amount. Using a large + * value helps insure that people don't mix & match arguments, e.g. to + * findEntryByIndex(). + */ +#define kZipEntryAdj 10000 + +/* + * Convert a ZipEntryRO to a hash table index, verifying that it's in a + * valid range. + */ +int ZipFileRO::entryToIndex(const ZipEntryRO entry) const +{ + long ent = ((long) entry) - kZipEntryAdj; + if (ent < 0 || ent >= mHashTableSize || mHashTable[ent].name == NULL) { + LOGW("Invalid ZipEntryRO %p (%ld)\n", entry, ent); + return -1; + } + return ent; +} + + +/* + * Open the specified file read-only. We memory-map the entire thing and + * close the file before returning. + */ +status_t ZipFileRO::open(const char* zipFileName) +{ + int fd = -1; + off_t length; + + assert(mFileMap == NULL); + + /* + * Open and map the specified file. + */ + fd = ::open(zipFileName, O_RDONLY); + if (fd < 0) { + LOGW("Unable to open zip '%s': %s\n", zipFileName, strerror(errno)); + return NAME_NOT_FOUND; + } + + length = lseek(fd, 0, SEEK_END); + if (length < 0) { + close(fd); + return UNKNOWN_ERROR; + } + + mFileMap = new FileMap(); + if (mFileMap == NULL) { + close(fd); + return NO_MEMORY; + } + if (!mFileMap->create(zipFileName, fd, 0, length, true)) { + LOGW("Unable to map '%s': %s\n", zipFileName, strerror(errno)); + close(fd); + return UNKNOWN_ERROR; + } + + mFd = fd; + + /* + * Got it mapped, verify it and create data structures for fast access. + */ + if (!parseZipArchive()) { + mFileMap->release(); + mFileMap = NULL; + return UNKNOWN_ERROR; + } + + return OK; +} + +/* + * Parse the Zip archive, verifying its contents and initializing internal + * data structures. + */ +bool ZipFileRO::parseZipArchive(void) +{ +#define CHECK_OFFSET(_off) { \ + if ((unsigned int) (_off) >= maxOffset) { \ + LOGE("ERROR: bad offset %u (max %d): %s\n", \ + (unsigned int) (_off), maxOffset, #_off); \ + goto bail; \ + } \ + } + const unsigned char* basePtr = (const unsigned char*)mFileMap->getDataPtr(); + const unsigned char* ptr; + size_t length = mFileMap->getDataLength(); + bool result = false; + unsigned int i, numEntries, cdOffset; + unsigned int val; + + /* + * The first 4 bytes of the file will either be the local header + * signature for the first file (kLFHSignature) or, if the archive doesn't + * have any files in it, the end-of-central-directory signature + * (kEOCDSignature). + */ + val = get4LE(basePtr); + if (val == kEOCDSignature) { + LOGI("Found Zip archive, but it looks empty\n"); + goto bail; + } else if (val != kLFHSignature) { + LOGV("Not a Zip archive (found 0x%08x)\n", val); + goto bail; + } + + /* + * Find the EOCD. We'll find it immediately unless they have a file + * comment. + */ + ptr = basePtr + length - kEOCDLen; + + while (ptr >= basePtr) { + if (*ptr == (kEOCDSignature & 0xff) && get4LE(ptr) == kEOCDSignature) + break; + ptr--; + } + if (ptr < basePtr) { + LOGI("Could not find end-of-central-directory in Zip\n"); + goto bail; + } + + /* + * There are two interesting items in the EOCD block: the number of + * entries in the file, and the file offset of the start of the + * central directory. + * + * (There's actually a count of the #of entries in this file, and for + * all files which comprise a spanned archive, but for our purposes + * we're only interested in the current file. Besides, we expect the + * two to be equivalent for our stuff.) + */ + numEntries = get2LE(ptr + kEOCDNumEntries); + cdOffset = get4LE(ptr + kEOCDFileOffset); + + /* valid offsets are [0,EOCD] */ + unsigned int maxOffset; + maxOffset = (ptr - basePtr) +1; + + LOGV("+++ numEntries=%d cdOffset=%d\n", numEntries, cdOffset); + if (numEntries == 0 || cdOffset >= length) { + LOGW("Invalid entries=%d offset=%d (len=%zd)\n", + numEntries, cdOffset, length); + goto bail; + } + + /* + * Create hash table. We have a minimum 75% load factor, possibly as + * low as 50% after we round off to a power of 2. + */ + mNumEntries = numEntries; + mHashTableSize = roundUpPower2(1 + ((numEntries * 4) / 3)); + mHashTable = (HashEntry*) calloc(1, sizeof(HashEntry) * mHashTableSize); + + /* + * Walk through the central directory, adding entries to the hash + * table. + */ + ptr = basePtr + cdOffset; + for (i = 0; i < numEntries; i++) { + unsigned int fileNameLen, extraLen, commentLen, localHdrOffset; + const unsigned char* localHdr; + unsigned int hash; + + if (get4LE(ptr) != kCDESignature) { + LOGW("Missed a central dir sig (at %d)\n", i); + goto bail; + } + if (ptr + kCDELen > basePtr + length) { + LOGW("Ran off the end (at %d)\n", i); + goto bail; + } + + localHdrOffset = get4LE(ptr + kCDELocalOffset); + CHECK_OFFSET(localHdrOffset); + fileNameLen = get2LE(ptr + kCDENameLen); + extraLen = get2LE(ptr + kCDEExtraLen); + commentLen = get2LE(ptr + kCDECommentLen); + + //LOGV("+++ %d: localHdr=%d fnl=%d el=%d cl=%d\n", + // i, localHdrOffset, fileNameLen, extraLen, commentLen); + //LOGV(" '%.*s'\n", fileNameLen, ptr + kCDELen); + + /* add the CDE filename to the hash table */ + hash = computeHash((const char*)ptr + kCDELen, fileNameLen); + addToHash((const char*)ptr + kCDELen, fileNameLen, hash); + + localHdr = basePtr + localHdrOffset; + if (get4LE(localHdr) != kLFHSignature) { + LOGW("Bad offset to local header: %d (at %d)\n", + localHdrOffset, i); + goto bail; + } + + ptr += kCDELen + fileNameLen + extraLen + commentLen; + CHECK_OFFSET(ptr - basePtr); + } + + result = true; + +bail: + return result; +#undef CHECK_OFFSET +} + + +/* + * Simple string hash function for non-null-terminated strings. + */ +/*static*/ unsigned int ZipFileRO::computeHash(const char* str, int len) +{ + unsigned int hash = 0; + + while (len--) + hash = hash * 31 + *str++; + + return hash; +} + +/* + * Add a new entry to the hash table. + */ +void ZipFileRO::addToHash(const char* str, int strLen, unsigned int hash) +{ + int ent = hash & (mHashTableSize-1); + + /* + * We over-allocate the table, so we're guaranteed to find an empty slot. + */ + while (mHashTable[ent].name != NULL) + ent = (ent + 1) & (mHashTableSize-1); + + mHashTable[ent].name = str; + mHashTable[ent].nameLen = strLen; +} + +/* + * Find a matching entry. + * + * Returns 0 if not found. + */ +ZipEntryRO ZipFileRO::findEntryByName(const char* fileName) const +{ + int nameLen = strlen(fileName); + unsigned int hash = computeHash(fileName, nameLen); + int ent = hash & (mHashTableSize-1); + + while (mHashTable[ent].name != NULL) { + if (mHashTable[ent].nameLen == nameLen && + memcmp(mHashTable[ent].name, fileName, nameLen) == 0) + { + /* match */ + return (ZipEntryRO) (ent + kZipEntryAdj); + } + + ent = (ent + 1) & (mHashTableSize-1); + } + + return NULL; +} + +/* + * Find the Nth entry. + * + * This currently involves walking through the sparse hash table, counting + * non-empty entries. If we need to speed this up we can either allocate + * a parallel lookup table or (perhaps better) provide an iterator interface. + */ +ZipEntryRO ZipFileRO::findEntryByIndex(int idx) const +{ + if (idx < 0 || idx >= mNumEntries) { + LOGW("Invalid index %d\n", idx); + return NULL; + } + + for (int ent = 0; ent < mHashTableSize; ent++) { + if (mHashTable[ent].name != NULL) { + if (idx-- == 0) + return (ZipEntryRO) (ent + kZipEntryAdj); + } + } + + return NULL; +} + +/* + * Get the useful fields from the zip entry. + * + * Returns "false" if the offsets to the fields or the contents of the fields + * appear to be bogus. + */ +bool ZipFileRO::getEntryInfo(ZipEntryRO entry, int* pMethod, long* pUncompLen, + long* pCompLen, off_t* pOffset, long* pModWhen, long* pCrc32) const +{ + int ent = entryToIndex(entry); + if (ent < 0) + return false; + + /* + * Recover the start of the central directory entry from the filename + * pointer. + */ + const unsigned char* basePtr = (const unsigned char*)mFileMap->getDataPtr(); + const unsigned char* ptr = (const unsigned char*) mHashTable[ent].name; + size_t zipLength = mFileMap->getDataLength(); + + ptr -= kCDELen; + + int method = get2LE(ptr + kCDEMethod); + if (pMethod != NULL) + *pMethod = method; + + if (pModWhen != NULL) + *pModWhen = get4LE(ptr + kCDEModWhen); + if (pCrc32 != NULL) + *pCrc32 = get4LE(ptr + kCDECRC); + + /* + * We need to make sure that the lengths are not so large that somebody + * trying to map the compressed or uncompressed data runs off the end + * of the mapped region. + */ + unsigned long localHdrOffset = get4LE(ptr + kCDELocalOffset); + if (localHdrOffset + kLFHLen >= zipLength) { + LOGE("ERROR: bad local hdr offset in zip\n"); + return false; + } + const unsigned char* localHdr = basePtr + localHdrOffset; + off_t dataOffset = localHdrOffset + kLFHLen + + get2LE(localHdr + kLFHNameLen) + get2LE(localHdr + kLFHExtraLen); + if ((unsigned long) dataOffset >= zipLength) { + LOGE("ERROR: bad data offset in zip\n"); + return false; + } + + if (pCompLen != NULL) { + *pCompLen = get4LE(ptr + kCDECompLen); + if (*pCompLen < 0 || (size_t)(dataOffset + *pCompLen) >= zipLength) { + LOGE("ERROR: bad compressed length in zip\n"); + return false; + } + } + if (pUncompLen != NULL) { + *pUncompLen = get4LE(ptr + kCDEUncompLen); + if (*pUncompLen < 0) { + LOGE("ERROR: negative uncompressed length in zip\n"); + return false; + } + if (method == kCompressStored && + (size_t)(dataOffset + *pUncompLen) >= zipLength) + { + LOGE("ERROR: bad uncompressed length in zip\n"); + return false; + } + } + + if (pOffset != NULL) { + *pOffset = dataOffset; + } + return true; +} + +/* + * Copy the entry's filename to the buffer. + */ +int ZipFileRO::getEntryFileName(ZipEntryRO entry, char* buffer, int bufLen) + const +{ + int ent = entryToIndex(entry); + if (ent < 0) + return -1; + + int nameLen = mHashTable[ent].nameLen; + if (bufLen < nameLen+1) + return nameLen+1; + + memcpy(buffer, mHashTable[ent].name, nameLen); + buffer[nameLen] = '\0'; + return 0; +} + +/* + * Create a new FileMap object that spans the data in "entry". + */ +FileMap* ZipFileRO::createEntryFileMap(ZipEntryRO entry) const +{ + /* + * TODO: the efficient way to do this is to modify FileMap to allow + * sub-regions of a file to be mapped. A reference-counting scheme + * can manage the base memory mapping. For now, we just create a brand + * new mapping off of the Zip archive file descriptor. + */ + + FileMap* newMap; + long compLen; + off_t offset; + + if (!getEntryInfo(entry, NULL, NULL, &compLen, &offset, NULL, NULL)) + return NULL; + + newMap = new FileMap(); + if (!newMap->create(mFileMap->getFileName(), mFd, offset, compLen, true)) { + newMap->release(); + return NULL; + } + + return newMap; +} + +/* + * Uncompress an entry, in its entirety, into the provided output buffer. + * + * This doesn't verify the data's CRC, which might be useful for + * uncompressed data. The caller should be able to manage it. + */ +bool ZipFileRO::uncompressEntry(ZipEntryRO entry, void* buffer) const +{ + const int kSequentialMin = 32768; + bool result = false; + int ent = entryToIndex(entry); + if (ent < 0) + return -1; + + const unsigned char* basePtr = (const unsigned char*)mFileMap->getDataPtr(); + int method; + long uncompLen, compLen; + off_t offset; + + getEntryInfo(entry, &method, &uncompLen, &compLen, &offset, NULL, NULL); + + /* + * Experiment with madvise hint. When we want to uncompress a file, + * we pull some stuff out of the central dir entry and then hit a + * bunch of compressed or uncompressed data sequentially. The CDE + * visit will cause a limited amount of read-ahead because it's at + * the end of the file. We could end up doing lots of extra disk + * access if the file we're prying open is small. Bottom line is we + * probably don't want to turn MADV_SEQUENTIAL on and leave it on. + * + * So, if the compressed size of the file is above a certain minimum + * size, temporarily boost the read-ahead in the hope that the extra + * pair of system calls are negated by a reduction in page faults. + */ + if (compLen > kSequentialMin) + mFileMap->advise(FileMap::SEQUENTIAL); + + if (method == kCompressStored) { + memcpy(buffer, basePtr + offset, uncompLen); + } else { + if (!inflateBuffer(buffer, basePtr + offset, uncompLen, compLen)) + goto bail; + } + + if (compLen > kSequentialMin) + mFileMap->advise(FileMap::NORMAL); + + result = true; + +bail: + return result; +} + +/* + * Uncompress an entry, in its entirety, to an open file descriptor. + * + * This doesn't verify the data's CRC, but probably should. + */ +bool ZipFileRO::uncompressEntry(ZipEntryRO entry, int fd) const +{ + bool result = false; + int ent = entryToIndex(entry); + if (ent < 0) + return -1; + + const unsigned char* basePtr = (const unsigned char*)mFileMap->getDataPtr(); + int method; + long uncompLen, compLen; + off_t offset; + + getEntryInfo(entry, &method, &uncompLen, &compLen, &offset, NULL, NULL); + + if (method == kCompressStored) { + ssize_t actual; + + actual = write(fd, basePtr + offset, uncompLen); + if (actual < 0) { + LOGE("Write failed: %s\n", strerror(errno)); + goto bail; + } else if (actual != uncompLen) { + LOGE("Partial write during uncompress (%d of %ld)\n", + (int)actual, uncompLen); + goto bail; + } else { + LOGI("+++ successful write\n"); + } + } else { + if (!inflateBuffer(fd, basePtr+offset, uncompLen, compLen)) + goto bail; + } + + result = true; + +bail: + return result; +} + +/* + * Uncompress "deflate" data from one buffer to another. + */ +/*static*/ bool ZipFileRO::inflateBuffer(void* outBuf, const void* inBuf, + long uncompLen, long compLen) +{ + bool result = false; + z_stream zstream; + int zerr; + + /* + * Initialize the zlib stream struct. + */ + memset(&zstream, 0, sizeof(zstream)); + zstream.zalloc = Z_NULL; + zstream.zfree = Z_NULL; + zstream.opaque = Z_NULL; + zstream.next_in = (Bytef*)inBuf; + zstream.avail_in = compLen; + zstream.next_out = (Bytef*) outBuf; + zstream.avail_out = uncompLen; + zstream.data_type = Z_UNKNOWN; + + /* + * Use the undocumented "negative window bits" feature to tell zlib + * that there's no zlib header waiting for it. + */ + zerr = inflateInit2(&zstream, -MAX_WBITS); + if (zerr != Z_OK) { + if (zerr == Z_VERSION_ERROR) { + LOGE("Installed zlib is not compatible with linked version (%s)\n", + ZLIB_VERSION); + } else { + LOGE("Call to inflateInit2 failed (zerr=%d)\n", zerr); + } + goto bail; + } + + /* + * Expand data. + */ + zerr = inflate(&zstream, Z_FINISH); + if (zerr != Z_STREAM_END) { + LOGW("Zip inflate failed, zerr=%d (nIn=%p aIn=%u nOut=%p aOut=%u)\n", + zerr, zstream.next_in, zstream.avail_in, + zstream.next_out, zstream.avail_out); + goto z_bail; + } + + /* paranoia */ + if ((long) zstream.total_out != uncompLen) { + LOGW("Size mismatch on inflated file (%ld vs %ld)\n", + zstream.total_out, uncompLen); + goto z_bail; + } + + result = true; + +z_bail: + inflateEnd(&zstream); /* free up any allocated structures */ + +bail: + return result; +} + +/* + * Uncompress "deflate" data from one buffer to an open file descriptor. + */ +/*static*/ bool ZipFileRO::inflateBuffer(int fd, const void* inBuf, + long uncompLen, long compLen) +{ + bool result = false; + const int kWriteBufSize = 32768; + unsigned char writeBuf[kWriteBufSize]; + z_stream zstream; + int zerr; + + /* + * Initialize the zlib stream struct. + */ + memset(&zstream, 0, sizeof(zstream)); + zstream.zalloc = Z_NULL; + zstream.zfree = Z_NULL; + zstream.opaque = Z_NULL; + zstream.next_in = (Bytef*)inBuf; + zstream.avail_in = compLen; + zstream.next_out = (Bytef*) writeBuf; + zstream.avail_out = sizeof(writeBuf); + zstream.data_type = Z_UNKNOWN; + + /* + * Use the undocumented "negative window bits" feature to tell zlib + * that there's no zlib header waiting for it. + */ + zerr = inflateInit2(&zstream, -MAX_WBITS); + if (zerr != Z_OK) { + if (zerr == Z_VERSION_ERROR) { + LOGE("Installed zlib is not compatible with linked version (%s)\n", + ZLIB_VERSION); + } else { + LOGE("Call to inflateInit2 failed (zerr=%d)\n", zerr); + } + goto bail; + } + + /* + * Loop while we have more to do. + */ + do { + /* + * Expand data. + */ + zerr = inflate(&zstream, Z_NO_FLUSH); + if (zerr != Z_OK && zerr != Z_STREAM_END) { + LOGW("zlib inflate: zerr=%d (nIn=%p aIn=%u nOut=%p aOut=%u)\n", + zerr, zstream.next_in, zstream.avail_in, + zstream.next_out, zstream.avail_out); + goto z_bail; + } + + /* write when we're full or when we're done */ + if (zstream.avail_out == 0 || + (zerr == Z_STREAM_END && zstream.avail_out != sizeof(writeBuf))) + { + long writeSize = zstream.next_out - writeBuf; + int cc = write(fd, writeBuf, writeSize); + if (cc != (int) writeSize) { + LOGW("write failed in inflate (%d vs %ld)\n", cc, writeSize); + goto z_bail; + } + + zstream.next_out = writeBuf; + zstream.avail_out = sizeof(writeBuf); + } + } while (zerr == Z_OK); + + assert(zerr == Z_STREAM_END); /* other errors should've been caught */ + + /* paranoia */ + if ((long) zstream.total_out != uncompLen) { + LOGW("Size mismatch on inflated file (%ld vs %ld)\n", + zstream.total_out, uncompLen); + goto z_bail; + } + + result = true; + +z_bail: + inflateEnd(&zstream); /* free up any allocated structures */ + +bail: + return result; +} diff --git a/libs/utils/ZipUtils.cpp b/libs/utils/ZipUtils.cpp new file mode 100644 index 0000000..bfbacfe --- /dev/null +++ b/libs/utils/ZipUtils.cpp @@ -0,0 +1,344 @@ +/* + * Copyright (C) 2007 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. + */ + +// +// Misc zip/gzip utility functions. +// + +#define LOG_TAG "ziputil" + +#include "utils/ZipUtils.h" +#include "utils/ZipFileRO.h" +#include "utils/Log.h" + +#include <stdlib.h> +#include <string.h> +#include <assert.h> + +#include <zlib.h> + +using namespace android; + +/* + * Utility function that expands zip/gzip "deflate" compressed data + * into a buffer. + * + * "fd" is an open file positioned at the start of the "deflate" data + * "buf" must hold at least "uncompressedLen" bytes. + */ +/*static*/ bool ZipUtils::inflateToBuffer(int fd, void* buf, + long uncompressedLen, long compressedLen) +{ + bool result = false; + const unsigned long kReadBufSize = 32768; + unsigned char* readBuf = NULL; + z_stream zstream; + int zerr; + unsigned long compRemaining; + + assert(uncompressedLen >= 0); + assert(compressedLen >= 0); + + readBuf = new unsigned char[kReadBufSize]; + if (readBuf == NULL) + goto bail; + compRemaining = compressedLen; + + /* + * Initialize the zlib stream. + */ + memset(&zstream, 0, sizeof(zstream)); + zstream.zalloc = Z_NULL; + zstream.zfree = Z_NULL; + zstream.opaque = Z_NULL; + zstream.next_in = NULL; + zstream.avail_in = 0; + zstream.next_out = (Bytef*) buf; + zstream.avail_out = uncompressedLen; + zstream.data_type = Z_UNKNOWN; + + /* + * Use the undocumented "negative window bits" feature to tell zlib + * that there's no zlib header waiting for it. + */ + zerr = inflateInit2(&zstream, -MAX_WBITS); + if (zerr != Z_OK) { + if (zerr == Z_VERSION_ERROR) { + LOGE("Installed zlib is not compatible with linked version (%s)\n", + ZLIB_VERSION); + } else { + LOGE("Call to inflateInit2 failed (zerr=%d)\n", zerr); + } + goto bail; + } + + /* + * Loop while we have data. + */ + do { + unsigned long getSize; + + /* read as much as we can */ + if (zstream.avail_in == 0) { + getSize = (compRemaining > kReadBufSize) ? + kReadBufSize : compRemaining; + LOGV("+++ reading %ld bytes (%ld left)\n", + getSize, compRemaining); + + int cc = read(fd, readBuf, getSize); + if (cc != (int) getSize) { + LOGD("inflate read failed (%d vs %ld)\n", + cc, getSize); + goto z_bail; + } + + compRemaining -= getSize; + + zstream.next_in = readBuf; + zstream.avail_in = getSize; + } + + /* uncompress the data */ + zerr = inflate(&zstream, Z_NO_FLUSH); + if (zerr != Z_OK && zerr != Z_STREAM_END) { + LOGD("zlib inflate call failed (zerr=%d)\n", zerr); + goto z_bail; + } + + /* output buffer holds all, so no need to write the output */ + } while (zerr == Z_OK); + + assert(zerr == Z_STREAM_END); /* other errors should've been caught */ + + if ((long) zstream.total_out != uncompressedLen) { + LOGW("Size mismatch on inflated file (%ld vs %ld)\n", + zstream.total_out, uncompressedLen); + goto z_bail; + } + + // success! + result = true; + +z_bail: + inflateEnd(&zstream); /* free up any allocated structures */ + +bail: + delete[] readBuf; + return result; +} + +/* + * Utility function that expands zip/gzip "deflate" compressed data + * into a buffer. + * + * (This is a clone of the previous function, but it takes a FILE* instead + * of an fd. We could pass fileno(fd) to the above, but we can run into + * trouble when "fp" has a different notion of what fd's file position is.) + * + * "fp" is an open file positioned at the start of the "deflate" data + * "buf" must hold at least "uncompressedLen" bytes. + */ +/*static*/ bool ZipUtils::inflateToBuffer(FILE* fp, void* buf, + long uncompressedLen, long compressedLen) +{ + bool result = false; + const unsigned long kReadBufSize = 32768; + unsigned char* readBuf = NULL; + z_stream zstream; + int zerr; + unsigned long compRemaining; + + assert(uncompressedLen >= 0); + assert(compressedLen >= 0); + + readBuf = new unsigned char[kReadBufSize]; + if (readBuf == NULL) + goto bail; + compRemaining = compressedLen; + + /* + * Initialize the zlib stream. + */ + memset(&zstream, 0, sizeof(zstream)); + zstream.zalloc = Z_NULL; + zstream.zfree = Z_NULL; + zstream.opaque = Z_NULL; + zstream.next_in = NULL; + zstream.avail_in = 0; + zstream.next_out = (Bytef*) buf; + zstream.avail_out = uncompressedLen; + zstream.data_type = Z_UNKNOWN; + + /* + * Use the undocumented "negative window bits" feature to tell zlib + * that there's no zlib header waiting for it. + */ + zerr = inflateInit2(&zstream, -MAX_WBITS); + if (zerr != Z_OK) { + if (zerr == Z_VERSION_ERROR) { + LOGE("Installed zlib is not compatible with linked version (%s)\n", + ZLIB_VERSION); + } else { + LOGE("Call to inflateInit2 failed (zerr=%d)\n", zerr); + } + goto bail; + } + + /* + * Loop while we have data. + */ + do { + unsigned long getSize; + + /* read as much as we can */ + if (zstream.avail_in == 0) { + getSize = (compRemaining > kReadBufSize) ? + kReadBufSize : compRemaining; + LOGV("+++ reading %ld bytes (%ld left)\n", + getSize, compRemaining); + + int cc = fread(readBuf, getSize, 1, fp); + if (cc != (int) getSize) { + LOGD("inflate read failed (%d vs %ld)\n", + cc, getSize); + goto z_bail; + } + + compRemaining -= getSize; + + zstream.next_in = readBuf; + zstream.avail_in = getSize; + } + + /* uncompress the data */ + zerr = inflate(&zstream, Z_NO_FLUSH); + if (zerr != Z_OK && zerr != Z_STREAM_END) { + LOGD("zlib inflate call failed (zerr=%d)\n", zerr); + goto z_bail; + } + + /* output buffer holds all, so no need to write the output */ + } while (zerr == Z_OK); + + assert(zerr == Z_STREAM_END); /* other errors should've been caught */ + + if ((long) zstream.total_out != uncompressedLen) { + LOGW("Size mismatch on inflated file (%ld vs %ld)\n", + zstream.total_out, uncompressedLen); + goto z_bail; + } + + // success! + result = true; + +z_bail: + inflateEnd(&zstream); /* free up any allocated structures */ + +bail: + delete[] readBuf; + return result; +} + +/* + * Look at the contents of a gzip archive. We want to know where the + * data starts, and how long it will be after it is uncompressed. + * + * We expect to find the CRC and length as the last 8 bytes on the file. + * This is a pretty reasonable thing to expect for locally-compressed + * files, but there's a small chance that some extra padding got thrown + * on (the man page talks about compressed data written to tape). We + * don't currently deal with that here. If "gzip -l" whines, we're going + * to fail too. + * + * On exit, "fp" is pointing at the start of the compressed data. + */ +/*static*/ bool ZipUtils::examineGzip(FILE* fp, int* pCompressionMethod, + long* pUncompressedLen, long* pCompressedLen, unsigned long* pCRC32) +{ + enum { // flags + FTEXT = 0x01, + FHCRC = 0x02, + FEXTRA = 0x04, + FNAME = 0x08, + FCOMMENT = 0x10, + }; + int ic; + int method, flags; + int i; + + ic = getc(fp); + if (ic != 0x1f || getc(fp) != 0x8b) + return false; // not gzip + method = getc(fp); + flags = getc(fp); + + /* quick sanity checks */ + if (method == EOF || flags == EOF) + return false; + if (method != ZipFileRO::kCompressDeflated) + return false; + + /* skip over 4 bytes of mod time, 1 byte XFL, 1 byte OS */ + for (i = 0; i < 6; i++) + (void) getc(fp); + /* consume "extra" field, if present */ + if ((flags & FEXTRA) != 0) { + int len; + + len = getc(fp); + len |= getc(fp) << 8; + while (len-- && getc(fp) != EOF) + ; + } + /* consume filename, if present */ + if ((flags & FNAME) != 0) { + do { + ic = getc(fp); + } while (ic != 0 && ic != EOF); + } + /* consume comment, if present */ + if ((flags & FCOMMENT) != 0) { + do { + ic = getc(fp); + } while (ic != 0 && ic != EOF); + } + /* consume 16-bit header CRC, if present */ + if ((flags & FHCRC) != 0) { + (void) getc(fp); + (void) getc(fp); + } + + if (feof(fp) || ferror(fp)) + return false; + + /* seek to the end; CRC and length are in the last 8 bytes */ + long curPosn = ftell(fp); + unsigned char buf[8]; + fseek(fp, -8, SEEK_END); + *pCompressedLen = ftell(fp) - curPosn; + + if (fread(buf, 1, 8, fp) != 8) + return false; + /* seek back to start of compressed data */ + fseek(fp, curPosn, SEEK_SET); + + *pCompressionMethod = method; + *pCRC32 = ZipFileRO::get4LE(&buf[0]); + *pUncompressedLen = ZipFileRO::get4LE(&buf[4]); + + return true; +} + diff --git a/libs/utils/characterData.h b/libs/utils/characterData.h new file mode 100644 index 0000000..e931d99 --- /dev/null +++ b/libs/utils/characterData.h @@ -0,0 +1,730 @@ +/* + * Copyright (C) 2008 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. + */ + +// Automatically generated on 07-11-2006 by make-CharacterDataC +// DO NOT EDIT DIRECTLY +namespace CharacterData { + + // Structure containing an array of ranges + struct Range { + int length; + const uint32_t* array; + }; + + // For Latin1 characters just index into this array to get the index and decomposition + static const uint16_t LATIN1_DATA[] = { + 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, + 0x0001, 0x0002, 0x0003, 0x0002, 0x0004, 0x0003, 0x0001, 0x0001, + 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, + 0x0001, 0x0001, 0x0001, 0x0001, 0x0003, 0x0003, 0x0003, 0x0002, + 0x0005, 0x0006, 0x0006, 0x0007, 0x0008, 0x0007, 0x0006, 0x0006, + 0x0009, 0x000A, 0x0006, 0x000B, 0x000C, 0x000D, 0x000C, 0x000C, + 0x000E, 0x000F, 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, + 0x0016, 0x0017, 0x000C, 0x0006, 0x0018, 0x0019, 0x001A, 0x0006, + 0x0006, 0x001B, 0x001C, 0x001D, 0x001E, 0x001F, 0x0020, 0x0021, + 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, 0x0028, 0x0029, + 0x002A, 0x002B, 0x002C, 0x002D, 0x002E, 0x002F, 0x0030, 0x0031, + 0x0032, 0x0033, 0x0034, 0x0035, 0x0006, 0x0036, 0x0037, 0x0038, + 0x0037, 0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F, + 0x0050, 0x0051, 0x0052, 0x0035, 0x0019, 0x0036, 0x0019, 0x0001, + 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0003, 0x0001, 0x0001, + 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, + 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, + 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, + 0x5853, 0x0006, 0x0008, 0x0008, 0x0008, 0x0008, 0x0054, 0x0054, + 0x1037, 0x0054, 0x7855, 0x0056, 0x0019, 0x0057, 0x0054, 0x1037, + 0x0058, 0x0059, 0x785A, 0x785B, 0x1037, 0x105C, 0x0054, 0x0006, + 0x1037, 0x785D, 0x7855, 0x005E, 0x305F, 0x305F, 0x305F, 0x0006, + 0x0860, 0x0860, 0x0860, 0x0860, 0x0860, 0x0860, 0x0060, 0x0860, + 0x0860, 0x0860, 0x0860, 0x0860, 0x0860, 0x0860, 0x0860, 0x0860, + 0x0060, 0x0860, 0x0860, 0x0860, 0x0860, 0x0860, 0x0860, 0x0019, + 0x0060, 0x0860, 0x0860, 0x0860, 0x0860, 0x0860, 0x0060, 0x0055, + 0x0861, 0x0861, 0x0861, 0x0861, 0x0861, 0x0861, 0x0061, 0x0861, + 0x0861, 0x0861, 0x0861, 0x0861, 0x0861, 0x0861, 0x0861, 0x0861, + 0x0061, 0x0861, 0x0861, 0x0861, 0x0861, 0x0861, 0x0861, 0x0019, + 0x0061, 0x0861, 0x0861, 0x0861, 0x0861, 0x0861, 0x0061, 0x0862 + }; + + // Each of these arrays is stripped into ranges. In order to build the arrays, each + // codepoint was bit-shifted so that even and odd characters were separated into different + // arrays. The identifier of each array is the top byte after bit-shifting. + // The numbers stored in the array are the bit-shifted codepoint, the decomposition, and an + // index into another array of all possible packed data values. The top 16 bits are the + // codepoint and the bottom 16 are the decomposition and index. The top 5 bits for the decomposition + // and the rest for the index. + static const uint32_t a0[] = { + 0x00800863, 0x00880063, 0x00890863, 0x00930063, 0x00940863, 0x00980864, 0x00991063, 0x009A0863, + 0x009C0055, 0x009D0865, 0x00A01065, 0x00A10065, 0x00A20865, 0x00A50063, 0x00A60863, 0x00A90063, + 0x00AA0863, 0x00B30063, 0x00B40863, 0x00BC0866, 0x00BD0865, 0x00C00055, 0x00C10063, 0x00C30067, + 0x00C40065, 0x00C50068, 0x00C60065, 0x00C70069, 0x00C8006A, 0x00C90065, 0x00CA006B, 0x00CB006C, + 0x00CC0063, 0x00CD006D, 0x00CE006C, 0x00CF006E, 0x00D00863, 0x00D10063, 0x00D3006F, 0x00D40065, + 0x00D50055, 0x00D60063, 0x00D7006F, 0x00D80865, 0x00D90070, 0x00DA0065, 0x00DC0063, 0x00DD0055, + 0x00DE0063, 0x00DF0055, 0x00E00071, 0x00E21072, 0x00E31073, 0x00E41074, 0x00E51072, 0x00E61073, + 0x00E70865, 0x00EF0863, 0x00F20063, 0x00F30863, 0x00F80855, 0x00F91074, 0x00FA0863, 0x00FB0075, + 0x00FC0863, 0x010E0063, 0x010F0863, 0x01100076, 0x01110063, 0x01130863, 0x011A0055, 0x011D0077, + 0x011E0065, 0x011F0077, 0x01200055, 0x01210078, 0x01280055, 0x012A0079, 0x012B007A, 0x012C0055, + 0x0130007A, 0x01310055, 0x0134007B, 0x01350055, 0x0139007C, 0x013A0055, 0x0140007D, 0x01410055, + 0x0144007D, 0x0145007E, 0x01460055, 0x0149007F, 0x014A0080, 0x014B0055, 0x01587881, 0x015D0082, + 0x015E0081, 0x01610037, 0x01630082, 0x01680081, 0x01690037, 0x016C1037, 0x016F0037, 0x01707881, + 0x01730037, 0x01770081, 0x01780037, 0x01800083, 0x01A00883, 0x01A10083, 0x01A20883, 0x01A30083, + 0x01B80078, 0x01BA0837, 0x01BB0078, 0x01BD1081, 0x01BE0078, 0x01BF0806, 0x01C00078, 0x01C21037, + 0x01C30884, 0x01C40885, 0x01C60886, 0x01C70887, 0x01C80855, 0x01C90060, 0x01D10078, 0x01D20060, + 0x01D50860, 0x01D60888, 0x01D70889, 0x01D80855, 0x01D90061, 0x01E1008A, 0x01E20061, 0x01E50861, + 0x01E6088B, 0x01E7088C, 0x01E8108D, 0x01E91077, 0x01EA0877, 0x01EB108E, 0x01EC0063, 0x01F8108F, + 0x01F91090, 0x01FA1091, 0x01FB0019, 0x01FC0065, 0x01FD0063, 0x01FE0055, 0x01FF0077, 0x02000892, + 0x02010092, 0x02060892, 0x02080060, 0x02180061, 0x02280893, 0x02290093, 0x022E0893, 0x02300063, + 0x023B0863, 0x023C0063, 0x02410094, 0x02420083, 0x02440095, 0x02450063, 0x02600077, 0x02610865, + 0x02620065, 0x02680863, 0x026A0063, 0x026B0863, 0x026C0063, 0x026D0863, 0x02700063, 0x02710863, + 0x02740063, 0x02750863, 0x027B0063, 0x027C0863, 0x027D0078, 0x02800063, 0x02880078, 0x02990096, + 0x02AC0078, 0x02AD0097, 0x02B00078, 0x02B10098, 0x02C40078, 0x02C50099, 0x02C60078, 0x02C90083, + 0x02DD0078, 0x02DE0083, 0x02DF009A, 0x02E10083, 0x02E3009A, 0x02E40078, 0x02E8009B, 0x02F60078, + 0x02F8009B, 0x02FA009A, 0x02FB0078, 0x0300009C, 0x03020078, 0x0306000C, 0x03070054, 0x03080083, + 0x030B0078, 0x030F009D, 0x03100078, 0x0311089E, 0x0314009E, 0x031E0078, 0x0320009F, 0x0321009E, + 0x03260083, 0x033000A0, 0x033100A1, 0x033200A2, 0x033300A3, 0x033400A4, 0x03350007, 0x033600A5, + 0x0337009E, 0x03380083, 0x0339009E, 0x033B109E, 0x033D009E, 0x0360089E, 0x0362009E, 0x036A009D, + 0x036B0083, 0x036F0095, 0x03700083, 0x0373009F, 0x03740083, 0x0377009E, 0x0378000E, 0x03790010, + 0x037A0012, 0x037B0014, 0x037C0016, 0x037D009E, 0x037F00A6, 0x0380009D, 0x03870078, 0x0388009E, + 0x03980083, 0x03A60078, 0x03A7009E, 0x03B70078, 0x03C0009E, 0x03D30083, 0x03D90078, 0x04810083, + 0x04820071, 0x049A0871, 0x049B0071, 0x049D0078, 0x049E0083, 0x049F00A7, 0x04A10083, 0x04A500A7, + 0x04A70078, 0x04A80071, 0x04A90083, 0x04AB0078, 0x04AC0871, 0x04B00071, 0x04B10083, 0x04B20097, + 0x04B300A8, 0x04B400A9, 0x04B500AA, 0x04B600AB, 0x04B700AC, 0x04B80097, 0x04B90078, 0x04C100A7, + 0x04C20078, 0x04C30071, 0x04C70078, 0x04C80071, 0x04C90078, 0x04CA0071, 0x04DA0078, 0x04DB0071, + 0x04DD0078, 0x04DE0083, 0x04DF00A7, 0x04E10083, 0x04E30078, 0x04E400A7, 0x04E50078, 0x04E608A7, + 0x04E70071, 0x04E80078, 0x04EE0871, 0x04EF0078, 0x04F00071, 0x04F10083, 0x04F20078, 0x04F300A8, + 0x04F400A9, 0x04F500AA, 0x04F600AB, 0x04F700AC, 0x04F80071, 0x04F90008, 0x04FA00AD, 0x04FB00AE, + 0x04FC00AF, 0x04FD0094, 0x04FE0078, 0x05010083, 0x05020078, 0x05030071, 0x05060078, 0x05080071, + 0x05090078, 0x050A0071, 0x051A0078, 0x051B0871, 0x051C0071, 0x051D0078, 0x051E0083, 0x051F00A7, + 0x05210083, 0x05220078, 0x05240083, 0x05250078, 0x05260083, 0x05270078, 0x052D0871, 0x052E0071, + 0x052F0871, 0x05300078, 0x053300A8, 0x053400A9, 0x053500AA, 0x053600AB, 0x053700AC, 0x05380083, + 0x05390071, 0x053B0078, 0x05410083, 0x05420078, 0x05430071, 0x05470078, 0x05480071, 0x05490078, + 0x054A0071, 0x055A0078, 0x055B0071, 0x055D0078, 0x055E0083, 0x055F00A7, 0x05610083, 0x05630078, + 0x05640083, 0x05650078, 0x056600A7, 0x05670078, 0x05680071, 0x05690078, 0x05700071, 0x05710083, + 0x05720078, 0x057300A8, 0x057400A9, 0x057500AA, 0x057600AB, 0x057700AC, 0x05780078, 0x058100A7, + 0x05820078, 0x05830071, 0x05870078, 0x05880071, 0x05890078, 0x058A0071, 0x059A0078, 0x059B0071, + 0x059D0078, 0x059E0083, 0x059F00A7, 0x05A10083, 0x05A20078, 0x05A408A7, 0x05A50078, 0x05A608A7, + 0x05A70078, 0x05AB0083, 0x05AC0078, 0x05AE0871, 0x05AF0078, 0x05B00071, 0x05B10078, 0x05B300A8, + 0x05B400A9, 0x05B500AA, 0x05B600AB, 0x05B700AC, 0x05B80094, 0x05B90078, 0x05C10083, 0x05C20078, + 0x05C30071, 0x05C60078, 0x05C70071, 0x05CA0871, 0x05CB0078, 0x05CD0071, 0x05D00078, 0x05D20071, + 0x05D30078, 0x05D40071, 0x05D60078, 0x05D70071, 0x05DD0078, 0x05DF00A7, 0x05E00083, 0x05E100A7, + 0x05E20078, 0x05E300A7, 0x05E508A7, 0x05E70078, 0x05F300A8, 0x05F400A9, 0x05F500AA, 0x05F600AB, + 0x05F700AC, 0x05F800B0, 0x05F900B1, 0x05FA0054, 0x05FE0078, 0x060100A7, 0x06020078, 0x06030071, + 0x061A0078, 0x061B0071, 0x061D0078, 0x061F0083, 0x062100A7, 0x06230083, 0x06240883, 0x06250083, + 0x06270078, 0x062B0083, 0x062C0078, 0x06300071, 0x06310078, 0x063300A8, 0x063400A9, 0x063500AA, + 0x063600AB, 0x063700AC, 0x06380078, 0x064100A7, 0x06420078, 0x06430071, 0x065A0078, 0x065B0071, + 0x065D0078, 0x065E0083, 0x065F00A7, 0x066008A7, 0x066100A7, 0x066300B2, 0x066408A7, 0x06660083, + 0x06670078, 0x066B00A7, 0x066C0078, 0x066F0071, 0x06710078, 0x067300A8, 0x067400A9, 0x067500AA, + 0x067600AB, 0x067700AC, 0x06780078, 0x068100A7, 0x06820078, 0x06830071, 0x069D0078, 0x069F00A7, + 0x06A10083, 0x06A20078, 0x06A300A7, 0x06A508A7, 0x06A70078, 0x06B00071, 0x06B10078, 0x06B300A8, + 0x06B400A9, 0x06B500AA, 0x06B600AB, 0x06B700AC, 0x06B80078, 0x06C100A7, 0x06C20078, 0x06C30071, + 0x06CC0078, 0x06CD0071, 0x06D90078, 0x06DA0071, 0x06DE0078, 0x06E00071, 0x06E40078, 0x06E50083, + 0x06E60078, 0x06E800A7, 0x06E90083, 0x06EC00A7, 0x06ED08A7, 0x06F00078, 0x06F900A7, 0x06FA0097, + 0x06FB0078, 0x07010071, 0x071A0083, 0x071E0078, 0x07200071, 0x07230081, 0x07240083, 0x072800A8, + 0x072900A9, 0x072A00AA, 0x072B00AB, 0x072C00AC, 0x072D0097, 0x072E0078, 0x07410071, 0x07430078, + 0x07440071, 0x07460078, 0x074A0071, 0x074C0078, 0x074D0071, 0x07500078, 0x07510071, 0x07520078, + 0x07550071, 0x07560078, 0x07570071, 0x075A0083, 0x075D0078, 0x075E0083, 0x075F0078, 0x07600071, + 0x07630081, 0x07640083, 0x07670078, 0x076800A8, 0x076900A9, 0x076A00AA, 0x076B00AB, 0x076C00AC, + 0x076D0078, 0x076E1071, 0x076F0078, 0x07800071, 0x07810094, 0x07820097, 0x07865897, 0x07870097, + 0x078A0094, 0x078C0083, 0x078D0094, 0x079000A8, 0x079100A9, 0x079200AA, 0x079300AB, 0x079400AC, + 0x079500B3, 0x079A0094, 0x079D00B4, 0x079F00A7, 0x07A00071, 0x07A40078, 0x07A50071, 0x07A90871, + 0x07AA0071, 0x07AE0871, 0x07AF0071, 0x07B60078, 0x07B90083, 0x07BB0883, 0x07BD0083, 0x07C40071, + 0x07C60078, 0x07C80083, 0x07CC0078, 0x07CD0083, 0x07D10883, 0x07D20083, 0x07D60883, 0x07D70083, + 0x07DF0094, 0x07E30083, 0x07E40094, 0x07E70078, 0x07E80097, 0x07E90078, 0x08000071, 0x08110078, + 0x08120071, 0x08130871, 0x08140078, 0x08150071, 0x081600A7, 0x08170083, 0x081A0078, 0x081B0083, + 0x081C00A7, 0x081D0078, 0x082000A8, 0x082100A9, 0x082200AA, 0x082300AB, 0x082400AC, 0x08250097, + 0x08280071, 0x082B00A7, 0x082C0083, 0x082D0078, 0x085000B5, 0x08630078, 0x08680071, 0x087E7881, + 0x087F0078, 0x08800071, 0x08AD0078, 0x08B00071, 0x08D20078, 0x08D40071, 0x08FD0078, 0x09000071, + 0x09270078, 0x09280071, 0x092F0078, 0x09300071, 0x09470078, 0x09480071, 0x095B0078, 0x095C0071, + 0x09630078, 0x09640071, 0x098B0078, 0x098C0071, 0x09AE0078, 0x09B00094, 0x09B10097, 0x09B500B6, + 0x09B600B7, 0x09B700B8, 0x09B800B9, 0x09B900B0, 0x09BA00BA, 0x09BB00BB, 0x09BC00BC, 0x09BD00BD, + 0x09BE00BE, 0x09BF0078, 0x09C00071, 0x09C80054, 0x09CD0078, 0x09D00071, 0x09FB0078, 0x0A010071, + 0x0B370097, 0x0B380071, 0x0B3C0078, 0x0B400005, 0x0B410071, 0x0B4E00BF, 0x0B4F0078, 0x0B500071, + 0x0B760097, 0x0B7700C0, 0x0B7800C1, 0x0B790078, 0x0B800071, 0x0B890083, 0x0B8B0078, 0x0B900071, + 0x0B990083, 0x0B9B0097, 0x0B9C0078, 0x0BA00071, 0x0BA90083, 0x0BAA0078, 0x0BB00071, 0x0BB90083, + 0x0BBA0078, 0x0BC00071, 0x0BDA00C2, 0x0BDB00A7, 0x0BDC0083, 0x0BDF00A7, 0x0BE30083, 0x0BE400A7, + 0x0BE50083, 0x0BEA0097, 0x0BEE0071, 0x0BEF0078, 0x0BF000A8, 0x0BF100A9, 0x0BF200AA, 0x0BF300AB, + 0x0BF400AC, 0x0BF50078, 0x0BF800C3, 0x0BF900C4, 0x0BFA00C5, 0x0BFB00C6, 0x0BFC00C7, 0x0BFD0078, + 0x0C000006, 0x0C030099, 0x0C040006, 0x0C060083, 0x0C070005, 0x0C0800A8, 0x0C0900A9, 0x0C0A00AA, + 0x0C0B00AB, 0x0C0C00AC, 0x0C0D0078, 0x0C100071, 0x0C3C0078, 0x0C400071, 0x0C550078, 0x0C800071, + 0x0C8F0078, 0x0C900083, 0x0C9200A7, 0x0C940083, 0x0C9500C8, 0x0C960078, 0x0C9800A7, 0x0C990083, + 0x0C9A00A7, 0x0C9D0083, 0x0C9E0078, 0x0CA00054, 0x0CA10078, 0x0CA20006, 0x0CA300A8, 0x0CA400A9, + 0x0CA500AA, 0x0CA600AB, 0x0CA700AC, 0x0CA80071, 0x0CB70078, 0x0CB80071, 0x0CBB0078, 0x0CC00071, + 0x0CD50078, 0x0CD800A7, 0x0CE10071, 0x0CE400A7, 0x0CE50078, 0x0CE800A8, 0x0CE900A9, 0x0CEA00AA, + 0x0CEB00AB, 0x0CEC00AC, 0x0CED0078, 0x0CEF0006, 0x0CF00054, 0x0D000071, 0x0D0C0083, 0x0D0D00A7, + 0x0D0E0078, 0x0D0F0097, 0x0D100078, 0x0E800055, 0x0E967881, 0x0EA70081, 0x0EA87881, 0x0EB17055, + 0x0EB60055, 0x0EBC7881, 0x0EBD0055, 0x0ECE7881, 0x0EE00083, 0x0EE20078, 0x0F000863, 0x0F4B0855, + 0x0F4D1055, 0x0F4E0078, 0x0F500863, 0x0F7D0078, 0x0F8008C9, 0x0F8408CA, 0x0F8808C9, 0x0F8B0078, + 0x0F8C08CA, 0x0F8F0078, 0x0F9008C9, 0x0F9408CA, 0x0F9808C9, 0x0F9C08CA, 0x0FA008C9, 0x0FA30078, + 0x0FA408CA, 0x0FA70078, 0x0FA80855, 0x0FAC0078, 0x0FB008C9, 0x0FB408CA, 0x0FB808CB, 0x0FB908CC, + 0x0FBB08CD, 0x0FBC08CE, 0x0FBD08CF, 0x0FBE08D0, 0x0FBF0078, 0x0FC008C9, 0x0FC408D1, 0x0FC808C9, + 0x0FCC08D1, 0x0FD008C9, 0x0FD408D1, 0x0FD808C9, 0x0FD90855, 0x0FDC08CA, 0x0FDD08D2, 0x0FDE08D3, + 0x0FDF08D4, 0x0FE01037, 0x0FE10855, 0x0FE408D5, 0x0FE608D3, 0x0FE70837, 0x0FE808C9, 0x0FE90855, + 0x0FEA0078, 0x0FEB0855, 0x0FEC08CA, 0x0FED08D6, 0x0FEE0078, 0x0FEF0837, 0x0FF008C9, 0x0FF10855, + 0x0FF408CA, 0x0FF508D7, 0x0FF608D8, 0x0FF70837, 0x0FF80078, 0x0FF90855, 0x0FFC08D9, 0x0FFD08DA, + 0x0FFE08D3, 0x0FFF1037, 0x10000805, 0x10011005, 0x10060057, 0x100700C2, 0x10080099, 0x100B0006, + 0x100C00DB, 0x100D00B4, 0x100E00DB, 0x100F00B4, 0x10100006, 0x10121006, 0x101400DC, 0x101500DD, + 0x101600DE, 0x101700DF, 0x10180007, 0x101A1007, 0x101B1006, 0x101C0006, 0x101D00E0, 0x101E1006, + 0x10200038, 0x10210006, 0x102200E1, 0x1023000A, 0x10241006, 0x10250006, 0x10290019, 0x102A0038, + 0x102B0006, 0x10300057, 0x10320078, 0x10350057, 0x103878E2, 0x10390078, 0x103A78E3, 0x103B78E4, + 0x103C78E5, 0x103D780B, 0x103E7819, 0x103F780A, 0x104070E2, 0x1041705A, 0x104270E3, 0x104370E4, + 0x104470E5, 0x1045700B, 0x10467019, 0x1047700A, 0x10487081, 0x104B0078, 0x10500008, 0x10541008, + 0x10550008, 0x105B0078, 0x10680083, 0x106F0095, 0x10730083, 0x10760078, 0x10801054, 0x10812877, + 0x10820054, 0x10831054, 0x10840054, 0x10852855, 0x10862877, 0x10872855, 0x10882877, 0x108A0054, + 0x108B1054, 0x108C0054, 0x108D2877, 0x108F0054, 0x10907854, 0x10922877, 0x109308E6, 0x10942877, + 0x109508E7, 0x10962877, 0x10970058, 0x10982877, 0x10990054, 0x109A2855, 0x109B1071, 0x109D0054, + 0x109E2855, 0x109F2877, 0x10A028E8, 0x10A10019, 0x10A32855, 0x10A50054, 0x10A70078, 0x10AA305F, + 0x10B010E9, 0x10B110EA, 0x10B210EB, 0x10B310EC, 0x10B410ED, 0x10B510EE, 0x10B610EF, 0x10B710F0, + 0x10B810F1, 0x10B910F2, 0x10BA10F3, 0x10BB10F4, 0x10BC10F5, 0x10BD10F6, 0x10BE10F7, 0x10BF10F8, + 0x10C000F9, 0x10C100FA, 0x10C20078, 0x10C80019, 0x10CB0054, 0x10CD0819, 0x10CE0054, 0x10D00019, + 0x10D10054, 0x10D30019, 0x10D40054, 0x10D70819, 0x10D80054, 0x10E70819, 0x10E80054, 0x10E90019, + 0x10EB0054, 0x10FA0019, 0x110100E8, 0x110208E8, 0x11030019, 0x110400FB, 0x110608FC, 0x11070019, + 0x1109000B, 0x110A0019, 0x110B00E8, 0x110C0019, 0x110D00E8, 0x110F0019, 0x111000E8, 0x111208E8, + 0x11140019, 0x111610E8, 0x111700E8, 0x111810E8, 0x111900E8, 0x111A0019, 0x111E00FD, 0x111F00E8, + 0x112208E8, 0x112300E8, 0x11270019, 0x112900FD, 0x112B0019, 0x113008E8, 0x113200FD, 0x11360019, + 0x113708FD, 0x113900FD, 0x113A08FD, 0x113B00FD, 0x113C08FD, 0x113D00FD, 0x114008FD, 0x114100FD, + 0x114208FD, 0x114300FD, 0x114408FD, 0x114500FD, 0x114600E8, 0x11470019, 0x114800FE, 0x114A0019, + 0x114C00FF, 0x114D0019, 0x115100FD, 0x11520019, 0x11530100, 0x11540101, 0x115500E8, 0x115608E8, + 0x115800FD, 0x115C00E8, 0x115D0019, 0x115F00E8, 0x11600019, 0x116500FE, 0x11670019, 0x116800FD, + 0x11690019, 0x116B00FD, 0x117008FD, 0x117200FD, 0x117508FD, 0x11770019, 0x117800FD, 0x11790102, + 0x117B0103, 0x117C00E8, 0x117D0104, 0x117F0105, 0x11800054, 0x118400FD, 0x11860054, 0x119000E8, + 0x11910054, 0x1195080A, 0x11960054, 0x119B0094, 0x11BE0019, 0x11BF0054, 0x11CE0019, 0x11DA00B4, + 0x11DB0006, 0x11DC0054, 0x11EE0078, 0x12000054, 0x12140078, 0x12200054, 0x12260078, 0x12301906, + 0x12311907, 0x12321908, 0x12331909, 0x1234190A, 0x1235190B, 0x1236190C, 0x1237190D, 0x1238190E, + 0x1239190F, 0x123A1106, 0x123B1107, 0x123C1108, 0x123D1109, 0x123E110A, 0x123F110B, 0x1240110C, + 0x1241110D, 0x1242110E, 0x1243110F, 0x1244105D, 0x1245105B, 0x12461110, 0x12471111, 0x12481112, + 0x12491113, 0x124A1114, 0x124B1115, 0x124C1116, 0x124D1117, 0x124E1094, 0x125B1918, 0x12681919, + 0x127518C3, 0x1276011A, 0x1277011B, 0x1278011C, 0x1279011D, 0x127A011E, 0x127B00C4, 0x127C00C5, + 0x127D00C6, 0x127E00C7, 0x127F011F, 0x12800054, 0x12FC0019, 0x13000054, 0x134F0078, 0x13500054, + 0x13560094, 0x13570054, 0x13590078, 0x13810054, 0x13850078, 0x13860054, 0x13940078, 0x13950054, + 0x13A60078, 0x13A80054, 0x13AA0078, 0x13AB0054, 0x13B00078, 0x13B10054, 0x13B40009, 0x13BB0106, + 0x13BC0107, 0x13BD0108, 0x13BE0109, 0x13BF010A, 0x13C00106, 0x13C10107, 0x13C20108, 0x13C30109, + 0x13C4010A, 0x13C50106, 0x13C60107, 0x13C70108, 0x13C80109, 0x13C9010A, 0x13CA0054, 0x13CB0078, + 0x13CC0054, 0x13D80078, 0x13D90054, 0x13E000E8, 0x13E10019, 0x13E200FE, 0x13E3000A, 0x13E40078, + 0x13E80019, 0x13EA00E8, 0x13EB00FE, 0x13EC0019, 0x13EE00E8, 0x13EF00FE, 0x13F00019, 0x13F100FD, + 0x13F30009, 0x13F60078, 0x13F80019, 0x14000094, 0x14800019, 0x14C2000A, 0x14C70120, 0x14C80121, + 0x14C9000A, 0x14CD0019, 0x14CE00E8, 0x14D80019, 0x14DC0122, 0x14DD0019, 0x14E000FD, 0x14E100E8, + 0x14E200FD, 0x14E30019, 0x14E700E8, 0x14E800FE, 0x14EA00FD, 0x14EB0019, 0x14EC0009, 0x14EE00E8, + 0x14EF0019, 0x14F200E8, 0x14F30019, 0x14F400E8, 0x14F50019, 0x14FA00E8, 0x14FC00FD, 0x14FD0019, + 0x14FE0009, 0x14FF0019, 0x150500E8, 0x150610E8, 0x150700E8, 0x15110019, 0x151200E8, 0x15140019, + 0x151600FE, 0x15180019, 0x151A00FD, 0x151B0019, 0x151E00FD, 0x151F00E8, 0x15200019, 0x152C00E8, + 0x152D0019, 0x153200FD, 0x15330019, 0x153500E8, 0x15370019, 0x153800E8, 0x15390019, 0x153A10E8, + 0x153B1019, 0x153C0019, 0x153D00FE, 0x153E00E8, 0x153F00FE, 0x154300E8, 0x154600FE, 0x154700E8, + 0x154900FE, 0x154F00E8, 0x155100FE, 0x15520019, 0x155300FD, 0x15570019, 0x155800FE, 0x155900E8, + 0x155A00FE, 0x155B00E8, 0x155E00FE, 0x156400E8, 0x156700FE, 0x156C0019, 0x156E08E8, 0x156F0123, + 0x15700019, 0x157100E8, 0x15720124, 0x157300E8, 0x15740019, 0x157600FD, 0x157700E8, 0x15780019, + 0x157C00FE, 0x157E0019, 0x15800054, 0x158A0078, 0x16000096, 0x16180098, 0x16300078, 0x16400063, + 0x16720055, 0x16730054, 0x16760078, 0x167D0006, 0x16800125, 0x16930078, 0x16980071, 0x16B30078, + 0x16C00071, 0x16CC0078, 0x16D00071, 0x16F00078, 0x17000006, 0x17010126, 0x17030006, 0x170500E0, + 0x17060126, 0x17070006, 0x170C0078, 0x170E0126, 0x170F0078, 0x17400054, 0x174D0078, 0x174E0054, + 0x177A0078, 0x17801054, 0x17EB0078, 0x17F80054, 0x17FE0078, 0x18008805, 0x18010006, 0x18020054, + 0x18030071, 0x18040009, 0x18090054, 0x180A0009, 0x180E0099, 0x180F00BF, 0x18100054, 0x18110127, + 0x18120128, 0x18130129, 0x1814012A, 0x18150083, 0x18180099, 0x18190081, 0x181B1054, 0x181C112B, + 0x181D112C, 0x181E0071, 0x181F0054, 0x18200078, 0x18210071, 0x18260871, 0x18320071, 0x18380871, + 0x18390071, 0x183A0871, 0x183C0071, 0x183D0871, 0x183F0071, 0x184A0871, 0x184B0071, 0x184C0078, + 0x184D0083, 0x184E1037, 0x184F0881, 0x18500099, 0x18510071, 0x18560871, 0x18620071, 0x18680871, + 0x18690071, 0x186A0871, 0x186C0071, 0x186D0871, 0x186F0071, 0x187A0871, 0x187B0071, 0x187C0871, + 0x187E0081, 0x187F0881, 0x18800078, 0x18830071, 0x18970078, 0x18991071, 0x18C80094, 0x18C978AD, + 0x18CA78AE, 0x18CB7894, 0x18D00071, 0x18DC0078, 0x18E00054, 0x18E80078, 0x18F80071, 0x19001094, + 0x190F1054, 0x191010AD, 0x191110AE, 0x1912112D, 0x1913112E, 0x1914112F, 0x19151094, 0x19220078, + 0x19286854, 0x19291930, 0x192A1931, 0x192B1932, 0x192C1933, 0x192D1934, 0x192E1935, 0x192F1936, + 0x19301894, 0x193E1854, 0x194018AD, 0x194118AE, 0x1942192D, 0x1943192E, 0x1944192F, 0x19451894, + 0x19591937, 0x195A1938, 0x195B1939, 0x195C193A, 0x195D193B, 0x195E193C, 0x195F193D, 0x19601094, + 0x19666854, 0x19681894, 0x19806894, 0x19AC1094, 0x19B96894, 0x19BC6854, 0x19BE6894, 0x19EF6854, + 0x19F01094, 0x1A000071, 0x26DB0078, 0x26E00054, 0x27000071, 0x4FDE0078, 0x50000071, 0x52470078, + 0x52480054, 0x52640078, 0x53800037, 0x538C0078, 0x54000071, 0x540100C8, 0x54020071, 0x54030083, + 0x54040071, 0x541200A7, 0x54130083, 0x54140054, 0x54160078, 0x56000071, 0x6BD20078, 0x6C00013E, + 0x7000013F, 0x7C800871, 0x7D070071, 0x7D080871, 0x7D0A0071, 0x7D0B0871, 0x7D120071, 0x7D130871, + 0x7D140071, 0x7D150871, 0x7D170078, 0x7D180871, 0x7D360078, 0x7D380871, 0x7D6D0078, 0x7D801055, + 0x7D840078, 0x7D8A1055, 0x7D8C0078, 0x7D8F0083, 0x7D90289B, 0x7D95089B, 0x7DA10078, 0x7DA2089B, + 0x7DA8409E, 0x7DAA389E, 0x7DAB409E, 0x7DAC389E, 0x7DAD409E, 0x7DAE389E, 0x7DAF409E, 0x7DB0389E, + 0x7DB1409E, 0x7DB2389E, 0x7DB3409E, 0x7DB4389E, 0x7DB5409E, 0x7DB6389E, 0x7DB7409E, 0x7DB8389E, + 0x7DB9409E, 0x7DBA389E, 0x7DBB409E, 0x7DBC389E, 0x7DBD409E, 0x7DBE389E, 0x7DBF409E, 0x7DC0389E, + 0x7DC1409E, 0x7DC8389E, 0x7DC9409E, 0x7DCA389E, 0x7DCB409E, 0x7DCC389E, 0x7DCD409E, 0x7DCE389E, + 0x7DCF409E, 0x7DD1389E, 0x7DD2409E, 0x7DD4389E, 0x7DD5409E, 0x7DD6389E, 0x7DD7409E, 0x7DD90078, + 0x7DEA209E, 0x7DEB489E, 0x7DEC209E, 0x7DEF409E, 0x7DF3389E, 0x7DF5409E, 0x7DFC389E, 0x7DFD209E, + 0x7DFE409E, 0x7DFF389E, 0x7E00409E, 0x7E32209E, 0x7E4C389E, 0x7E70489E, 0x7E7B409E, 0x7E89209E, + 0x7E97389E, 0x7E9A489E, 0x7E9E209E, 0x7E9F00B4, 0x7EA00078, 0x7EA8389E, 0x7EAC209E, 0x7EAE389E, + 0x7EAF209E, 0x7EB0389E, 0x7EB1209E, 0x7EB4389E, 0x7EB5209E, 0x7EB8389E, 0x7EBA209E, 0x7EC3389E, + 0x7EC80078, 0x7EC9389E, 0x7ECB209E, 0x7ECC389E, 0x7ECD209E, 0x7EDA389E, 0x7EDB209E, 0x7EDC389E, + 0x7EDE209E, 0x7EE2389E, 0x7EE3209E, 0x7EE40078, 0x7EF8409E, 0x7EFE4140, 0x7EFF0078, 0x7F000083, + 0x7F088006, 0x7F0C80BF, 0x7F0D0078, 0x7F100083, 0x7F120078, 0x7F188006, 0x7F198099, 0x7F1A8038, + 0x7F1B80BF, 0x7F230006, 0x7F2480BF, 0x7F251006, 0x7F271038, 0x7F28600C, 0x7F2A6006, 0x7F2C6099, + 0x7F2D60BF, 0x7F306006, 0x7F31600B, 0x7F326019, 0x7F346006, 0x7F356007, 0x7F360078, 0x7F38409E, + 0x7F41209E, 0x7F46489E, 0x7F47209E, 0x7F49489E, 0x7F4A209E, 0x7F4C489E, 0x7F4D209E, 0x7F4E489E, + 0x7F4F209E, 0x7F50489E, 0x7F51209E, 0x7F52489E, 0x7F53209E, 0x7F54489E, 0x7F55209E, 0x7F5A489E, + 0x7F5B209E, 0x7F5C489E, 0x7F5D209E, 0x7F5E489E, 0x7F5F209E, 0x7F60489E, 0x7F61209E, 0x7F62489E, + 0x7F63209E, 0x7F64489E, 0x7F65209E, 0x7F66489E, 0x7F67209E, 0x7F68489E, 0x7F69209E, 0x7F6A489E, + 0x7F6B209E, 0x7F6C489E, 0x7F6D209E, 0x7F6E489E, 0x7F6F209E, 0x7F70489E, 0x7F71209E, 0x7F72489E, + 0x7F73209E, 0x7F74489E, 0x7F75209E, 0x7F76489E, 0x7F77209E, 0x7F7A489E, 0x7F7B209E, 0x7F7F0078, + 0x7F818806, 0x7F828808, 0x7F838806, 0x7F848809, 0x7F858806, 0x7F86880C, 0x7F88880E, 0x7F898810, + 0x7F8A8812, 0x7F8B8814, 0x7F8C8816, 0x7F8D880C, 0x7F8E8818, 0x7F8F881A, 0x7F908806, 0x7F918860, + 0x7F9E8806, 0x7F9F8837, 0x7FA18861, 0x7FAE8819, 0x7FB0880A, 0x7FB15009, 0x7FB25006, 0x7FB35071, + 0x7FB85081, 0x7FB95071, 0x7FCF5081, 0x7FD05071, 0x7FE00078, 0x7FE15071, 0x7FE40078, 0x7FE55071, + 0x7FE80078, 0x7FE95071, 0x7FEC0078, 0x7FED5071, 0x7FEF0078, 0x7FF08808, 0x7FF18819, 0x7FF28854, + 0x7FF38808, 0x7FF45054, 0x7FF55019, 0x7FF75054, 0x7FF80078, 0x7FFD0141, 0x7FFE0054, 0x7FFF0078, + 0x80000071, 0x80060078, 0x80070071, 0x801F0078, 0x80200071, 0x80270078, 0x80280071, 0x802F0078, + 0x80400071, 0x807E0078, 0x80800097, 0x80810094, 0x80820078, 0x808400B6, 0x808500B7, 0x808600B8, + 0x808700B9, 0x808800B0, 0x808900BA, 0x808A00BB, 0x808B00BC, 0x808C00BD, 0x808D0142, 0x808E0143, + 0x808F0144, 0x80900145, 0x809100B1, 0x80920146, 0x80930147, 0x80940148, 0x80950149, 0x8096014A, + 0x8097014B, 0x8098014C, 0x8099014D, 0x809A0078, 0x809C0094, 0x80A0014E, 0x80A1014F, 0x80A20150, + 0x80A30151, 0x80A40152, 0x80A50150, 0x80A60153, 0x80A70151, 0x80A80154, 0x80A90155, 0x80AA0156, + 0x80AB0157, 0x80AC014F, 0x80AE0158, 0x80B00154, 0x80B30150, 0x80B50155, 0x80B60153, 0x80B90151, + 0x80BA0150, 0x80BB005F, 0x80BD0054, 0x80C500C3, 0x80C60078, 0x81800071, 0x819000AD, 0x819100B0, + 0x81920078, 0x81980071, 0x81A50159, 0x81A60078, 0x81C00071, 0x81CF0078, 0x81D00071, 0x81E20078, + 0x81E40071, 0x81E80094, 0x81E90158, 0x81EA015A, 0x81EB0078, 0x8200015B, 0x8214015C, 0x82280071, + 0x824F0078, 0x825000A8, 0x825100A9, 0x825200AA, 0x825300AB, 0x825400AC, 0x82550078, 0x8400009B, + 0x84030078, 0x8404009B, 0x841B0078, 0x841C009B, 0x841D0078, 0x841E009B, 0x841F0078, 0x8500009B, + 0x85010083, 0x85020078, 0x85030083, 0x85040078, 0x85060083, 0x8508009B, 0x850A0078, 0x850B009B, + 0x850C0078, 0x850D009B, 0x851A0078, 0x851C0083, 0x851E0078, 0x8520015D, 0x8521015E, 0x8522015F, + 0x85230160, 0x85240078, 0x8528009A, 0x852D0078, 0xE8000094, 0xE87B0078, 0xE8800094, 0xE8940078, + 0xE8950094, 0xE8AF0894, 0xE8B300A7, 0xE8B40083, 0xE8B50094, 0xE8B700A7, 0xE8BA0057, 0xE8BE0083, + 0xE8C20094, 0xE8C30083, 0xE8C60094, 0xE8D50083, 0xE8D70094, 0xE8DE0894, 0xE8E10094, 0xE8EF0078, + 0xE9000054, 0xE9210083, 0xE9230078, 0xE9800054, 0xE9AC0078, 0xEA002877, 0xEA0D2855, 0xEA1A2877, + 0xEA272855, 0xEA342877, 0xEA412855, 0xEA4E2877, 0xEA500078, 0xEA512877, 0xEA520078, 0xEA532877, + 0xEA540078, 0xEA552877, 0xEA5B2855, 0xEA5D0078, 0xEA5F2855, 0xEA620078, 0xEA632855, 0xEA682877, + 0xEA752855, 0xEA822877, 0xEA830078, 0xEA842877, 0xEA860078, 0xEA872877, 0xEA8F2855, 0xEA9C2877, + 0xEA9D0078, 0xEA9E2877, 0xEAA40078, 0xEAA52877, 0xEAA92855, 0xEAB62877, 0xEAC32855, 0xEAD02877, + 0xEADD2855, 0xEAEA2877, 0xEAF72855, 0xEB042877, 0xEB112855, 0xEB1E2877, 0xEB2B2855, 0xEB382877, + 0xEB452855, 0xEB530078, 0xEB542877, 0xEB612855, 0xEB712877, 0xEB7E2855, 0xEB8E2877, 0xEB9B2855, + 0xEBAB2877, 0xEBB82855, 0xEBC82877, 0xEBD52855, 0xEBE50078, 0xEBE7280E, 0xEBE82810, 0xEBE92812, + 0xEBEA2814, 0xEBEB2816, 0xEBEC280E, 0xEBED2810, 0xEBEE2812, 0xEBEF2814, 0xEBF02816, 0xEBF1280E, + 0xEBF22810, 0xEBF32812, 0xEBF42814, 0xEBF52816, 0xEBF6280E, 0xEBF72810, 0xEBF82812, 0xEBF92814, + 0xEBFA2816, 0xEBFB280E, 0xEBFC2810, 0xEBFD2812, 0xEBFE2814, 0xEBFF2816, 0xEC000078 + }; + + static const uint32_t a1[] = { + 0x00000071, 0x536C0078, 0x7C000871, 0x7D0F0078 + }; + + static const uint32_t a7[] = { + 0x00100057, 0x00400078, 0x00800083, 0x00F80078, 0x8000013F, 0xFFFF0078 + }; + + static const uint32_t a8[] = { + 0x0000013F, 0x7FFF0078 + }; + + static const uint32_t a16[] = { + 0x00800865, 0x00880065, 0x00890865, 0x00930065, 0x00940865, 0x00980161, 0x00991065, 0x009A0865, + 0x009C0863, 0x009F1063, 0x00A00063, 0x00A10863, 0x00A41055, 0x00A50065, 0x00A60865, 0x00A90065, + 0x00AA0865, 0x00B30065, 0x00B40865, 0x00BC0863, 0x00BF1162, 0x00C00163, 0x00C10065, 0x00C30063, + 0x00C40068, 0x00C50063, 0x00C60055, 0x00C70164, 0x00C80063, 0x00C90068, 0x00CA0165, 0x00CB0166, + 0x00CC0065, 0x00CD0055, 0x00CE0167, 0x00CF0168, 0x00D00865, 0x00D10065, 0x00D30063, 0x00D4006F, + 0x00D50055, 0x00D60065, 0x00D70863, 0x00D80070, 0x00D90063, 0x00DB0169, 0x00DC0065, 0x00DD0071, + 0x00DE0065, 0x00DF016A, 0x00E00071, 0x00E21074, 0x00E31072, 0x00E41073, 0x00E51074, 0x00E60863, + 0x00EE016B, 0x00EF0865, 0x00F20065, 0x00F30865, 0x00F81072, 0x00F91073, 0x00FA0865, 0x00FB016C, + 0x00FC0865, 0x010E0065, 0x010F0865, 0x01100055, 0x01110065, 0x01130865, 0x011A0055, 0x011D0063, + 0x011E016D, 0x011F0055, 0x0120016E, 0x01210078, 0x01280055, 0x0129016F, 0x012A0055, 0x012B007A, + 0x012C0170, 0x012D0171, 0x012E0055, 0x01310172, 0x01320055, 0x01340173, 0x01350055, 0x01370173, + 0x01380055, 0x013A0174, 0x013B0055, 0x0141007D, 0x01420055, 0x0145007E, 0x01460055, 0x01587881, + 0x015C0082, 0x015D0081, 0x01610037, 0x01630082, 0x01680081, 0x01690037, 0x016C1037, 0x016F0037, + 0x01707881, 0x01720037, 0x01800083, 0x01A00883, 0x01A20175, 0x01A30083, 0x01B80078, 0x01BA0037, + 0x01BB0078, 0x01C20837, 0x01C30806, 0x01C40885, 0x01C50078, 0x01C70887, 0x01C80060, 0x01D50860, + 0x01D60889, 0x01D80061, 0x01E50861, 0x01E6088C, 0x01E70078, 0x01E81176, 0x01E90877, 0x01EA1177, + 0x01EB0055, 0x01EC0065, 0x01F81093, 0x01F90055, 0x01FA1178, 0x01FB0063, 0x01FC10D8, 0x01FD0065, + 0x01FE0077, 0x02000892, 0x02020092, 0x02030892, 0x02040092, 0x02060892, 0x02070092, 0x02080060, + 0x020C0860, 0x020D0060, 0x02180061, 0x021C0861, 0x021D0061, 0x02280893, 0x022A0093, 0x022B0893, + 0x022C0093, 0x022E0893, 0x022F0093, 0x02300065, 0x023B0865, 0x023C0065, 0x02410083, 0x02430078, + 0x02440095, 0x02450065, 0x02600863, 0x02610063, 0x02670078, 0x02680865, 0x026A0065, 0x026B0865, + 0x026C0065, 0x026D0865, 0x02700065, 0x02710865, 0x02740065, 0x02750865, 0x027B0065, 0x027C0865, + 0x027D0078, 0x02800065, 0x02880078, 0x02980096, 0x02AB0078, 0x02AC0081, 0x02AD0097, 0x02B00098, + 0x02C31055, 0x02C40097, 0x02C50078, 0x02C80083, 0x02E1009A, 0x02E20083, 0x02E40078, 0x02E8009B, + 0x02F50078, 0x02F8009B, 0x02F9009A, 0x02FA0078, 0x0300009C, 0x03020078, 0x03050140, 0x0306009D, + 0x03070054, 0x03080083, 0x030B0078, 0x030D009D, 0x030E0078, 0x030F009D, 0x0310009E, 0x0311089E, + 0x0313009E, 0x031D0078, 0x0320009E, 0x03250083, 0x032F0078, 0x03300179, 0x0331017A, 0x0332017B, + 0x0333017C, 0x0334017D, 0x033500A5, 0x0336009D, 0x0337009E, 0x033A109E, 0x033C009E, 0x0369089E, + 0x036A009E, 0x036B0083, 0x036E009C, 0x036F0083, 0x0372009F, 0x03730083, 0x03740054, 0x03750083, + 0x0377009E, 0x0378000F, 0x03790011, 0x037A0013, 0x037B0015, 0x037C0017, 0x037D009E, 0x037E00A6, + 0x037F009E, 0x0380009D, 0x03870057, 0x03880083, 0x0389009E, 0x03980083, 0x03A50078, 0x03A6009E, + 0x03B70078, 0x03C0009E, 0x03D30083, 0x03D8009E, 0x03D90078, 0x04800083, 0x048100A7, 0x04820071, + 0x04940871, 0x04950071, 0x04980871, 0x04990071, 0x049D0078, 0x049E0071, 0x049F00A7, 0x04A00083, + 0x04A400A7, 0x04A60083, 0x04A70078, 0x04A80083, 0x04AA0078, 0x04AC0871, 0x04B00071, 0x04B10083, + 0x04B20097, 0x04B3017E, 0x04B4017F, 0x04B50180, 0x04B60181, 0x04B70182, 0x04B80078, 0x04BE0071, + 0x04BF0078, 0x04C00083, 0x04C100A7, 0x04C20071, 0x04C60078, 0x04C70071, 0x04C80078, 0x04C90071, + 0x04D40078, 0x04D50071, 0x04D80078, 0x04DB0071, 0x04DD0078, 0x04DE0071, 0x04DF00A7, 0x04E00083, + 0x04E20078, 0x04E300A7, 0x04E40078, 0x04E508A7, 0x04E60083, 0x04E70078, 0x04EB00A7, 0x04EC0078, + 0x04EE0871, 0x04F00071, 0x04F10083, 0x04F20078, 0x04F3017E, 0x04F4017F, 0x04F50180, 0x04F60181, + 0x04F70182, 0x04F80071, 0x04F90008, 0x04FA00B6, 0x04FB00B7, 0x04FC0183, 0x04FD0078, 0x05000083, + 0x050100A7, 0x05020071, 0x05050078, 0x05070071, 0x05080078, 0x05090071, 0x05140078, 0x05150071, + 0x05180078, 0x05190871, 0x051A0071, 0x051B0078, 0x051C0071, 0x051D0078, 0x051F00A7, 0x05200083, + 0x05210078, 0x05230083, 0x05240078, 0x05250083, 0x05270078, 0x052C0871, 0x052E0078, 0x0533017E, + 0x0534017F, 0x05350180, 0x05360181, 0x05370182, 0x05380083, 0x05390071, 0x053A0078, 0x05400083, + 0x054100A7, 0x05420071, 0x05540078, 0x05550071, 0x05580078, 0x05590071, 0x055D0078, 0x055E0071, + 0x055F00A7, 0x05600083, 0x056400A7, 0x05660083, 0x05670078, 0x05700071, 0x05710083, 0x05720078, + 0x0573017E, 0x0574017F, 0x05750180, 0x05760181, 0x05770182, 0x05780008, 0x05790078, 0x05800083, + 0x058100A7, 0x05820071, 0x05860078, 0x05870071, 0x05880078, 0x05890071, 0x05940078, 0x05950071, + 0x05980078, 0x05990071, 0x059D0078, 0x059E0071, 0x059F0083, 0x05A20078, 0x05A300A7, 0x05A40078, + 0x05A508A7, 0x05A60083, 0x05A70078, 0x05AB00A7, 0x05AC0078, 0x05AE0871, 0x05AF0071, 0x05B10078, + 0x05B3017E, 0x05B4017F, 0x05B50180, 0x05B60181, 0x05B70182, 0x05B80071, 0x05B90078, 0x05C10071, + 0x05C50078, 0x05C70071, 0x05C80078, 0x05C90071, 0x05CB0078, 0x05CC0071, 0x05CD0078, 0x05CF0071, + 0x05D00078, 0x05D10071, 0x05D20078, 0x05D40071, 0x05D50078, 0x05D70071, 0x05DD0078, 0x05DF00A7, + 0x05E10078, 0x05E300A7, 0x05E40078, 0x05E508A7, 0x05E60083, 0x05E70078, 0x05EB00A7, 0x05EC0078, + 0x05F3017E, 0x05F4017F, 0x05F50180, 0x05F60181, 0x05F70182, 0x05F80184, 0x05F90054, 0x05FC0008, + 0x05FD0078, 0x060000A7, 0x06020071, 0x06060078, 0x06070071, 0x06080078, 0x06090071, 0x06140078, + 0x06150071, 0x061D0078, 0x061F0083, 0x062000A7, 0x06220078, 0x06230083, 0x06240078, 0x06250083, + 0x06270078, 0x062A0083, 0x062B0078, 0x06300071, 0x06310078, 0x0633017E, 0x0634017F, 0x06350180, + 0x06360181, 0x06370182, 0x06380078, 0x064100A7, 0x06420071, 0x06460078, 0x06470071, 0x06480078, + 0x06490071, 0x06540078, 0x06550071, 0x065D0078, 0x065E0071, 0x065F00B2, 0x066000A7, 0x06620078, + 0x066308A7, 0x06640078, 0x066508A7, 0x06660083, 0x06670078, 0x066A00A7, 0x066B0078, 0x06700071, + 0x06710078, 0x0673017E, 0x0674017F, 0x06750180, 0x06760181, 0x06770182, 0x06780078, 0x068100A7, + 0x06820071, 0x06860078, 0x06870071, 0x06880078, 0x06890071, 0x06940078, 0x06950071, 0x069D0078, + 0x069F00A7, 0x06A00083, 0x06A20078, 0x06A300A7, 0x06A40078, 0x06A508A7, 0x06A60083, 0x06A70078, + 0x06AB00A7, 0x06AC0078, 0x06B00071, 0x06B10078, 0x06B3017E, 0x06B4017F, 0x06B50180, 0x06B60181, + 0x06B70182, 0x06B80078, 0x06C100A7, 0x06C20071, 0x06CB0078, 0x06CD0071, 0x06DF0078, 0x06E00071, + 0x06E30078, 0x06E700A7, 0x06E90083, 0x06EA0078, 0x06EC00A7, 0x06EE08A7, 0x06EF00A7, 0x06F00078, + 0x06F900A7, 0x06FA0078, 0x07000071, 0x07180083, 0x07191071, 0x071A0083, 0x071D0078, 0x071F0008, + 0x07200071, 0x07230083, 0x07270097, 0x0728017E, 0x0729017F, 0x072A0180, 0x072B0181, 0x072C0182, + 0x072D0097, 0x072E0078, 0x07400071, 0x07410078, 0x07430071, 0x07440078, 0x07460071, 0x07470078, + 0x074A0071, 0x07540078, 0x07550071, 0x07580083, 0x07591071, 0x075A0083, 0x075E0071, 0x075F0078, + 0x07600071, 0x07620078, 0x07640083, 0x07670078, 0x0768017E, 0x0769017F, 0x076A0180, 0x076B0181, + 0x076C0182, 0x076D0078, 0x076E1071, 0x076F0078, 0x07800094, 0x07820097, 0x07890094, 0x078C0083, + 0x078D0094, 0x0790017E, 0x0791017F, 0x07920180, 0x07930181, 0x07940182, 0x079500B3, 0x079A0083, + 0x079D00BF, 0x079F00A7, 0x07A00071, 0x07A10871, 0x07A20071, 0x07A60871, 0x07A70071, 0x07AB0871, + 0x07AC0071, 0x07B40871, 0x07B50078, 0x07B80083, 0x07B90883, 0x07BB1083, 0x07BD0083, 0x07BF00A7, + 0x07C00883, 0x07C10083, 0x07C20097, 0x07C30083, 0x07C40071, 0x07C60078, 0x07C80083, 0x07C90883, + 0x07CA0083, 0x07CE0883, 0x07CF0083, 0x07D30883, 0x07D40083, 0x07DC0883, 0x07DD0083, 0x07DE0078, + 0x07DF0094, 0x07E60078, 0x07E70094, 0x07E80097, 0x07E90078, 0x08000071, 0x08150078, 0x08160083, + 0x081800A7, 0x08190078, 0x081B0083, 0x081D0078, 0x0820017E, 0x0821017F, 0x08220180, 0x08230181, + 0x08240182, 0x08250097, 0x08280071, 0x082B00A7, 0x082C0083, 0x082D0078, 0x085000B5, 0x08630078, + 0x08680071, 0x087D0097, 0x087E0078, 0x08800071, 0x08AD0078, 0x08AF0071, 0x08D10078, 0x08D40071, + 0x08FD0078, 0x09000071, 0x09240078, 0x09250071, 0x09270078, 0x09280071, 0x092B0078, 0x092D0071, + 0x092F0078, 0x09300071, 0x09440078, 0x09450071, 0x09470078, 0x09480071, 0x09580078, 0x09590071, + 0x095B0078, 0x095C0071, 0x095F0078, 0x09610071, 0x09630078, 0x09640071, 0x096B0078, 0x096C0071, + 0x09880078, 0x09890071, 0x098B0078, 0x098C0071, 0x09AD0078, 0x09AF0083, 0x09B00097, 0x09B400AD, + 0x09B500AE, 0x09B6012D, 0x09B7012E, 0x09B8012F, 0x09B90185, 0x09BA0186, 0x09BB0187, 0x09BC0188, + 0x09BD0184, 0x09BE0078, 0x09C00071, 0x09C80054, 0x09CD0078, 0x09D00071, 0x09FA0078, 0x0A000071, + 0x0B360097, 0x0B370071, 0x0B3B0078, 0x0B400071, 0x0B4D00B4, 0x0B4E0078, 0x0B500071, 0x0B750097, + 0x0B770189, 0x0B780078, 0x0B800071, 0x0B860078, 0x0B870071, 0x0B890083, 0x0B8A0078, 0x0B900071, + 0x0B990083, 0x0B9A0097, 0x0B9B0078, 0x0BA00071, 0x0BA90083, 0x0BAA0078, 0x0BB00071, 0x0BB60078, + 0x0BB70071, 0x0BB80078, 0x0BB90083, 0x0BBA0078, 0x0BC00071, 0x0BDA00C2, 0x0BDB0083, 0x0BDF00A7, + 0x0BE40083, 0x0BEA0097, 0x0BEB0081, 0x0BEC0097, 0x0BED0008, 0x0BEE0083, 0x0BEF0078, 0x0BF0017E, + 0x0BF1017F, 0x0BF20180, 0x0BF30181, 0x0BF40182, 0x0BF50078, 0x0BF80106, 0x0BF90107, 0x0BFA0108, + 0x0BFB0109, 0x0BFC010A, 0x0BFD0078, 0x0C000006, 0x0C050083, 0x0C070078, 0x0C08017E, 0x0C09017F, + 0x0C0A0180, 0x0C0B0181, 0x0C0C0182, 0x0C0D0078, 0x0C100071, 0x0C210081, 0x0C220071, 0x0C3C0078, + 0x0C400071, 0x0C540083, 0x0C550078, 0x0C800071, 0x0C8E0078, 0x0C900083, 0x0C9100A7, 0x0C930083, + 0x0C9400C8, 0x0C960078, 0x0C9800A7, 0x0C9C0083, 0x0C9E0078, 0x0CA20006, 0x0CA3017E, 0x0CA4017F, + 0x0CA50180, 0x0CA60181, 0x0CA70182, 0x0CA80071, 0x0CB70078, 0x0CB80071, 0x0CBA0078, 0x0CC00071, + 0x0CD50078, 0x0CD800A7, 0x0CE00071, 0x0CE400A7, 0x0CE50078, 0x0CE8017E, 0x0CE9017F, 0x0CEA0180, + 0x0CEB0181, 0x0CEC0182, 0x0CED0078, 0x0CEF0006, 0x0CF00054, 0x0D000071, 0x0D0B0083, 0x0D0C00A7, + 0x0D0E0078, 0x0D0F0097, 0x0D100078, 0x0E800055, 0x0E967881, 0x0E970081, 0x0E987881, 0x0E9D0081, + 0x0E9E7881, 0x0EB17055, 0x0EB50055, 0x0ECD7881, 0x0EE00083, 0x0EE20078, 0x0F000865, 0x0F4B0855, + 0x0F4D098A, 0x0F4E0078, 0x0F500865, 0x0F7D0078, 0x0F8008C9, 0x0F8408CA, 0x0F8808C9, 0x0F8B0078, + 0x0F8C08CA, 0x0F8F0078, 0x0F9008C9, 0x0F9408CA, 0x0F9808C9, 0x0F9C08CA, 0x0FA008C9, 0x0FA30078, + 0x0FA408CA, 0x0FA70078, 0x0FA808C9, 0x0FAC08CA, 0x0FB008C9, 0x0FB408CA, 0x0FB808CB, 0x0FB908CC, + 0x0FBB08CD, 0x0FBC08CE, 0x0FBD08CF, 0x0FBE08D0, 0x0FBF0078, 0x0FC008C9, 0x0FC408D1, 0x0FC808C9, + 0x0FCC08D1, 0x0FD008C9, 0x0FD408D1, 0x0FD808C9, 0x0FD9098B, 0x0FDA0078, 0x0FDB0855, 0x0FDC08CA, + 0x0FDD08D2, 0x0FDE1037, 0x0FE00837, 0x0FE1098B, 0x0FE20078, 0x0FE30855, 0x0FE408D5, 0x0FE60837, + 0x0FE808C9, 0x0FE90855, 0x0FEA0078, 0x0FEB0855, 0x0FEC08CA, 0x0FED08D6, 0x0FEE0837, 0x0FF008C9, + 0x0FF10855, 0x0FF20890, 0x0FF30855, 0x0FF408CA, 0x0FF508D7, 0x0FF60837, 0x0FF80078, 0x0FF9098B, + 0x0FFA0078, 0x0FFB0855, 0x0FFC08D9, 0x0FFD08DA, 0x0FFE0837, 0x0FFF0078, 0x10000805, 0x10011005, + 0x10035805, 0x10041005, 0x10050057, 0x1007018C, 0x10085899, 0x10090099, 0x100B1006, 0x100C018D, + 0x100D00DB, 0x100E018D, 0x100F00DB, 0x10100006, 0x10121006, 0x10130006, 0x1014018E, 0x1015018F, + 0x10160190, 0x10175853, 0x10180007, 0x10191007, 0x101A0006, 0x101B1006, 0x101C0126, 0x101D0006, + 0x101F0038, 0x10200006, 0x10220009, 0x10231006, 0x10250006, 0x102B1006, 0x102C0006, 0x102F1005, + 0x10300057, 0x10320078, 0x10350057, 0x10387855, 0x10390078, 0x103A7910, 0x103B7911, 0x103C7912, + 0x103D780B, 0x103E7809, 0x103F7855, 0x1040705D, 0x1041705B, 0x10427110, 0x10437111, 0x10447112, + 0x1045700B, 0x10467009, 0x10470078, 0x10487081, 0x104A0078, 0x10500008, 0x105B0078, 0x10680083, + 0x106E0095, 0x10700083, 0x10710095, 0x10720083, 0x10760078, 0x10801054, 0x10831077, 0x10841054, + 0x10852877, 0x10872855, 0x10882877, 0x10892855, 0x108A2877, 0x108B0054, 0x108C2877, 0x108F0054, + 0x10901054, 0x10910054, 0x10950991, 0x10962877, 0x10972855, 0x10982877, 0x109A1071, 0x109C2855, + 0x109D1054, 0x109E2855, 0x109F2877, 0x10A00019, 0x10A22877, 0x10A32855, 0x10A50019, 0x10A60078, + 0x10A9305F, 0x10AF3106, 0x10B01192, 0x10B11193, 0x10B21194, 0x10B31195, 0x10B41196, 0x10B51197, + 0x10B61198, 0x10B71199, 0x10B8119A, 0x10B9119B, 0x10BA119C, 0x10BB119D, 0x10BC119E, 0x10BD119F, + 0x10BE11A0, 0x10BF11A1, 0x10C001A2, 0x10C101A3, 0x10C20078, 0x10C80019, 0x10CA0054, 0x10CD0819, + 0x10CE0054, 0x10D10019, 0x10D20054, 0x10E60854, 0x10E70819, 0x10E80054, 0x10FA0019, 0x110000E8, + 0x11020019, 0x110408FB, 0x110500FC, 0x11070019, 0x110800E8, 0x11090059, 0x110A01A4, 0x110B0019, + 0x110D00E8, 0x11110019, 0x111500E8, 0x111610E8, 0x111800E8, 0x111A0019, 0x111C00E8, 0x111E00FE, + 0x111F00E8, 0x112008E8, 0x112101A5, 0x112200E8, 0x112308E8, 0x112500E8, 0x11260019, 0x112900FE, + 0x112B0019, 0x112F00E8, 0x11300019, 0x113200FE, 0x11360819, 0x113708FE, 0x113900FE, 0x113A08FE, + 0x113B00FE, 0x113C08FE, 0x113D00FE, 0x114008FE, 0x114100FE, 0x114208FE, 0x114300FE, 0x114408FE, + 0x114500FE, 0x11460019, 0x114700FD, 0x11490019, 0x115100FE, 0x11520019, 0x115300E8, 0x115401A6, + 0x115608E8, 0x115800FE, 0x115C0019, 0x115F00E8, 0x11600019, 0x116400FD, 0x116601A7, 0x11670019, + 0x116800FE, 0x11690019, 0x116B00FE, 0x117008FE, 0x117200FE, 0x117508FE, 0x11770019, 0x117800FE, + 0x11790102, 0x117A00E8, 0x117B0103, 0x117C00E8, 0x117D0104, 0x117E0105, 0x117F00E8, 0x11800054, + 0x118400FE, 0x11860054, 0x119000E8, 0x11910054, 0x11940809, 0x11950054, 0x119B0094, 0x11BD0054, + 0x11CA0094, 0x11CB0054, 0x11CD0019, 0x11DA00BF, 0x11DB0054, 0x11EE0078, 0x12000054, 0x12130078, + 0x12200054, 0x12250078, 0x123018C4, 0x123118C5, 0x123218C6, 0x123318C7, 0x1234191F, 0x1235191A, + 0x1236191B, 0x1237191C, 0x1238191D, 0x1239191E, 0x123A10C4, 0x123B10C5, 0x123C10C6, 0x123D10C7, + 0x123E111F, 0x123F111A, 0x1240111B, 0x1241111C, 0x1242111D, 0x1243111E, 0x1244105A, 0x124510E3, + 0x124610E4, 0x124710E5, 0x124811A8, 0x124911A9, 0x124A11AA, 0x124B11AB, 0x124C11AC, 0x124D11AD, + 0x124E1094, 0x125B1918, 0x12681919, 0x1275010B, 0x1276010C, 0x1277010D, 0x1278010E, 0x1279010F, + 0x127A0106, 0x127B0107, 0x127C0108, 0x127D0109, 0x127E010A, 0x127F00C3, 0x12800054, 0x12DB0019, + 0x12DC0054, 0x12E00019, 0x12E10054, 0x12FC0019, 0x13000054, 0x13370019, 0x13380054, 0x134E0078, + 0x13500054, 0x13590078, 0x13800054, 0x13820078, 0x13830054, 0x13850078, 0x13860054, 0x13A90078, + 0x13AC0054, 0x13AF0078, 0x13B00054, 0x13B4000A, 0x13BB00C4, 0x13BC00C5, 0x13BD00C6, 0x13BE00C7, + 0x13BF011F, 0x13C000C4, 0x13C100C5, 0x13C200C6, 0x13C300C7, 0x13C4011F, 0x13C500C4, 0x13C600C5, + 0x13C700C6, 0x13C800C7, 0x13C9011F, 0x13CA0078, 0x13CC0054, 0x13DF0078, 0x13E00019, 0x13E100FD, + 0x13E20009, 0x13E30078, 0x13E80019, 0x13E900E8, 0x13EA00FD, 0x13EB0019, 0x13EE00FD, 0x13EF0019, + 0x13F100FE, 0x13F3000A, 0x13F60078, 0x13F80019, 0x14000094, 0x14800019, 0x14C10009, 0x14C601AE, + 0x14C701AF, 0x14C80009, 0x14CC0019, 0x14CD00E8, 0x14D80019, 0x14E000FE, 0x14E100E8, 0x14E200FE, + 0x14E30019, 0x14E400E8, 0x14E50019, 0x14E700FD, 0x14E90019, 0x14EA00FE, 0x14EB0019, 0x14EC000A, + 0x14EE0019, 0x14F000E8, 0x14F30019, 0x14F400E8, 0x14F50019, 0x14FA01B0, 0x14FB00E8, 0x14FC00FE, + 0x14FD0019, 0x14FE000A, 0x14FF0019, 0x150500E8, 0x150E0019, 0x150F00E8, 0x15110019, 0x151400E8, + 0x151500FD, 0x15170019, 0x151A00FE, 0x151B0019, 0x151E00FE, 0x151F0019, 0x152B00E8, 0x152C0019, + 0x153200FE, 0x15330019, 0x153500E8, 0x15380019, 0x153900E8, 0x153A1019, 0x153B0019, 0x153C00FD, + 0x153D00E8, 0x153E00FD, 0x154200E8, 0x154500FD, 0x154600E8, 0x154800FD, 0x154E00E8, 0x155000FD, + 0x155100E8, 0x15520019, 0x155300FE, 0x155700FD, 0x155800E8, 0x155900FD, 0x155A00E8, 0x155D00FD, + 0x156300E8, 0x156600FD, 0x156B0019, 0x157101B1, 0x15730019, 0x157600FE, 0x15770019, 0x157900E8, + 0x157A0019, 0x157B00FD, 0x157D00E8, 0x157F0019, 0x15800054, 0x158A0078, 0x16000096, 0x16170078, + 0x16180098, 0x162F0078, 0x16400065, 0x16720054, 0x16750078, 0x167C0006, 0x167E005F, 0x167F0006, + 0x16800125, 0x16930078, 0x16980071, 0x16B30078, 0x16B77881, 0x16B80078, 0x16C00071, 0x16CB0078, + 0x16D00071, 0x16D30078, 0x16D40071, 0x16D70078, 0x16D80071, 0x16DB0078, 0x16DC0071, 0x16DF0078, + 0x16E00071, 0x16E30078, 0x16E40071, 0x16E70078, 0x16E80071, 0x16EB0078, 0x16EC0071, 0x16EF0078, + 0x17000006, 0x170100E0, 0x17030006, 0x17040126, 0x17050006, 0x170600E0, 0x17070006, 0x170B0099, + 0x170C0078, 0x170E00E0, 0x170F0078, 0x17400054, 0x174F1054, 0x17500054, 0x17791054, 0x177A0078, + 0x17801054, 0x17EB0078, 0x17F80054, 0x17FE0078, 0x18000006, 0x18020081, 0x180301B2, 0x1804000A, + 0x18090054, 0x180A000A, 0x180E00B4, 0x180F00BF, 0x181001B3, 0x181101B4, 0x181201B5, 0x181301B6, + 0x181401B7, 0x18150083, 0x18180081, 0x181B0054, 0x181C11B8, 0x181D0081, 0x181E0006, 0x181F0054, + 0x18200071, 0x18320871, 0x18350071, 0x18380871, 0x183A0071, 0x183B0871, 0x183D0071, 0x183E0871, + 0x183F0071, 0x184B0078, 0x184C0083, 0x184D1037, 0x184E0081, 0x184F8071, 0x18500071, 0x18620871, + 0x18650071, 0x18680871, 0x186A0071, 0x186B0871, 0x186D0071, 0x186E0871, 0x186F0071, 0x187B0871, + 0x187D0006, 0x187E0081, 0x187F8071, 0x18800078, 0x18820071, 0x18960078, 0x18981071, 0x18C70078, + 0x18C80094, 0x18C978B6, 0x18CA78B7, 0x18CB7894, 0x18D00071, 0x18DC0078, 0x18E00054, 0x18E80078, + 0x18F80071, 0x19001094, 0x190E1054, 0x190F0078, 0x191010B6, 0x191110B7, 0x191210B8, 0x191310B9, + 0x191410B0, 0x19151094, 0x19220078, 0x192819B9, 0x192919BA, 0x192A19BB, 0x192B19BC, 0x192C19BD, + 0x192D19BE, 0x192E19BF, 0x192F19C0, 0x19301894, 0x193E1854, 0x193F0094, 0x194018B6, 0x194118B7, + 0x194218B8, 0x194318B9, 0x194418B0, 0x19451894, 0x195819C1, 0x195919C2, 0x195A19C3, 0x195B19C4, + 0x195C19C5, 0x195D19C6, 0x195E19C7, 0x195F19C8, 0x19601094, 0x19666854, 0x19681894, 0x197F0078, + 0x19806894, 0x19AC1094, 0x19B86894, 0x19BB6854, 0x19BD6894, 0x19EF6854, 0x19F01094, 0x19FF6854, + 0x1A000071, 0x26DB0078, 0x26E00054, 0x27000071, 0x4FDE0078, 0x50000071, 0x500A0081, 0x500B0071, + 0x52460078, 0x52480054, 0x52630078, 0x53800037, 0x538B0078, 0x54000071, 0x54050083, 0x54060071, + 0x541100A7, 0x54120083, 0x541300A7, 0x54140054, 0x54160078, 0x56000071, 0x6BD20078, 0x6C00013E, + 0x7000013F, 0x7C800871, 0x7D070071, 0x7D0A0871, 0x7D0F0071, 0x7D120871, 0x7D130071, 0x7D150871, + 0x7D170078, 0x7D180871, 0x7D350078, 0x7D380871, 0x7D6D0078, 0x7D801055, 0x7D830078, 0x7D891055, + 0x7D8C0078, 0x7D8E089B, 0x7D90289B, 0x7D94280B, 0x7D95089B, 0x7D9B0078, 0x7D9C089B, 0x7D9E0078, + 0x7DA0089B, 0x7DA20078, 0x7DA3089B, 0x7DA7109B, 0x7DA8209E, 0x7DAA489E, 0x7DAB209E, 0x7DAC489E, + 0x7DAD209E, 0x7DAE489E, 0x7DAF209E, 0x7DB0489E, 0x7DB1209E, 0x7DB2489E, 0x7DB3209E, 0x7DB4489E, + 0x7DB5209E, 0x7DB6489E, 0x7DB7209E, 0x7DB8489E, 0x7DB9209E, 0x7DBA489E, 0x7DBB209E, 0x7DBC489E, + 0x7DBD209E, 0x7DBE489E, 0x7DBF209E, 0x7DC0489E, 0x7DC1209E, 0x7DC8489E, 0x7DC9209E, 0x7DCA489E, + 0x7DCB209E, 0x7DCC489E, 0x7DCD209E, 0x7DCE489E, 0x7DCF209E, 0x7DD1489E, 0x7DD2209E, 0x7DD4489E, + 0x7DD5209E, 0x7DD6489E, 0x7DD7209E, 0x7DD90078, 0x7DE9409E, 0x7DEA389E, 0x7DEB409E, 0x7DEF209E, + 0x7DF3489E, 0x7DF5209E, 0x7DFC409E, 0x7DFD389E, 0x7DFE209E, 0x7DFF489E, 0x7E00409E, 0x7E32209E, + 0x7E4B389E, 0x7E6F489E, 0x7E7A409E, 0x7E88209E, 0x7E96389E, 0x7E9A489E, 0x7E9E409E, 0x7E9F00BF, + 0x7EA00078, 0x7EA8209E, 0x7EA9389E, 0x7EAD209E, 0x7EAE389E, 0x7EAF209E, 0x7EB0389E, 0x7EB3209E, + 0x7EB5389E, 0x7EB7209E, 0x7EB9389E, 0x7EBA209E, 0x7EBB389E, 0x7EBC209E, 0x7EBE389E, 0x7EBF209E, + 0x7EC1389E, 0x7EC2209E, 0x7EC4389E, 0x7EC5209E, 0x7EC6389E, 0x7EC80078, 0x7EC9389E, 0x7ECB209E, + 0x7ECE389E, 0x7ECF209E, 0x7EDA389E, 0x7EDB209E, 0x7EE1389E, 0x7EE3209E, 0x7EE40078, 0x7EF8409E, + 0x7EFE0054, 0x7EFF0078, 0x7F000083, 0x7F088006, 0x7F0B80B4, 0x7F0C8006, 0x7F0D0078, 0x7F100083, + 0x7F120078, 0x7F188099, 0x7F198038, 0x7F1A80B4, 0x7F220006, 0x7F2380B4, 0x7F241006, 0x7F261038, + 0x7F286006, 0x7F290078, 0x7F2A600C, 0x7F2B6006, 0x7F2C60B4, 0x7F2F6007, 0x7F306006, 0x7F31600D, + 0x7F326019, 0x7F330078, 0x7F346008, 0x7F356006, 0x7F360078, 0x7F38489E, 0x7F39009E, 0x7F3A0078, + 0x7F3B489E, 0x7F40409E, 0x7F45389E, 0x7F46409E, 0x7F48389E, 0x7F49409E, 0x7F4B389E, 0x7F4C409E, + 0x7F4D389E, 0x7F4E409E, 0x7F4F389E, 0x7F50409E, 0x7F51389E, 0x7F52409E, 0x7F53389E, 0x7F54409E, + 0x7F59389E, 0x7F5A409E, 0x7F5B389E, 0x7F5C409E, 0x7F5D389E, 0x7F5E409E, 0x7F5F389E, 0x7F60409E, + 0x7F61389E, 0x7F62409E, 0x7F63389E, 0x7F64409E, 0x7F65389E, 0x7F66409E, 0x7F67389E, 0x7F68409E, + 0x7F69389E, 0x7F6A409E, 0x7F6B389E, 0x7F6C409E, 0x7F6D389E, 0x7F6E409E, 0x7F6F389E, 0x7F70409E, + 0x7F71389E, 0x7F72409E, 0x7F73389E, 0x7F74409E, 0x7F75389E, 0x7F76409E, 0x7F79389E, 0x7F7A409E, + 0x7F7E0078, 0x7F7F0057, 0x7F808806, 0x7F818807, 0x7F838806, 0x7F84880A, 0x7F85880B, 0x7F86880D, + 0x7F87880C, 0x7F88880F, 0x7F898811, 0x7F8A8813, 0x7F8B8815, 0x7F8C8817, 0x7F8D8806, 0x7F8E8819, + 0x7F8F8806, 0x7F908860, 0x7F9D8835, 0x7F9E8836, 0x7F9F8838, 0x7FA08861, 0x7FAD8835, 0x7FAE8836, + 0x7FAF8809, 0x7FB05006, 0x7FB1500A, 0x7FB25006, 0x7FB35071, 0x7FCF5081, 0x7FD05071, 0x7FDF0078, + 0x7FE15071, 0x7FE40078, 0x7FE55071, 0x7FE80078, 0x7FE95071, 0x7FEC0078, 0x7FED5071, 0x7FEE0078, + 0x7FF08808, 0x7FF18837, 0x7FF28808, 0x7FF30078, 0x7FF45019, 0x7FF65054, 0x7FF70078, 0x7FFC0141, + 0x7FFE0054, 0x7FFF0078, 0x80000071, 0x80130078, 0x80140071, 0x801D0078, 0x801E0071, 0x80270078, + 0x80280071, 0x802F0078, 0x80400071, 0x807D0078, 0x80800006, 0x80810078, 0x808300AD, 0x808400AE, + 0x8085012D, 0x8086012E, 0x8087012F, 0x80880185, 0x80890186, 0x808A0187, 0x808B0188, 0x808C0184, + 0x808D01C9, 0x808E01CA, 0x808F01CB, 0x809001CC, 0x809101CD, 0x809201CE, 0x809301CF, 0x809401D0, + 0x809500BE, 0x809601D1, 0x809701D2, 0x809801D3, 0x809901D4, 0x809A0078, 0x809B0094, 0x80A0014E, + 0x80A10152, 0x80A20153, 0x80A30157, 0x80A40154, 0x80A50155, 0x80A60156, 0x80A70152, 0x80A80150, + 0x80A90153, 0x80AA01D5, 0x80AB0154, 0x80AC014F, 0x80AD0158, 0x80AF0152, 0x80B00154, 0x80B201D6, + 0x80B30150, 0x80B501D7, 0x80B60153, 0x80B80156, 0x80B90152, 0x80BA005F, 0x80BC0054, 0x80C50078, + 0x81800071, 0x818F0078, 0x8190012D, 0x819100BB, 0x81920078, 0x81980071, 0x81A50078, 0x81C00071, + 0x81CF0097, 0x81D00071, 0x81E20078, 0x81E40071, 0x81E8014F, 0x81E90154, 0x81EA0155, 0x81EB0078, + 0x8200015B, 0x8214015C, 0x82280071, 0x824F0078, 0x8250017E, 0x8251017F, 0x82520180, 0x82530181, + 0x82540182, 0x82550078, 0x8400009B, 0x84030078, 0x8405009B, 0x841C0078, 0x841F009B, 0x84200078, + 0x85000083, 0x85030078, 0x85060083, 0x8508009B, 0x851A0078, 0x851C0083, 0x851D0078, 0x851F0083, + 0x852001D8, 0x852101D9, 0x852201DA, 0x852301DB, 0x85240078, 0x8528009A, 0x852C0078, 0xE8000094, + 0xE87B0078, 0xE8800094, 0xE8930078, 0xE8950094, 0xE8AF0894, 0xE8B200A7, 0xE8B30083, 0xE8B50094, + 0xE8B600A7, 0xE8B90057, 0xE8BD0083, 0xE8C10094, 0xE8C20083, 0xE8C60094, 0xE8D50083, 0xE8D70094, + 0xE8DD0894, 0xE8E00094, 0xE8EF0078, 0xE9000054, 0xE9210083, 0xE9220054, 0xE9230078, 0xE9800054, + 0xE9AB0078, 0xEA002877, 0xEA0D2855, 0xEA1A2877, 0xEA272855, 0xEA2A0078, 0xEA2B2855, 0xEA342877, + 0xEA412855, 0xEA4E0078, 0xEA4F2877, 0xEA500078, 0xEA522877, 0xEA530078, 0xEA542877, 0xEA560078, + 0xEA572877, 0xEA5B2855, 0xEA682877, 0xEA752855, 0xEA822877, 0xEA850078, 0xEA862877, 0xEA8A0078, + 0xEA8B2877, 0xEA8E0078, 0xEA8F2855, 0xEA9C2877, 0xEA9F0078, 0xEAA02877, 0xEAA20078, 0xEAA52877, + 0xEAA80078, 0xEAA92855, 0xEAB62877, 0xEAC32855, 0xEAD02877, 0xEADD2855, 0xEAEA2877, 0xEAF72855, + 0xEB042877, 0xEB112855, 0xEB1E2877, 0xEB2B2855, 0xEB382877, 0xEB452855, 0xEB530078, 0xEB542877, + 0xEB6029DC, 0xEB612855, 0xEB6D29DC, 0xEB6E2855, 0xEB712877, 0xEB7D29DC, 0xEB7E2855, 0xEB8A29DC, + 0xEB8B2855, 0xEB8E2877, 0xEB9A29DC, 0xEB9B2855, 0xEBA729DC, 0xEBA82855, 0xEBAB2877, 0xEBB729DC, + 0xEBB82855, 0xEBC429DC, 0xEBC52855, 0xEBC82877, 0xEBD429DC, 0xEBD52855, 0xEBE129DC, 0xEBE22855, + 0xEBE50078, 0xEBE7280F, 0xEBE82811, 0xEBE92813, 0xEBEA2815, 0xEBEB2817, 0xEBEC280F, 0xEBED2811, + 0xEBEE2813, 0xEBEF2815, 0xEBF02817, 0xEBF1280F, 0xEBF22811, 0xEBF32813, 0xEBF42815, 0xEBF52817, + 0xEBF6280F, 0xEBF72811, 0xEBF82813, 0xEBF92815, 0xEBFA2817, 0xEBFB280F, 0xEBFC2811, 0xEBFD2813, + 0xEBFE2815, 0xEBFF2817, 0xEC000078 + }; + + static const uint32_t a17[] = { + 0x00000071, 0x536B0078, 0x7C000871, 0x7D0F0078 + }; + + static const uint32_t a23[] = { + 0x00000057, 0x00010078, 0x00100057, 0x00400078, 0x00800083, 0x00F80078, 0x8000013F, 0xFFFF0078 + }; + + static const uint32_t a24[] = { + 0x0000013F, 0x7FFF0078 + }; + + + // The full set of all arrays to be searched. + static const Range FULL_DATA[] = { + {sizeof(a0)/sizeof(uint32_t), a0}, + {sizeof(a1)/sizeof(uint32_t), a1}, + {0, 0}, + {0, 0}, + {0, 0}, + {0, 0}, + {0, 0}, + {sizeof(a7)/sizeof(uint32_t), a7}, + {sizeof(a8)/sizeof(uint32_t), a8}, + {0, 0}, + {0, 0}, + {0, 0}, + {0, 0}, + {0, 0}, + {0, 0}, + {0, 0}, + {sizeof(a16)/sizeof(uint32_t), a16}, + {sizeof(a17)/sizeof(uint32_t), a17}, + {0, 0}, + {0, 0}, + {0, 0}, + {0, 0}, + {0, 0}, + {sizeof(a23)/sizeof(uint32_t), a23}, + {sizeof(a24)/sizeof(uint32_t), a24}, + {0, 0}, + {0, 0}, + {0, 0}, + {0, 0}, + {0, 0}, + {0, 0}, + {0, 0} + }; + + // Array of uppercase differences + static const short UCDIFF[] = { + 0, -32, 743, 121, -1, -232, -300, 97, + 163, 130, 56, -2, -79, -210, -206, -205, + -202, -203, -207, -209, -211, -213, -214, -218, + -217, -219, -83, 84, -38, -37, -31, -64, + -63, -62, -57, -47, -54, -86, -80, 7, + -96, -48, -59, 8, 74, 86, 100, 128, + 112, 126, 9, -7205, -16, -26, -7264, -40 + }; + + // Array of lowercase differences + static const short LCDIFF[] = { + 0, 32, 1, -199, -121, 210, 206, 205, + 79, 202, 203, 207, 211, 209, 213, 214, + 218, 217, 219, 2, -97, -56, -130, -163, + 83, 38, 37, 64, 63, -60, -7, 80, + 48, 7264, -8, -74, -9, -86, -100, -112, + -128, -126, -7517, -8383, -8262, 16, 26, 40 + }; + + // Array of titlecase differences + static const short TCDIFF[] = { + 3, 1, 0, -1 + }; + + // Array of mirrored character differences + static const short MIRROR_DIFF[] = { + 0, 1, -1, 2, -2, 16, -16, 3, + -3, 2016, 138, 1824, 2104, 2108, 2106, -138, + 8, 7, -8, -7, -1824, -2016, -2104, -2106, + -2108 + }; + + // Array of all possible numeric values + static const int NUMERICS[] = { + -1, 0, 1, 2, 3, 4, 5, 6, + 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, + 23, 24, 25, 26, 27, 28, 29, 30, + 31, 32, 33, 34, 35, -2, 100, 1000, + 40, 50, 60, 70, 80, 90, 10000, 500, + 5000, 36, 37, 38, 39, 41, 42, 43, + 44, 45, 46, 47, 48, 49, 200, 300, + 400, 600, 700, 800, 900, 2000, 3000, 4000, + 6000, 7000, 8000, 9000, 20000, 30000, 40000, 50000, + 60000, 70000, 80000, 90000 + }; + + // All possible packed data values, no duplicates + static const uint32_t PACKED_DATA[] = { + 0x00000000, 0x0000012F, 0x0000016F, 0x0000014F, 0x0000018F, 0x0000018C, 0x000001B8, 0x000000B8, + 0x000000BA, 0x020005B5, 0x040005B6, 0x00000099, 0x000000F8, 0x00000094, 0x02000069, 0x04000069, + 0x06000069, 0x08000069, 0x0A000069, 0x0C000069, 0x0E000069, 0x10000069, 0x12000069, 0x14000069, + 0x060005B9, 0x000001B9, 0x080005B9, 0x16020001, 0x18020001, 0x1A020001, 0x1C020001, 0x1E020001, + 0x20020001, 0x22020001, 0x24020001, 0x26020001, 0x28020001, 0x2A020001, 0x2C020001, 0x2E020001, + 0x30020001, 0x32020001, 0x34020001, 0x36020001, 0x38020001, 0x3A020001, 0x3C020001, 0x3E020001, + 0x40020001, 0x42020001, 0x44020001, 0x46020001, 0x48020001, 0x060005B5, 0x080005B6, 0x000001BB, + 0x000001B7, 0x16000802, 0x18000802, 0x1A000802, 0x1C000802, 0x1E000802, 0x20000802, 0x22000802, + 0x24000802, 0x26000802, 0x28000802, 0x2A000802, 0x2C000802, 0x2E000802, 0x30000802, 0x32000802, + 0x34000802, 0x36000802, 0x38000802, 0x3A000802, 0x3C000802, 0x3E000802, 0x40000802, 0x42000802, + 0x44000802, 0x46000802, 0x48000802, 0x000000EC, 0x000001BC, 0x00000002, 0x0A0005BD, 0x00000130, + 0x000000BC, 0x000000B9, 0x0600006B, 0x0800006B, 0x00001002, 0x0400006B, 0x0C0005BE, 0x4A0001AB, + 0x00020001, 0x00000802, 0x00001802, 0x00040001, 0x00060001, 0x00002002, 0x00080001, 0x000C0001, + 0x000E0001, 0x00100001, 0x00140001, 0x00160001, 0x00180001, 0x00004002, 0x00004802, 0x00200001, + 0x00220001, 0x00000005, 0x00A60001, 0x01805802, 0x01042003, 0x00280001, 0x002C0001, 0x00000001, + 0x00000000, 0x00007002, 0x00007802, 0x00009802, 0x0000A802, 0x0000B802, 0x0000C002, 0x0000C802, + 0x0000D002, 0x00000004, 0x000001A4, 0x00000106, 0x00320001, 0x00340001, 0x00360001, 0x00380001, + 0x0000E002, 0x0000E802, 0x0000F002, 0x0000F802, 0x00010002, 0x00010802, 0x00012002, 0x00012802, + 0x00013802, 0x003A0001, 0x003E0001, 0x00013002, 0x0000001C, 0x00000107, 0x00400001, 0x00000018, + 0x00014802, 0x000001B4, 0x00000038, 0x00000025, 0x00000050, 0x00000058, 0x00000045, 0x00000044, + 0x020000C9, 0x060000C9, 0x0A0000C9, 0x0E0000C9, 0x120000C9, 0x000000D8, 0x0000005C, 0x00000008, + 0x02000009, 0x06000009, 0x0A000009, 0x0E000009, 0x12000009, 0x0400000B, 0x0800000B, 0x0000000B, + 0x1600000B, 0x4E00000B, 0x00000006, 0x4A00000B, 0x000001B5, 0x00420001, 0x0600000B, 0x0A00000B, + 0x0E00000B, 0x1200000B, 0x3E00000B, 0x5200000B, 0x5600000B, 0x5A00000B, 0x5C00000B, 0x000001B6, + 0x2400000A, 0x2800000A, 0x00000010, 0x020001AB, 0x060001AB, 0x0A0001AB, 0x0E0001AB, 0x120001AB, + 0x00000108, 0x00015802, 0x00440001, 0x00016002, 0x00016802, 0x00017002, 0x00017802, 0x00018002, + 0x00018802, 0x00440003, 0x00460001, 0x00480003, 0x00019802, 0x004A0001, 0x004C0001, 0x004E0001, + 0x003C0001, 0x00500001, 0x00520001, 0x000001BD, 0x0000018D, 0x000001D0, 0x00000250, 0x00000230, + 0x040005BE, 0x000000F9, 0x0200006B, 0x0A00006B, 0x0E00006B, 0x1200006B, 0x00540001, 0x00560001, + 0x000005B9, 0x045A000A, 0x085A000A, 0x0C5A000A, 0x105A000A, 0x145A000A, 0x185A000A, 0x525A000A, + 0x5E5A000A, 0x0401A00A, 0x0801A00A, 0x0C01A00A, 0x1001A00A, 0x1401A00A, 0x1801A00A, 0x5201A00A, + 0x5E01A00A, 0x4E00000A, 0x5C00000A, 0x0E0005B9, 0x100005B9, 0x020005B9, 0x040005B9, 0x160005B9, + 0x180005B9, 0x1A0005B9, 0x200005B9, 0x220005B9, 0x240005B9, 0x260005B9, 0x040001AB, 0x080001AB, + 0x0C0001AB, 0x100001AB, 0x140001AB, 0x180001AB, 0x1C0001AB, 0x200001AB, 0x240001AB, 0x280001AB, + 0x0C00006B, 0x1000006B, 0x1400006B, 0x1800006B, 0x1C00006B, 0x2000006B, 0x2400006B, 0x2800006B, + 0x005C001C, 0x0001A81C, 0x1A0001AB, 0x1E0001AB, 0x220001AB, 0x260001AB, 0x2A0001AB, 0x160001AB, + 0x020005B6, 0x100005B6, 0x280005B9, 0x2C0005B9, 0x300005B9, 0x0001B002, 0x020005BD, 0x0600000A, + 0x0A00000A, 0x0E00000A, 0x1200000A, 0x1600000A, 0x3E00000A, 0x0C00000B, 0x1000000B, 0x1400000B, + 0x2E0001AB, 0x320001AB, 0x360001AB, 0x3A0001AB, 0x3E0001AB, 0x420001AB, 0x460001AB, 0x640001AB, + 0x680001AB, 0x6A0001AB, 0x6E0001AB, 0x720001AB, 0x760001AB, 0x7A0001AB, 0x00000013, 0x00000012, + 0x0000005A, 0x000001B0, 0x7C00000B, 0x8000000B, 0x8200000B, 0x8600000B, 0x8C00000B, 0x6000000B, + 0x9200000B, 0x9600000B, 0x9800000B, 0x9C00000B, 0xA000000B, 0xA400000B, 0x4A0001AA, 0x040001AA, + 0x520001AA, 0x600001AA, 0x0C0001AA, 0x5E0001AA, 0x160001AA, 0x4C0001AA, 0x4E0001AA, 0x9E0001AA, + 0x060001AA, 0x8800000A, 0x2A0001AA, 0x005E0001, 0x0001B802, 0x0400002B, 0x0800002B, 0x1600002B, + 0x4C00002B, 0x00002802, 0x00003002, 0x000A0001, 0x00120001, 0x00003802, 0x001A0001, 0x001C0001, + 0x001E0001, 0x00240001, 0x00005002, 0x00006002, 0x002A0001, 0x002E0001, 0x00300001, 0x00006802, + 0x00008002, 0x00008802, 0x00009002, 0x0000A002, 0x0000B002, 0x0000D906, 0x00011002, 0x00011802, + 0x00014002, 0x040000C9, 0x080000C9, 0x0C0000C9, 0x100000C9, 0x140000C9, 0x04000009, 0x08000009, + 0x0C000009, 0x10000009, 0x14000009, 0x2200000B, 0x4C00000B, 0x2A00000B, 0x5000000B, 0x5400000B, + 0x5800000B, 0x2600000A, 0x00015002, 0x00019002, 0x00000030, 0x000001BE, 0x0000014E, 0x00000210, + 0x000001F0, 0x00580001, 0x065A000A, 0x0A5A000A, 0x0E5A000A, 0x125A000A, 0x165A000A, 0x1A5A000A, + 0x4C5A000A, 0x4E5A000A, 0x0601A00A, 0x0A01A00A, 0x0E01A00A, 0x1201A00A, 0x1601A00A, 0x1A01A00A, + 0x4C01A00A, 0x4E01A00A, 0x6000000A, 0x0000000A, 0x120005B9, 0x140005B9, 0x1C0005B9, 0x1E0005B9, + 0x1600006B, 0x1A00006B, 0x1E00006B, 0x2200006B, 0x2600006B, 0x2A00006B, 0x0E0005B5, 0x040005B5, + 0x2A0005B9, 0x2E0005B9, 0x0200000A, 0x0400000A, 0x0800000A, 0x0C00000A, 0x1000000A, 0x1400000A, + 0x2A00000A, 0x2C0001AB, 0x300001AB, 0x340001AB, 0x380001AB, 0x3C0001AB, 0x400001AB, 0x440001AB, + 0x480001AB, 0x620001AB, 0x660001AB, 0x500001AB, 0x6C0001AB, 0x700001AB, 0x740001AB, 0x780001AB, + 0x520001AB, 0x7E00000B, 0x5E00000B, 0x8400000B, 0x8800000B, 0x8A00000B, 0x8E00000B, 0x9000000B, + 0x9400000B, 0x9A00000B, 0x9E00000B, 0xA200000B, 0xA600000B, 0x5C0001AA, 0x3E0001AA, 0x7E0001AA, + 0x0600002B, 0x0A00002B, 0x2A00002B, 0x4E00002B, 0x00000019 + }; +} diff --git a/libs/utils/executablepath_darwin.cpp b/libs/utils/executablepath_darwin.cpp new file mode 100644 index 0000000..2e3c3a0 --- /dev/null +++ b/libs/utils/executablepath_darwin.cpp @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2008 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. + */ + +#include <utils/executablepath.h> +#import <Carbon/Carbon.h> +#include <unistd.h> + +void executablepath(char s[PATH_MAX]) +{ + ProcessSerialNumber psn; + GetCurrentProcess(&psn); + CFDictionaryRef dict; + dict = ProcessInformationCopyDictionary(&psn, 0xffffffff); + CFStringRef value = (CFStringRef)CFDictionaryGetValue(dict, + CFSTR("CFBundleExecutable")); + CFStringGetCString(value, s, PATH_MAX+1, kCFStringEncodingUTF8); +} + diff --git a/libs/utils/executablepath_linux.cpp b/libs/utils/executablepath_linux.cpp new file mode 100644 index 0000000..b8d2a3d --- /dev/null +++ b/libs/utils/executablepath_linux.cpp @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2008 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. + */ + +#include <utils/executablepath.h> +#include <sys/types.h> +#include <unistd.h> +#include <limits.h> +#include <stdio.h> + +void executablepath(char exe[PATH_MAX]) +{ + char proc[100]; + sprintf(proc, "/proc/%d/exe", getpid()); + + int err = readlink(proc, exe, PATH_MAX); +} + diff --git a/libs/utils/futex_synchro.c b/libs/utils/futex_synchro.c new file mode 100644 index 0000000..c13760d --- /dev/null +++ b/libs/utils/futex_synchro.c @@ -0,0 +1,175 @@ +/* + * Copyright (C) 2008 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. + */ + +#include <stdio.h> +#include <limits.h> + +#include <sys/time.h> +#include <sched.h> + +#include <errno.h> + +#include <private/utils/futex_synchro.h> + + +// This futex glue code is need on desktop linux, but is part of klibc on ARM +#if !defined(__arm__) + +#include <sys/syscall.h> +typedef unsigned int u32; +#define asmlinkage +#define __user +#include <linux/futex.h> +#include <utils/Atomic.h> + + +int futex (int *uaddr, int op, int val, const struct timespec *timeout, int *uaddr2, int val3) +{ + int err = syscall(SYS_futex, uaddr, op, val, timeout, uaddr2, val3); + return err == 0 ? 0 : -errno; +} + +int __futex_wait(volatile void *ftx, int val, const struct timespec *timeout) +{ + return futex((int*)ftx, FUTEX_WAIT, val, timeout, NULL, 0); +} + +int __futex_wake(volatile void *ftx, int count) +{ + return futex((int*)ftx, FUTEX_WAKE, count, NULL, NULL, 0); +} + +int __atomic_cmpxchg(int old, int _new, volatile int *ptr) +{ + return android_atomic_cmpxchg(old, _new, ptr); +} + +int __atomic_swap(int _new, volatile int *ptr) +{ + return android_atomic_swap(_new, ptr); +} + +int __atomic_dec(volatile int *ptr) +{ + return android_atomic_dec(ptr); +} + +#else // !defined(__arm__) + +int __futex_wait(volatile void *ftx, int val, const struct timespec *timeout); +int __futex_wake(volatile void *ftx, int count); + +int __atomic_cmpxchg(int old, int _new, volatile int *ptr); +int __atomic_swap(int _new, volatile int *ptr); +int __atomic_dec(volatile int *ptr); + +#endif // !defined(__arm__) + + +// lock states +// +// 0: unlocked +// 1: locked, no waiters +// 2: locked, maybe waiters + +void futex_mutex_init(futex_mutex_t *m) +{ + m->value = 0; +} + +int futex_mutex_lock(futex_mutex_t *m, unsigned msec) +{ + if(__atomic_cmpxchg(0, 1, &m->value) == 0) { + return 0; + } + if(msec == FUTEX_WAIT_INFINITE) { + while(__atomic_swap(2, &m->value) != 0) { + __futex_wait(&m->value, 2, 0); + } + } else { + struct timespec ts; + ts.tv_sec = msec / 1000; + ts.tv_nsec = (msec % 1000) * 1000000; + while(__atomic_swap(2, &m->value) != 0) { + if(__futex_wait(&m->value, 2, &ts) == -ETIMEDOUT) { + return -1; + } + } + } + return 0; +} + +int futex_mutex_trylock(futex_mutex_t *m) +{ + if(__atomic_cmpxchg(0, 1, &m->value) == 0) { + return 0; + } + return -1; +} + +void futex_mutex_unlock(futex_mutex_t *m) +{ + if(__atomic_dec(&m->value) != 1) { + m->value = 0; + __futex_wake(&m->value, 1); + } +} + +/* XXX *technically* there is a race condition that could allow + * XXX a signal to be missed. If thread A is preempted in _wait() + * XXX after unlocking the mutex and before waiting, and if other + * XXX threads call signal or broadcast UINT_MAX times (exactly), + * XXX before thread A is scheduled again and calls futex_wait(), + * XXX then the signal will be lost. + */ + +void futex_cond_init(futex_cond_t *c) +{ + c->value = 0; +} + +int futex_cond_wait(futex_cond_t *c, futex_mutex_t *m, unsigned msec) +{ + if(msec == FUTEX_WAIT_INFINITE){ + int oldvalue = c->value; + futex_mutex_unlock(m); + __futex_wait(&c->value, oldvalue, 0); + futex_mutex_lock(m, FUTEX_WAIT_INFINITE); + return 0; + } else { + int oldvalue = c->value; + struct timespec ts; + ts.tv_sec = msec / 1000; + ts.tv_nsec = (msec % 1000) * 1000000; + futex_mutex_unlock(m); + const int err = __futex_wait(&c->value, oldvalue, &ts); + futex_mutex_lock(m, FUTEX_WAIT_INFINITE); + return err; + } +} + +void futex_cond_signal(futex_cond_t *c) +{ + __atomic_dec(&c->value); + __futex_wake(&c->value, 1); +} + +void futex_cond_broadcast(futex_cond_t *c) +{ + __atomic_dec(&c->value); + __futex_wake(&c->value, INT_MAX); +} + diff --git a/libs/utils/misc.cpp b/libs/utils/misc.cpp new file mode 100644 index 0000000..dc89d15 --- /dev/null +++ b/libs/utils/misc.cpp @@ -0,0 +1,185 @@ +/* + * Copyright (C) 2005 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. + */ + +// +// Miscellaneous utility functions. +// +#include <utils/misc.h> + +#include <sys/stat.h> +#include <string.h> +#include <errno.h> +#include <assert.h> +#include <stdio.h> + +using namespace android; + +namespace android { + +/* + * Like strdup(), but uses C++ "new" operator instead of malloc. + */ +char* strdupNew(const char* str) +{ + char* newStr; + int len; + + if (str == NULL) + return NULL; + + len = strlen(str); + newStr = new char[len+1]; + memcpy(newStr, str, len+1); + + return newStr; +} + +/* + * Concatenate an argument vector. + */ +char* concatArgv(int argc, const char* const argv[]) +{ + char* newStr = NULL; + int len, totalLen, posn, idx; + + /* + * First, figure out the total length. + */ + totalLen = idx = 0; + while (1) { + if (idx == argc || argv[idx] == NULL) + break; + if (idx) + totalLen++; // leave a space between args + totalLen += strlen(argv[idx]); + idx++; + } + + /* + * Alloc the string. + */ + newStr = new char[totalLen +1]; + if (newStr == NULL) + return NULL; + + /* + * Finally, allocate the string and copy data over. + */ + idx = posn = 0; + while (1) { + if (idx == argc || argv[idx] == NULL) + break; + if (idx) + newStr[posn++] = ' '; + + len = strlen(argv[idx]); + memcpy(&newStr[posn], argv[idx], len); + posn += len; + + idx++; + } + + assert(posn == totalLen); + newStr[posn] = '\0'; + + return newStr; +} + +/* + * Count the #of args in an argument vector. Don't count the final NULL. + */ +int countArgv(const char* const argv[]) +{ + int count = 0; + + while (argv[count] != NULL) + count++; + + return count; +} + + +#include <stdio.h> +/* + * Get a file's type. + */ +FileType getFileType(const char* fileName) +{ + struct stat sb; + + if (stat(fileName, &sb) < 0) { + if (errno == ENOENT || errno == ENOTDIR) + return kFileTypeNonexistent; + else { + fprintf(stderr, "getFileType got errno=%d on '%s'\n", + errno, fileName); + return kFileTypeUnknown; + } + } else { + if (S_ISREG(sb.st_mode)) + return kFileTypeRegular; + else if (S_ISDIR(sb.st_mode)) + return kFileTypeDirectory; + else if (S_ISCHR(sb.st_mode)) + return kFileTypeCharDev; + else if (S_ISBLK(sb.st_mode)) + return kFileTypeBlockDev; + else if (S_ISFIFO(sb.st_mode)) + return kFileTypeFifo; +#ifdef HAVE_SYMLINKS + else if (S_ISLNK(sb.st_mode)) + return kFileTypeSymlink; + else if (S_ISSOCK(sb.st_mode)) + return kFileTypeSocket; +#endif + else + return kFileTypeUnknown; + } +} + +/* + * Get a file's modification date. + */ +time_t getFileModDate(const char* fileName) +{ + struct stat sb; + + if (stat(fileName, &sb) < 0) + return (time_t) -1; + + return sb.st_mtime; +} + +/* + * Round up to the next highest power of 2. + * + * Found on http://graphics.stanford.edu/~seander/bithacks.html. + */ +unsigned int roundUpPower2(unsigned int val) +{ + val--; + val |= val >> 1; + val |= val >> 2; + val |= val >> 4; + val |= val >> 8; + val |= val >> 16; + val++; + + return val; +} + +}; // namespace android + diff --git a/libs/utils/ported.cpp b/libs/utils/ported.cpp new file mode 100644 index 0000000..656e46f --- /dev/null +++ b/libs/utils/ported.cpp @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2005 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. + */ + +// +// Ports of standard functions that don't exist on a specific platform. +// +// Note these are NOT in the "android" namespace. +// +#include <utils/ported.h> + +#if defined(NEED_GETTIMEOFDAY) || defined(NEED_USLEEP) +# include <sys/time.h> +# include <windows.h> +#endif + + +#if defined(NEED_GETTIMEOFDAY) +/* + * Replacement gettimeofday() for Windows environments (primarily MinGW). + * + * Ignores "tz". + */ +int gettimeofday(struct timeval* ptv, struct timezone* tz) +{ + long long nsTime; // time in 100ns units since Jan 1 1601 + FILETIME ft; + + if (tz != NULL) { + // oh well + } + + ::GetSystemTimeAsFileTime(&ft); + nsTime = (long long) ft.dwHighDateTime << 32 | + (long long) ft.dwLowDateTime; + // convert to time in usec since Jan 1 1970 + ptv->tv_usec = (long) ((nsTime / 10LL) % 1000000LL); + ptv->tv_sec = (long) ((nsTime - 116444736000000000LL) / 10000000LL); + + return 0; +} +#endif + +#if defined(NEED_USLEEP) +// +// Replacement usleep for Windows environments (primarily MinGW). +// +void usleep(unsigned long usec) +{ + // Win32 API function Sleep() takes milliseconds + ::Sleep((usec + 500) / 1000); +} +#endif + +#if 0 //defined(NEED_PIPE) +// +// Replacement pipe() command for MinGW +// +// The _O_NOINHERIT flag sets bInheritHandle to FALSE in the +// SecurityAttributes argument to CreatePipe(). This means the handles +// aren't inherited when a new process is created. The examples I've seen +// use it, possibly because there's a lot of junk going on behind the +// scenes. (I'm assuming "process" and "thread" are different here, so +// we should be okay spinning up a thread.) The recommended practice is +// to dup() the descriptor you want the child to have. +// +// It appears that unnamed pipes can't do non-blocking ("overlapped") I/O. +// You can't use select() either, since that only works on sockets. The +// Windows API calls that are useful here all operate on a HANDLE, not +// an integer file descriptor, and I don't think you can get there from +// here. The "named pipe" stuff is insane. +// +int pipe(int filedes[2]) +{ + return _pipe(filedes, 0, _O_BINARY | _O_NOINHERIT); +} +#endif + +#if defined(NEED_SETENV) +/* + * MinGW lacks these. For now, just stub them out so the code compiles. + */ +int setenv(const char* name, const char* value, int overwrite) +{ + return 0; +} +void unsetenv(const char* name) +{ +} +char* getenv(const char* name) +{ + return NULL; +} +#endif |