diff options
Diffstat (limited to 'libs/utils')
-rw-r--r-- | libs/utils/Android.mk | 8 | ||||
-rw-r--r-- | libs/utils/AssetManager.cpp | 2 | ||||
-rw-r--r-- | libs/utils/PollLoop.cpp | 277 | ||||
-rw-r--r-- | libs/utils/Pool.cpp | 37 | ||||
-rw-r--r-- | libs/utils/ResourceTypes.cpp | 3 | ||||
-rw-r--r-- | libs/utils/StopWatch.cpp | 11 | ||||
-rw-r--r-- | libs/utils/String8.cpp | 12 | ||||
-rw-r--r-- | libs/utils/VectorImpl.cpp | 18 | ||||
-rw-r--r-- | libs/utils/ZipFileCRO.cpp | 4 | ||||
-rw-r--r-- | libs/utils/ZipFileRO.cpp | 462 | ||||
-rw-r--r-- | libs/utils/tests/Android.mk | 38 | ||||
-rw-r--r-- | libs/utils/tests/PollLoop_test.cpp | 370 | ||||
-rw-r--r-- | libs/utils/tests/TestHelpers.h | 79 |
13 files changed, 1138 insertions, 183 deletions
diff --git a/libs/utils/Android.mk b/libs/utils/Android.mk index afecdcb..7d4524a 100644 --- a/libs/utils/Android.mk +++ b/libs/utils/Android.mk @@ -26,6 +26,7 @@ commonSources:= \ Debug.cpp \ FileMap.cpp \ Flattenable.cpp \ + Pool.cpp \ RefBase.cpp \ ResourceTypes.cpp \ SharedBuffer.cpp \ @@ -39,7 +40,7 @@ commonSources:= \ Threads.cpp \ Timers.cpp \ VectorImpl.cpp \ - ZipFileCRO.cpp \ + ZipFileCRO.cpp \ ZipFileRO.cpp \ ZipUtils.cpp \ misc.cpp @@ -76,8 +77,9 @@ include $(CLEAR_VARS) # we have the common sources, plus some device-specific stuff LOCAL_SRC_FILES:= \ $(commonSources) \ - BackupData.cpp \ - BackupHelpers.cpp + BackupData.cpp \ + BackupHelpers.cpp \ + PollLoop.cpp ifeq ($(TARGET_OS),linux) LOCAL_LDLIBS += -lrt -ldl diff --git a/libs/utils/AssetManager.cpp b/libs/utils/AssetManager.cpp index 5a05e6a..60a0d82 100644 --- a/libs/utils/AssetManager.cpp +++ b/libs/utils/AssetManager.cpp @@ -824,7 +824,7 @@ Asset* AssetManager::openAssetFromZipLocked(const ZipFileRO* pZipFile, // TODO: look for previously-created shared memory slice? int method; - long uncompressedLen; + size_t uncompressedLen; //printf("USING Zip '%s'\n", pEntry->getFileName()); diff --git a/libs/utils/PollLoop.cpp b/libs/utils/PollLoop.cpp new file mode 100644 index 0000000..20a4d13 --- /dev/null +++ b/libs/utils/PollLoop.cpp @@ -0,0 +1,277 @@ +// +// Copyright 2010 The Android Open Source Project +// +// A select loop implementation. +// +#define LOG_TAG "PollLoop" + +//#define LOG_NDEBUG 0 + +// Debugs poll and wake interactions. +#define DEBUG_POLL_AND_WAKE 0 + +// Debugs callback registration and invocation. +#define DEBUG_CALLBACKS 0 + +#include <cutils/log.h> +#include <utils/PollLoop.h> + +#include <unistd.h> +#include <fcntl.h> + +namespace android { + +PollLoop::PollLoop() : + mPolling(false), mWaiters(0) { + openWakePipe(); +} + +PollLoop::~PollLoop() { + closeWakePipe(); +} + +void PollLoop::openWakePipe() { + int wakeFds[2]; + int result = pipe(wakeFds); + LOG_ALWAYS_FATAL_IF(result != 0, "Could not create wake pipe. errno=%d", errno); + + mWakeReadPipeFd = wakeFds[0]; + mWakeWritePipeFd = wakeFds[1]; + + result = fcntl(mWakeReadPipeFd, F_SETFL, O_NONBLOCK); + LOG_ALWAYS_FATAL_IF(result != 0, "Could not make wake read pipe non-blocking. errno=%d", + errno); + + result = fcntl(mWakeWritePipeFd, F_SETFL, O_NONBLOCK); + LOG_ALWAYS_FATAL_IF(result != 0, "Could not make wake write pipe non-blocking. errno=%d", + errno); + + // Add the wake pipe to the head of the request list with a null callback. + struct pollfd requestedFd; + requestedFd.fd = mWakeReadPipeFd; + requestedFd.events = POLLIN; + mRequestedFds.insertAt(requestedFd, 0); + + RequestedCallback requestedCallback; + requestedCallback.callback = NULL; + requestedCallback.data = NULL; + mRequestedCallbacks.insertAt(requestedCallback, 0); +} + +void PollLoop::closeWakePipe() { + close(mWakeReadPipeFd); + close(mWakeWritePipeFd); + + // Note: We don't need to remove the poll structure or callback entry because this + // method is currently only called by the destructor. +} + +bool PollLoop::pollOnce(int timeoutMillis) { + mLock.lock(); + while (mWaiters != 0) { + mResume.wait(mLock); + } + mPolling = true; + mLock.unlock(); + + bool result; + size_t requestedCount = mRequestedFds.size(); + +#if DEBUG_POLL_AND_WAKE + LOGD("%p ~ pollOnce - waiting on %d fds", this, requestedCount); + for (size_t i = 0; i < requestedCount; i++) { + LOGD(" fd %d - events %d", mRequestedFds[i].fd, mRequestedFds[i].events); + } +#endif + + int respondedCount = poll(mRequestedFds.editArray(), requestedCount, timeoutMillis); + + if (respondedCount == 0) { + // Timeout +#if DEBUG_POLL_AND_WAKE + LOGD("%p ~ pollOnce - timeout", this); +#endif + result = false; + goto Done; + } + + if (respondedCount < 0) { + // Error +#if DEBUG_POLL_AND_WAKE + LOGD("%p ~ pollOnce - error, errno=%d", this, errno); +#endif + if (errno != EINTR) { + LOGW("Poll failed with an unexpected error, errno=%d", errno); + } + result = false; + goto Done; + } + +#if DEBUG_POLL_AND_WAKE + LOGD("%p ~ pollOnce - handling responses from %d fds", this, respondedCount); + for (size_t i = 0; i < requestedCount; i++) { + LOGD(" fd %d - events %d, revents %d", mRequestedFds[i].fd, mRequestedFds[i].events, + mRequestedFds[i].revents); + } +#endif + + mPendingCallbacks.clear(); + for (size_t i = 0; i < requestedCount; i++) { + const struct pollfd& requestedFd = mRequestedFds.itemAt(i); + + short revents = requestedFd.revents; + if (revents) { + const RequestedCallback& requestedCallback = mRequestedCallbacks.itemAt(i); + Callback callback = requestedCallback.callback; + + if (callback) { + PendingCallback pendingCallback; + pendingCallback.fd = requestedFd.fd; + pendingCallback.events = requestedFd.revents; + pendingCallback.callback = callback; + pendingCallback.data = requestedCallback.data; + mPendingCallbacks.push(pendingCallback); + } else { + if (requestedFd.fd == mWakeReadPipeFd) { +#if DEBUG_POLL_AND_WAKE + LOGD("%p ~ pollOnce - awoken", this); +#endif + char buffer[16]; + ssize_t nRead; + do { + nRead = read(mWakeReadPipeFd, buffer, sizeof(buffer)); + } while (nRead == sizeof(buffer)); + } else { +#if DEBUG_POLL_AND_WAKE || DEBUG_CALLBACKS + LOGD("%p ~ pollOnce - fd %d has no callback!", this, requestedFd.fd); +#endif + } + } + + respondedCount -= 1; + if (respondedCount == 0) { + break; + } + } + } + result = true; + +Done: + mLock.lock(); + mPolling = false; + if (mWaiters != 0) { + mAwake.broadcast(); + } + mLock.unlock(); + + if (result) { + size_t pendingCount = mPendingCallbacks.size(); + for (size_t i = 0; i < pendingCount; i++) { + const PendingCallback& pendingCallback = mPendingCallbacks.itemAt(i); +#if DEBUG_POLL_AND_WAKE || DEBUG_CALLBACKS + LOGD("%p ~ pollOnce - invoking callback for fd %d", this, pendingCallback.fd); +#endif + + bool keep = pendingCallback.callback(pendingCallback.fd, pendingCallback.events, + pendingCallback.data); + if (! keep) { + removeCallback(pendingCallback.fd); + } + } + } + +#if DEBUG_POLL_AND_WAKE + LOGD("%p ~ pollOnce - done", this); +#endif + return result; +} + +void PollLoop::wake() { +#if DEBUG_POLL_AND_WAKE + LOGD("%p ~ wake", this); +#endif + + ssize_t nWrite = write(mWakeWritePipeFd, "W", 1); + if (nWrite != 1) { + if (errno != EAGAIN) { + LOGW("Could not write wake signal, errno=%d", errno); + } + } +} + +void PollLoop::setCallback(int fd, int events, Callback callback, void* data) { +#if DEBUG_CALLBACKS + LOGD("%p ~ setCallback - fd=%d, events=%d", this, fd, events); +#endif + + if (! events || ! callback) { + LOGE("Invalid attempt to set a callback with no selected poll events or no callback."); + removeCallback(fd); + return; + } + + wakeAndLock(); + + struct pollfd requestedFd; + requestedFd.fd = fd; + requestedFd.events = events; + + RequestedCallback requestedCallback; + requestedCallback.callback = callback; + requestedCallback.data = data; + + ssize_t index = getRequestIndexLocked(fd); + if (index < 0) { + mRequestedFds.push(requestedFd); + mRequestedCallbacks.push(requestedCallback); + } else { + mRequestedFds.replaceAt(requestedFd, size_t(index)); + mRequestedCallbacks.replaceAt(requestedCallback, size_t(index)); + } + + mLock.unlock(); +} + +bool PollLoop::removeCallback(int fd) { +#if DEBUG_CALLBACKS + LOGD("%p ~ removeCallback - fd=%d", this, fd); +#endif + + wakeAndLock(); + + ssize_t index = getRequestIndexLocked(fd); + if (index >= 0) { + mRequestedFds.removeAt(size_t(index)); + mRequestedCallbacks.removeAt(size_t(index)); + } + + mLock.unlock(); + return index >= 0; +} + +ssize_t PollLoop::getRequestIndexLocked(int fd) { + size_t requestCount = mRequestedFds.size(); + + for (size_t i = 0; i < requestCount; i++) { + if (mRequestedFds.itemAt(i).fd == fd) { + return i; + } + } + + return -1; +} + +void PollLoop::wakeAndLock() { + mLock.lock(); + mWaiters += 1; + while (mPolling) { + wake(); + mAwake.wait(mLock); + } + mWaiters -= 1; + if (mWaiters == 0) { + mResume.signal(); + } +} + +} // namespace android diff --git a/libs/utils/Pool.cpp b/libs/utils/Pool.cpp new file mode 100644 index 0000000..8f18cb9 --- /dev/null +++ b/libs/utils/Pool.cpp @@ -0,0 +1,37 @@ +// +// Copyright 2010 The Android Open Source Project +// +// A simple memory pool. +// +#define LOG_TAG "Pool" + +//#define LOG_NDEBUG 0 + +#include <cutils/log.h> +#include <utils/Pool.h> + +#include <stdlib.h> + +namespace android { + +// TODO Provide a real implementation of a pool. This is just a stub for initial development. + +PoolImpl::PoolImpl(size_t objSize) : + mObjSize(objSize) { +} + +PoolImpl::~PoolImpl() { +} + +void* PoolImpl::allocImpl() { + void* ptr = malloc(mObjSize); + LOG_ALWAYS_FATAL_IF(ptr == NULL, "Cannot allocate new pool object."); + return ptr; +} + +void PoolImpl::freeImpl(void* obj) { + LOG_ALWAYS_FATAL_IF(obj == NULL, "Caller attempted to free NULL pool object."); + return free(obj); +} + +} // namespace android diff --git a/libs/utils/ResourceTypes.cpp b/libs/utils/ResourceTypes.cpp index 7e0f881..a1401ad 100644 --- a/libs/utils/ResourceTypes.cpp +++ b/libs/utils/ResourceTypes.cpp @@ -4178,6 +4178,9 @@ void ResTable::print(bool inclValues) const case ResTable_config::SCREENSIZE_LARGE: printf(" (large)"); break; + case ResTable_config::SCREENSIZE_XLARGE: + printf(" (xlarge)"); + break; } printf(" lng=%d", type->config.screenLayout&ResTable_config::MASK_SCREENLONG); diff --git a/libs/utils/StopWatch.cpp b/libs/utils/StopWatch.cpp index 68a1c52..b5dda2f 100644 --- a/libs/utils/StopWatch.cpp +++ b/libs/utils/StopWatch.cpp @@ -30,10 +30,9 @@ namespace android { StopWatch::StopWatch(const char *name, int clock, uint32_t flags) - : mName(name), mClock(clock), mFlags(flags), - mStartTime(0), mNumLaps(0) + : mName(name), mClock(clock), mFlags(flags) { - mStartTime = systemTime(mClock); + reset(); } StopWatch::~StopWatch() @@ -72,6 +71,12 @@ nsecs_t StopWatch::elapsedTime() const return systemTime(mClock) - mStartTime; } +void StopWatch::reset() +{ + mNumLaps = 0; + mStartTime = systemTime(mClock); +} + /*****************************************************************************/ diff --git a/libs/utils/String8.cpp b/libs/utils/String8.cpp index 636cd83..82776f4 100644 --- a/libs/utils/String8.cpp +++ b/libs/utils/String8.cpp @@ -301,8 +301,9 @@ void String8::setTo(const String8& other) status_t String8::setTo(const char* other) { + const char *newString = allocFromUTF8(other, strlen(other)); SharedBuffer::bufferFromData(mString)->release(); - mString = allocFromUTF8(other, strlen(other)); + mString = newString; if (mString) return NO_ERROR; mString = getEmptyString(); @@ -311,8 +312,9 @@ status_t String8::setTo(const char* other) status_t String8::setTo(const char* other, size_t len) { + const char *newString = allocFromUTF8(other, len); SharedBuffer::bufferFromData(mString)->release(); - mString = allocFromUTF8(other, len); + mString = newString; if (mString) return NO_ERROR; mString = getEmptyString(); @@ -321,8 +323,9 @@ status_t String8::setTo(const char* other, size_t len) status_t String8::setTo(const char16_t* other, size_t len) { + const char *newString = allocFromUTF16(other, len); SharedBuffer::bufferFromData(mString)->release(); - mString = allocFromUTF16(other, len); + mString = newString; if (mString) return NO_ERROR; mString = getEmptyString(); @@ -331,8 +334,9 @@ status_t String8::setTo(const char16_t* other, size_t len) status_t String8::setTo(const char32_t* other, size_t len) { + const char *newString = allocFromUTF32(other, len); SharedBuffer::bufferFromData(mString)->release(); - mString = allocFromUTF32(other, len); + mString = newString; if (mString) return NO_ERROR; mString = getEmptyString(); diff --git a/libs/utils/VectorImpl.cpp b/libs/utils/VectorImpl.cpp index 0322af7..289c826 100644 --- a/libs/utils/VectorImpl.cpp +++ b/libs/utils/VectorImpl.cpp @@ -108,18 +108,28 @@ size_t VectorImpl::capacity() const ssize_t VectorImpl::insertVectorAt(const VectorImpl& vector, size_t index) { + return insertArrayAt(vector.arrayImpl(), index, vector.size()); +} + +ssize_t VectorImpl::appendVector(const VectorImpl& vector) +{ + return insertVectorAt(vector, size()); +} + +ssize_t VectorImpl::insertArrayAt(const void* array, size_t index, size_t length) +{ if (index > size()) return BAD_INDEX; - void* where = _grow(index, vector.size()); + void* where = _grow(index, length); if (where) { - _do_copy(where, vector.arrayImpl(), vector.size()); + _do_copy(where, array, length); } return where ? index : (ssize_t)NO_MEMORY; } -ssize_t VectorImpl::appendVector(const VectorImpl& vector) +ssize_t VectorImpl::appendArray(const void* array, size_t length) { - return insertVectorAt(vector, size()); + return insertArrayAt(array, size(), length); } ssize_t VectorImpl::insertAt(size_t index, size_t numItems) diff --git a/libs/utils/ZipFileCRO.cpp b/libs/utils/ZipFileCRO.cpp index 45f6c8b..16b219c 100644 --- a/libs/utils/ZipFileCRO.cpp +++ b/libs/utils/ZipFileCRO.cpp @@ -39,8 +39,8 @@ ZipEntryCRO ZipFileCRO_findEntryByName(ZipFileCRO zipToken, } bool ZipFileCRO_getEntryInfo(ZipFileCRO zipToken, ZipEntryRO entryToken, - int* pMethod, long* pUncompLen, - long* pCompLen, off_t* pOffset, long* pModWhen, long* pCrc32) { + int* pMethod, size_t* pUncompLen, + size_t* pCompLen, off_t* pOffset, long* pModWhen, long* pCrc32) { ZipFileRO* zip = (ZipFileRO*)zipToken; ZipEntryRO entry = (ZipEntryRO)entryToken; return zip->getEntryInfo(entry, pMethod, pUncompLen, pCompLen, pOffset, diff --git a/libs/utils/ZipFileRO.cpp b/libs/utils/ZipFileRO.cpp index 6c701dd..28dc512 100644 --- a/libs/utils/ZipFileRO.cpp +++ b/libs/utils/ZipFileRO.cpp @@ -29,6 +29,22 @@ #include <fcntl.h> #include <errno.h> #include <assert.h> +#include <unistd.h> + +/* + * TEMP_FAILURE_RETRY is defined by some, but not all, versions of + * <unistd.h>. (Alas, it is not as standard as we'd hoped!) So, if it's + * not already defined, then define it here. + */ +#ifndef TEMP_FAILURE_RETRY +/* Used to retry syscalls that can return EINTR. */ +#define TEMP_FAILURE_RETRY(exp) ({ \ + typeof (exp) _rc; \ + do { \ + _rc = (exp); \ + } while (_rc == -1 && errno == EINTR); \ + _rc; }) +#endif using namespace android; @@ -38,6 +54,7 @@ using namespace android; #define kEOCDSignature 0x06054b50 #define kEOCDLen 22 #define kEOCDNumEntries 8 // offset to #of entries in file +#define kEOCDSize 12 // size of the central directory #define kEOCDFileOffset 16 // offset to central directory #define kMaxCommentLen 65535 // longest possible in ushort @@ -90,9 +107,8 @@ int ZipFileRO::entryToIndex(const ZipEntryRO entry) const status_t ZipFileRO::open(const char* zipFileName) { int fd = -1; - off_t length; - assert(mFileMap == NULL); + assert(mDirectoryMap == NULL); /* * Open and map the specified file. @@ -103,172 +119,240 @@ status_t ZipFileRO::open(const char* zipFileName) return NAME_NOT_FOUND; } - length = lseek(fd, 0, SEEK_END); - if (length < 0) { + mFileLength = lseek(fd, 0, SEEK_END); + if (mFileLength < kEOCDLen) { 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; + if (mFileName != NULL) { + free(mFileName); } + mFileName = strdup(zipFileName); mFd = fd; /* - * Got it mapped, verify it and create data structures for fast access. + * Find the Central Directory and store its size and number of entries. + */ + if (!mapCentralDirectory()) { + goto bail; + } + + /* + * Verify Central Directory and create data structures for fast access. */ if (!parseZipArchive()) { - mFileMap->release(); - mFileMap = NULL; - return UNKNOWN_ERROR; + goto bail; } return OK; + +bail: + free(mFileName); + mFileName = NULL; + close(fd); + return UNKNOWN_ERROR; } /* * Parse the Zip archive, verifying its contents and initializing internal * data structures. */ -bool ZipFileRO::parseZipArchive(void) +bool ZipFileRO::mapCentralDirectory(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; + size_t readAmount = kMaxEOCDSearch; + if (readAmount > (size_t) mFileLength) + readAmount = mFileLength; + + unsigned char* scanBuf = (unsigned char*) malloc(readAmount); + if (scanBuf == NULL) { + LOGW("couldn't allocate scanBuf: %s", strerror(errno)); + free(scanBuf); + return false; + } /* - * 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). + * Make sure this is a Zip archive. */ - 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; + if (lseek(mFd, 0, SEEK_SET) != 0) { + LOGW("seek to start failed: %s", strerror(errno)); + free(scanBuf); + return false; + } + + ssize_t actual = TEMP_FAILURE_RETRY(read(mFd, scanBuf, sizeof(int32_t))); + if (actual != (ssize_t) sizeof(int32_t)) { + LOGI("couldn't read first signature from zip archive: %s", strerror(errno)); + free(scanBuf); + return false; + } + + { + unsigned int header = get4LE(scanBuf); + if (header == kEOCDSignature) { + LOGI("Found Zip archive, but it looks empty\n"); + free(scanBuf); + return false; + } else if (header != kLFHSignature) { + LOGV("Not a Zip archive (found 0x%08x)\n", val); + free(scanBuf); + return false; + } } /* - * Find the EOCD. We'll find it immediately unless they have a file - * comment. + * Perform the traditional EOCD snipe hunt. + * + * We're searching for the End of Central Directory magic number, + * which appears at the start of the EOCD block. It's followed by + * 18 bytes of EOCD stuff and up to 64KB of archive comment. We + * need to read the last part of the file into a buffer, dig through + * it to find the magic number, parse some values out, and use those + * to determine the extent of the CD. + * + * We start by pulling in the last part of the file. */ - ptr = basePtr + length - kEOCDLen; + off_t searchStart = mFileLength - readAmount; - while (ptr >= basePtr) { - if (*ptr == (kEOCDSignature & 0xff) && get4LE(ptr) == kEOCDSignature) + if (lseek(mFd, searchStart, SEEK_SET) != searchStart) { + LOGW("seek %ld failed: %s\n", (long) searchStart, strerror(errno)); + free(scanBuf); + return false; + } + actual = TEMP_FAILURE_RETRY(read(mFd, scanBuf, readAmount)); + if (actual != (ssize_t) readAmount) { + LOGW("Zip: read %zd failed: %s\n", readAmount, strerror(errno)); + free(scanBuf); + return false; + } + + /* + * Scan backward for the EOCD magic. In an archive without a trailing + * comment, we'll find it on the first try. (We may want to consider + * doing an initial minimal read; if we don't find it, retry with a + * second read as above.) + */ + int i; + for (i = readAmount - kEOCDLen; i >= 0; i--) { + if (scanBuf[i] == 0x50 && get4LE(&scanBuf[i]) == kEOCDSignature) { + LOGV("+++ Found EOCD at buf+%d\n", i); break; - ptr--; + } } - if (ptr < basePtr) { - LOGI("Could not find end-of-central-directory in Zip\n"); - goto bail; + if (i < 0) { + LOGD("Zip: EOCD not found, %s is not zip\n", mFileName); + free(scanBuf); + return false; } + off_t eocdOffset = searchStart + i; + const unsigned char* eocdPtr = scanBuf + i; + + assert(eocdOffset < mFileLength); + /* - * 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.) + * Grab the CD offset and size, and the number of entries in the + * archive. Verify that they look reasonable. */ - numEntries = get2LE(ptr + kEOCDNumEntries); - cdOffset = get4LE(ptr + kEOCDFileOffset); + unsigned int numEntries = get2LE(eocdPtr + kEOCDNumEntries); + unsigned int dirSize = get4LE(eocdPtr + kEOCDSize); + unsigned int dirOffset = get4LE(eocdPtr + kEOCDFileOffset); + + if ((long long) dirOffset + (long long) dirSize > (long long) eocdOffset) { + LOGW("bad offsets (dir %ld, size %u, eocd %ld)\n", + (long) dirOffset, dirSize, (long) eocdOffset); + free(scanBuf); + return false; + } + if (numEntries == 0) { + LOGW("empty archive?\n"); + free(scanBuf); + return false; + } - /* valid offsets are [0,EOCD] */ - unsigned int maxOffset; - maxOffset = (ptr - basePtr) +1; + LOGV("+++ numEntries=%d dirSize=%d dirOffset=%d\n", + numEntries, dirSize, dirOffset); - 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; + mDirectoryMap = new FileMap(); + if (mDirectoryMap == NULL) { + LOGW("Unable to create directory map: %s", strerror(errno)); + free(scanBuf); + return false; } + if (!mDirectoryMap->create(mFileName, mFd, dirOffset, dirSize, true)) { + LOGW("Unable to map '%s' (%zd to %zd): %s\n", mFileName, + dirOffset, dirOffset + dirSize, strerror(errno)); + free(scanBuf); + return false; + } + + mNumEntries = numEntries; + mDirectoryOffset = dirOffset; + + return true; +} + +bool ZipFileRO::parseZipArchive(void) +{ + bool result = false; + const unsigned char* cdPtr = (const unsigned char*) mDirectoryMap->getDataPtr(); + size_t cdLength = mDirectoryMap->getDataLength(); + int numEntries = mNumEntries; + /* * 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); + mHashTableSize = roundUpPower2(1 + (numEntries * 4) / 3); + mHashTable = (HashEntry*) calloc(mHashTableSize, sizeof(HashEntry)); /* * 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; - + const unsigned char* ptr = cdPtr; + for (int i = 0; i < numEntries; i++) { if (get4LE(ptr) != kCDESignature) { LOGW("Missed a central dir sig (at %d)\n", i); goto bail; } - if (ptr + kCDELen > basePtr + length) { + if (ptr + kCDELen > cdPtr + cdLength) { LOGW("Ran off the end (at %d)\n", i); goto bail; } - localHdrOffset = get4LE(ptr + kCDELocalOffset); - CHECK_OFFSET(localHdrOffset); + long localHdrOffset = (long) get4LE(ptr + kCDELocalOffset); + if (localHdrOffset >= mDirectoryOffset) { + LOGW("bad LFH offset %ld at entry %d\n", localHdrOffset, i); + goto bail; + } + + unsigned int fileNameLen, extraLen, commentLen, hash; + 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); + ptr += kCDELen + fileNameLen + extraLen + commentLen; + if ((size_t)(ptr - cdPtr) > cdLength) { + LOGW("bad CD advance (%d vs %zd) at entry %d\n", + (int) (ptr - cdPtr), cdLength, i); goto bail; } - - ptr += kCDELen + fileNameLen + extraLen + commentLen; - CHECK_OFFSET(ptr - basePtr); } - + LOGV("+++ zip good scan %d entries\n", numEntries); result = true; bail: return result; -#undef CHECK_OFFSET } - /* * Simple string hash function for non-null-terminated strings. */ @@ -315,7 +399,7 @@ ZipEntryRO ZipFileRO::findEntryByName(const char* fileName) const memcmp(mHashTable[ent].name, fileName, nameLen) == 0) { /* match */ - return (ZipEntryRO) (ent + kZipEntryAdj); + return (ZipEntryRO)(long)(ent + kZipEntryAdj); } ent = (ent + 1) & (mHashTableSize-1); @@ -354,20 +438,24 @@ ZipEntryRO ZipFileRO::findEntryByIndex(int idx) const * 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 +bool ZipFileRO::getEntryInfo(ZipEntryRO entry, int* pMethod, size_t* pUncompLen, + size_t* pCompLen, off_t* pOffset, long* pModWhen, long* pCrc32) const { - int ent = entryToIndex(entry); + bool ret = false; + + const int ent = entryToIndex(entry); if (ent < 0) return false; + HashEntry hashEntry = mHashTable[ent]; + /* * Recover the start of the central directory entry from the filename - * pointer. + * pointer. The filename is the first entry past the fixed-size data, + * so we can just subtract back from that. */ - const unsigned char* basePtr = (const unsigned char*)mFileMap->getDataPtr(); - const unsigned char* ptr = (const unsigned char*) mHashTable[ent].name; - size_t zipLength = mFileMap->getDataLength(); + const unsigned char* ptr = (const unsigned char*) hashEntry.name; + off_t cdOffset = mDirectoryOffset; ptr -= kCDELen; @@ -380,48 +468,78 @@ bool ZipFileRO::getEntryInfo(ZipEntryRO entry, int* pMethod, long* pUncompLen, if (pCrc32 != NULL) *pCrc32 = get4LE(ptr + kCDECRC); + size_t compLen = get4LE(ptr + kCDECompLen); + if (pCompLen != NULL) + *pCompLen = compLen; + size_t uncompLen = get4LE(ptr + kCDEUncompLen); + if (pUncompLen != NULL) + *pUncompLen = uncompLen; + /* - * 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. + * If requested, determine the offset of the start of the data. All we + * have is the offset to the Local File Header, which is variable size, + * so we have to read the contents of the struct to figure out where + * the actual data starts. + * + * We also 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. + * + * Note we don't verify compLen/uncompLen if they don't request the + * dataOffset, because dataOffset is expensive to determine. However, + * if they don't have the file offset, they're not likely to be doing + * anything with the contents. */ - 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 (pOffset != NULL) { + long localHdrOffset = get4LE(ptr + kCDELocalOffset); + if (localHdrOffset + kLFHLen >= cdOffset) { + LOGE("ERROR: bad local hdr 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"); + unsigned char lfhBuf[kLFHLen]; + if (lseek(mFd, localHdrOffset, SEEK_SET) != localHdrOffset) { + LOGW("failed seeking to lfh at offset %ld\n", localHdrOffset); return false; } - } - if (pUncompLen != NULL) { - *pUncompLen = get4LE(ptr + kCDEUncompLen); - if (*pUncompLen < 0) { - LOGE("ERROR: negative uncompressed length in zip\n"); + ssize_t actual = + TEMP_FAILURE_RETRY(read(mFd, lfhBuf, sizeof(lfhBuf))); + if (actual != sizeof(lfhBuf)) { + LOGW("failed reading lfh from offset %ld\n", localHdrOffset); + return false; + } + + if (get4LE(lfhBuf) != kLFHSignature) { + LOGW("didn't find signature at start of lfh, offset=%ld\n", + localHdrOffset); return false; } + + off_t dataOffset = localHdrOffset + kLFHLen + + get2LE(lfhBuf + kLFHNameLen) + get2LE(lfhBuf + kLFHExtraLen); + if (dataOffset >= cdOffset) { + LOGW("bad data offset %ld in zip\n", (long) dataOffset); + return false; + } + + /* check lengths */ + if ((off_t)(dataOffset + compLen) > cdOffset) { + LOGW("bad compressed length in zip (%ld + %zd > %ld)\n", + (long) dataOffset, compLen, (long) cdOffset); + return false; + } + if (method == kCompressStored && - (size_t)(dataOffset + *pUncompLen) >= zipLength) + (off_t)(dataOffset + uncompLen) > cdOffset) { - LOGE("ERROR: bad uncompressed length in zip\n"); + LOGE("ERROR: bad uncompressed length in zip (%ld + %zd > %ld)\n", + (long) dataOffset, uncompLen, (long) cdOffset); return false; } - } - if (pOffset != NULL) { *pOffset = dataOffset; } + return true; } @@ -457,14 +575,14 @@ FileMap* ZipFileRO::createEntryFileMap(ZipEntryRO entry) const */ FileMap* newMap; - long compLen; + size_t 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)) { + if (!newMap->create(mFileName, mFd, offset, compLen, true)) { newMap->release(); return NULL; } @@ -480,19 +598,26 @@ FileMap* ZipFileRO::createEntryFileMap(ZipEntryRO entry) const */ bool ZipFileRO::uncompressEntry(ZipEntryRO entry, void* buffer) const { - const int kSequentialMin = 32768; + const size_t 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; + size_t uncompLen, compLen; off_t offset; + const unsigned char* ptr; getEntryInfo(entry, &method, &uncompLen, &compLen, &offset, NULL, NULL); + FileMap* file = createEntryFileMap(entry); + if (file == NULL) { + goto bail; + } + + ptr = (const unsigned char*) file->getDataPtr(); + /* * 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 @@ -507,17 +632,17 @@ bool ZipFileRO::uncompressEntry(ZipEntryRO entry, void* buffer) const * pair of system calls are negated by a reduction in page faults. */ if (compLen > kSequentialMin) - mFileMap->advise(FileMap::SEQUENTIAL); + file->advise(FileMap::SEQUENTIAL); if (method == kCompressStored) { - memcpy(buffer, basePtr + offset, uncompLen); + memcpy(buffer, ptr, uncompLen); } else { - if (!inflateBuffer(buffer, basePtr + offset, uncompLen, compLen)) + if (!inflateBuffer(buffer, ptr, uncompLen, compLen)) goto bail; } if (compLen > kSequentialMin) - mFileMap->advise(FileMap::NORMAL); + file->advise(FileMap::NORMAL); result = true; @@ -537,29 +662,34 @@ bool ZipFileRO::uncompressEntry(ZipEntryRO entry, int fd) const if (ent < 0) return -1; - const unsigned char* basePtr = (const unsigned char*)mFileMap->getDataPtr(); int method; - long uncompLen, compLen; + size_t uncompLen, compLen; off_t offset; + const unsigned char* ptr; getEntryInfo(entry, &method, &uncompLen, &compLen, &offset, NULL, NULL); - if (method == kCompressStored) { - ssize_t actual; + const FileMap* file = createEntryFileMap(entry); + if (file == NULL) { + goto bail; + } + + ptr = (const unsigned char*) file->getDataPtr(); - actual = write(fd, basePtr + offset, uncompLen); + if (method == kCompressStored) { + ssize_t actual = write(fd, ptr, 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); + } else if ((size_t) actual != uncompLen) { + LOGE("Partial write during uncompress (%zd of %zd)\n", + actual, uncompLen); goto bail; } else { LOGI("+++ successful write\n"); } } else { - if (!inflateBuffer(fd, basePtr+offset, uncompLen, compLen)) + if (!inflateBuffer(fd, ptr, uncompLen, compLen)) goto bail; } @@ -573,7 +703,7 @@ bail: * Uncompress "deflate" data from one buffer to another. */ /*static*/ bool ZipFileRO::inflateBuffer(void* outBuf, const void* inBuf, - long uncompLen, long compLen) + size_t uncompLen, size_t compLen) { bool result = false; z_stream zstream; @@ -582,7 +712,7 @@ bail: /* * Initialize the zlib stream struct. */ - memset(&zstream, 0, sizeof(zstream)); + memset(&zstream, 0, sizeof(zstream)); zstream.zalloc = Z_NULL; zstream.zfree = Z_NULL; zstream.opaque = Z_NULL; @@ -592,10 +722,10 @@ bail: 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. - */ + /* + * 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) { @@ -619,8 +749,8 @@ bail: } /* paranoia */ - if ((long) zstream.total_out != uncompLen) { - LOGW("Size mismatch on inflated file (%ld vs %ld)\n", + if (zstream.total_out != uncompLen) { + LOGW("Size mismatch on inflated file (%ld vs %zd)\n", zstream.total_out, uncompLen); goto z_bail; } @@ -638,10 +768,10 @@ bail: * Uncompress "deflate" data from one buffer to an open file descriptor. */ /*static*/ bool ZipFileRO::inflateBuffer(int fd, const void* inBuf, - long uncompLen, long compLen) + size_t uncompLen, size_t compLen) { bool result = false; - const int kWriteBufSize = 32768; + const size_t kWriteBufSize = 32768; unsigned char writeBuf[kWriteBufSize]; z_stream zstream; int zerr; @@ -649,7 +779,7 @@ bail: /* * Initialize the zlib stream struct. */ - memset(&zstream, 0, sizeof(zstream)); + memset(&zstream, 0, sizeof(zstream)); zstream.zalloc = Z_NULL; zstream.zfree = Z_NULL; zstream.opaque = Z_NULL; @@ -659,10 +789,10 @@ bail: 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. - */ + /* + * 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) { @@ -708,8 +838,8 @@ bail: 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", + if (zstream.total_out != uncompLen) { + LOGW("Size mismatch on inflated file (%ld vs %zd)\n", zstream.total_out, uncompLen); goto z_bail; } diff --git a/libs/utils/tests/Android.mk b/libs/utils/tests/Android.mk new file mode 100644 index 0000000..92ebfd7 --- /dev/null +++ b/libs/utils/tests/Android.mk @@ -0,0 +1,38 @@ +# Build the unit tests. +LOCAL_PATH := $(call my-dir) +include $(CLEAR_VARS) + +test_src_files := \ + PollLoop_test.cpp + +shared_libraries := \ + libz \ + liblog \ + libcutils \ + libutils \ + libstlport + +static_libraries := \ + libgtest \ + libgtest_main + +c_includes := \ + external/zlib \ + external/icu4c/common \ + bionic \ + bionic/libstdc++/include \ + external/gtest/include \ + external/stlport/stlport + +module_tags := eng tests + +$(foreach file,$(test_src_files), \ + $(eval include $(CLEAR_VARS)) \ + $(eval LOCAL_SHARED_LIBRARIES := $(shared_libraries)) \ + $(eval LOCAL_STATIC_LIBRARIES := $(static_libraries)) \ + $(eval LOCAL_C_INCLUDES := $(c_includes)) \ + $(eval LOCAL_SRC_FILES := $(file)) \ + $(eval LOCAL_MODULE := $(notdir $(file:%.cpp=%))) \ + $(eval LOCAL_MODULE_TAGS := $(module_tags)) \ + $(eval include $(BUILD_EXECUTABLE)) \ +) diff --git a/libs/utils/tests/PollLoop_test.cpp b/libs/utils/tests/PollLoop_test.cpp new file mode 100644 index 0000000..4848c0f --- /dev/null +++ b/libs/utils/tests/PollLoop_test.cpp @@ -0,0 +1,370 @@ +// +// Copyright 2010 The Android Open Source Project +// + +#include <utils/PollLoop.h> +#include <utils/Timers.h> +#include <utils/StopWatch.h> +#include <gtest/gtest.h> +#include <unistd.h> +#include <time.h> + +#include "TestHelpers.h" + +// # of milliseconds to fudge stopwatch measurements +#define TIMING_TOLERANCE_MS 25 + +namespace android { + +class DelayedWake : public DelayedTask { + sp<PollLoop> mPollLoop; + +public: + DelayedWake(int delayMillis, const sp<PollLoop> pollLoop) : + DelayedTask(delayMillis), mPollLoop(pollLoop) { + } + +protected: + virtual void doTask() { + mPollLoop->wake(); + } +}; + +class DelayedWriteSignal : public DelayedTask { + Pipe* mPipe; + +public: + DelayedWriteSignal(int delayMillis, Pipe* pipe) : + DelayedTask(delayMillis), mPipe(pipe) { + } + +protected: + virtual void doTask() { + mPipe->writeSignal(); + } +}; + +class CallbackHandler { +public: + void setCallback(const sp<PollLoop>& pollLoop, int fd, int events) { + pollLoop->setCallback(fd, events, staticHandler, this); + } + +protected: + virtual ~CallbackHandler() { } + + virtual bool handler(int fd, int events) = 0; + +private: + static bool staticHandler(int fd, int events, void* data) { + return static_cast<CallbackHandler*>(data)->handler(fd, events); + } +}; + +class StubCallbackHandler : public CallbackHandler { +public: + bool nextResult; + int callbackCount; + + int fd; + int events; + + StubCallbackHandler(bool nextResult) : nextResult(nextResult), + callbackCount(0), fd(-1), events(-1) { + } + +protected: + virtual bool handler(int fd, int events) { + callbackCount += 1; + this->fd = fd; + this->events = events; + return nextResult; + } +}; + +class PollLoopTest : public testing::Test { +protected: + sp<PollLoop> mPollLoop; + + virtual void SetUp() { + mPollLoop = new PollLoop(); + } + + virtual void TearDown() { + mPollLoop.clear(); + } +}; + + +TEST_F(PollLoopTest, PollOnce_WhenNonZeroTimeoutAndNotAwoken_WaitsForTimeoutAndReturnsFalse) { + StopWatch stopWatch("pollOnce"); + bool result = mPollLoop->pollOnce(100); + int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime()); + + EXPECT_NEAR(100, elapsedMillis, TIMING_TOLERANCE_MS) + << "elapsed time should approx. equal timeout"; + EXPECT_FALSE(result) + << "pollOnce result should be false because timeout occurred"; +} + +TEST_F(PollLoopTest, PollOnce_WhenNonZeroTimeoutAndAwokenBeforeWaiting_ImmediatelyReturnsTrue) { + mPollLoop->wake(); + + StopWatch stopWatch("pollOnce"); + bool result = mPollLoop->pollOnce(1000); + int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime()); + + EXPECT_NEAR(0, elapsedMillis, TIMING_TOLERANCE_MS) + << "elapsed time should approx. zero because wake() was called before waiting"; + EXPECT_TRUE(result) + << "pollOnce result should be true because loop was awoken"; +} + +TEST_F(PollLoopTest, PollOnce_WhenNonZeroTimeoutAndAwokenWhileWaiting_PromptlyReturnsTrue) { + sp<DelayedWake> delayedWake = new DelayedWake(100, mPollLoop); + delayedWake->run(); + + StopWatch stopWatch("pollOnce"); + bool result = mPollLoop->pollOnce(1000); + int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime()); + + EXPECT_NEAR(100, elapsedMillis, TIMING_TOLERANCE_MS) + << "elapsed time should approx. equal wake delay"; + EXPECT_TRUE(result) + << "pollOnce result should be true because loop was awoken"; +} + +TEST_F(PollLoopTest, PollOnce_WhenZeroTimeoutAndNoRegisteredFDs_ImmediatelyReturnsFalse) { + StopWatch stopWatch("pollOnce"); + bool result = mPollLoop->pollOnce(0); + int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime()); + + EXPECT_NEAR(0, elapsedMillis, TIMING_TOLERANCE_MS) + << "elapsed time should be approx. zero"; + EXPECT_FALSE(result) + << "pollOnce result should be false because timeout occurred"; +} + +TEST_F(PollLoopTest, PollOnce_WhenZeroTimeoutAndNoSignalledFDs_ImmediatelyReturnsFalse) { + Pipe pipe; + StubCallbackHandler handler(true); + + handler.setCallback(mPollLoop, pipe.receiveFd, POLL_IN); + + StopWatch stopWatch("pollOnce"); + bool result = mPollLoop->pollOnce(0); + int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime()); + + EXPECT_NEAR(0, elapsedMillis, TIMING_TOLERANCE_MS) + << "elapsed time should be approx. zero"; + EXPECT_FALSE(result) + << "pollOnce result should be false because timeout occurred"; + EXPECT_EQ(0, handler.callbackCount) + << "callback should not have been invoked because FD was not signalled"; +} + +TEST_F(PollLoopTest, PollOnce_WhenZeroTimeoutAndSignalledFD_ImmediatelyInvokesCallbackAndReturnsTrue) { + Pipe pipe; + StubCallbackHandler handler(true); + + ASSERT_EQ(OK, pipe.writeSignal()); + handler.setCallback(mPollLoop, pipe.receiveFd, POLL_IN); + + StopWatch stopWatch("pollOnce"); + bool result = mPollLoop->pollOnce(0); + int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime()); + + EXPECT_NEAR(0, elapsedMillis, TIMING_TOLERANCE_MS) + << "elapsed time should be approx. zero"; + EXPECT_TRUE(result) + << "pollOnce result should be true because FD was signalled"; + EXPECT_EQ(1, handler.callbackCount) + << "callback should be invoked exactly once"; + EXPECT_EQ(pipe.receiveFd, handler.fd) + << "callback should have received pipe fd as parameter"; + EXPECT_EQ(POLL_IN, handler.events) + << "callback should have received POLL_IN as events"; +} + +TEST_F(PollLoopTest, PollOnce_WhenNonZeroTimeoutAndNoSignalledFDs_WaitsForTimeoutAndReturnsFalse) { + Pipe pipe; + StubCallbackHandler handler(true); + + handler.setCallback(mPollLoop, pipe.receiveFd, POLL_IN); + + StopWatch stopWatch("pollOnce"); + bool result = mPollLoop->pollOnce(100); + int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime()); + + EXPECT_NEAR(100, elapsedMillis, TIMING_TOLERANCE_MS) + << "elapsed time should approx. equal timeout"; + EXPECT_FALSE(result) + << "pollOnce result should be false because timeout occurred"; + EXPECT_EQ(0, handler.callbackCount) + << "callback should not have been invoked because FD was not signalled"; +} + +TEST_F(PollLoopTest, PollOnce_WhenNonZeroTimeoutAndSignalledFDBeforeWaiting_ImmediatelyInvokesCallbackAndReturnsTrue) { + Pipe pipe; + StubCallbackHandler handler(true); + + pipe.writeSignal(); + handler.setCallback(mPollLoop, pipe.receiveFd, POLL_IN); + + StopWatch stopWatch("pollOnce"); + bool result = mPollLoop->pollOnce(100); + int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime()); + + ASSERT_EQ(OK, pipe.readSignal()) + << "signal should actually have been written"; + EXPECT_NEAR(0, elapsedMillis, TIMING_TOLERANCE_MS) + << "elapsed time should be approx. zero"; + EXPECT_TRUE(result) + << "pollOnce result should be true because FD was signalled"; + EXPECT_EQ(1, handler.callbackCount) + << "callback should be invoked exactly once"; + EXPECT_EQ(pipe.receiveFd, handler.fd) + << "callback should have received pipe fd as parameter"; + EXPECT_EQ(POLL_IN, handler.events) + << "callback should have received POLL_IN as events"; +} + +TEST_F(PollLoopTest, PollOnce_WhenNonZeroTimeoutAndSignalledFDWhileWaiting_PromptlyInvokesCallbackAndReturnsTrue) { + Pipe pipe; + StubCallbackHandler handler(true); + sp<DelayedWriteSignal> delayedWriteSignal = new DelayedWriteSignal(100, & pipe); + + handler.setCallback(mPollLoop, pipe.receiveFd, POLL_IN); + delayedWriteSignal->run(); + + StopWatch stopWatch("pollOnce"); + bool result = mPollLoop->pollOnce(1000); + int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime()); + + ASSERT_EQ(OK, pipe.readSignal()) + << "signal should actually have been written"; + EXPECT_NEAR(100, elapsedMillis, TIMING_TOLERANCE_MS) + << "elapsed time should approx. equal signal delay"; + EXPECT_TRUE(result) + << "pollOnce result should be true because FD was signalled"; + EXPECT_EQ(1, handler.callbackCount) + << "callback should be invoked exactly once"; + EXPECT_EQ(pipe.receiveFd, handler.fd) + << "callback should have received pipe fd as parameter"; + EXPECT_EQ(POLL_IN, handler.events) + << "callback should have received POLL_IN as events"; +} + +TEST_F(PollLoopTest, PollOnce_WhenCallbackAddedThenRemoved_CallbackShouldNotBeInvoked) { + Pipe pipe; + StubCallbackHandler handler(true); + + handler.setCallback(mPollLoop, pipe.receiveFd, POLL_IN); + pipe.writeSignal(); // would cause FD to be considered signalled + mPollLoop->removeCallback(pipe.receiveFd); + + StopWatch stopWatch("pollOnce"); + bool result = mPollLoop->pollOnce(100); + int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime()); + + ASSERT_EQ(OK, pipe.readSignal()) + << "signal should actually have been written"; + EXPECT_NEAR(100, elapsedMillis, TIMING_TOLERANCE_MS) + << "elapsed time should approx. equal timeout because FD was no longer registered"; + EXPECT_FALSE(result) + << "pollOnce result should be false because timeout occurred"; + EXPECT_EQ(0, handler.callbackCount) + << "callback should not be invoked"; +} + +TEST_F(PollLoopTest, PollOnce_WhenCallbackReturnsFalse_CallbackShouldNotBeInvokedAgainLater) { + Pipe pipe; + StubCallbackHandler handler(false); + + handler.setCallback(mPollLoop, pipe.receiveFd, POLL_IN); + + // First loop: Callback is registered and FD is signalled. + pipe.writeSignal(); + + StopWatch stopWatch("pollOnce"); + bool result = mPollLoop->pollOnce(0); + int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime()); + + ASSERT_EQ(OK, pipe.readSignal()) + << "signal should actually have been written"; + EXPECT_NEAR(0, elapsedMillis, TIMING_TOLERANCE_MS) + << "elapsed time should approx. equal zero because FD was already signalled"; + EXPECT_TRUE(result) + << "pollOnce result should be true because FD was signalled"; + EXPECT_EQ(1, handler.callbackCount) + << "callback should be invoked"; + + // Second loop: Callback is no longer registered and FD is signalled. + pipe.writeSignal(); + + stopWatch.reset(); + result = mPollLoop->pollOnce(0); + elapsedMillis = ns2ms(stopWatch.elapsedTime()); + + ASSERT_EQ(OK, pipe.readSignal()) + << "signal should actually have been written"; + EXPECT_NEAR(0, elapsedMillis, TIMING_TOLERANCE_MS) + << "elapsed time should approx. equal zero because timeout was zero"; + EXPECT_FALSE(result) + << "pollOnce result should be false because timeout occurred"; + EXPECT_EQ(1, handler.callbackCount) + << "callback should not be invoked this time"; +} + +TEST_F(PollLoopTest, RemoveCallback_WhenCallbackNotAdded_ReturnsFalse) { + bool result = mPollLoop->removeCallback(1); + + EXPECT_FALSE(result) + << "removeCallback should return false because FD not registered"; +} + +TEST_F(PollLoopTest, RemoveCallback_WhenCallbackAddedThenRemovedTwice_ReturnsTrueFirstTimeAndReturnsFalseSecondTime) { + Pipe pipe; + StubCallbackHandler handler(false); + handler.setCallback(mPollLoop, pipe.receiveFd, POLL_IN); + + // First time. + bool result = mPollLoop->removeCallback(pipe.receiveFd); + + EXPECT_TRUE(result) + << "removeCallback should return true first time because FD was registered"; + + // Second time. + result = mPollLoop->removeCallback(pipe.receiveFd); + + EXPECT_FALSE(result) + << "removeCallback should return false second time because FD was no longer registered"; +} + +TEST_F(PollLoopTest, PollOnce_WhenCallbackAddedTwice_OnlySecondCallbackShouldBeInvoked) { + Pipe pipe; + StubCallbackHandler handler1(true); + StubCallbackHandler handler2(true); + + handler1.setCallback(mPollLoop, pipe.receiveFd, POLL_IN); + handler2.setCallback(mPollLoop, pipe.receiveFd, POLL_IN); // replace it + pipe.writeSignal(); // would cause FD to be considered signalled + + StopWatch stopWatch("pollOnce"); + bool result = mPollLoop->pollOnce(100); + int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime()); + + ASSERT_EQ(OK, pipe.readSignal()) + << "signal should actually have been written"; + EXPECT_NEAR(0, elapsedMillis, TIMING_TOLERANCE_MS) + << "elapsed time should approx. zero because FD was already signalled"; + EXPECT_TRUE(result) + << "pollOnce result should be true because FD was signalled"; + EXPECT_EQ(0, handler1.callbackCount) + << "original handler callback should not be invoked because it was replaced"; + EXPECT_EQ(1, handler2.callbackCount) + << "replacement handler callback should be invoked"; +} + + +} // namespace android diff --git a/libs/utils/tests/TestHelpers.h b/libs/utils/tests/TestHelpers.h new file mode 100644 index 0000000..d8e985e --- /dev/null +++ b/libs/utils/tests/TestHelpers.h @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2010 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 TESTHELPERS_H +#define TESTHELPERS_H + +#include <utils/threads.h> + +namespace android { + +class Pipe { +public: + int sendFd; + int receiveFd; + + Pipe() { + int fds[2]; + ::pipe(fds); + + receiveFd = fds[0]; + sendFd = fds[1]; + } + + ~Pipe() { + if (sendFd != -1) { + ::close(sendFd); + } + + if (receiveFd != -1) { + ::close(receiveFd); + } + } + + status_t writeSignal() { + ssize_t nWritten = ::write(sendFd, "*", 1); + return nWritten == 1 ? 0 : -errno; + } + + status_t readSignal() { + char buf[1]; + ssize_t nRead = ::read(receiveFd, buf, 1); + return nRead == 1 ? 0 : nRead == 0 ? -EPIPE : -errno; + } +}; + +class DelayedTask : public Thread { + int mDelayMillis; + +public: + DelayedTask(int delayMillis) : mDelayMillis(delayMillis) { } + +protected: + virtual ~DelayedTask() { } + + virtual void doTask() = 0; + + virtual bool threadLoop() { + usleep(mDelayMillis * 1000); + doTask(); + return false; + } +}; + +} // namespace android + +#endif // TESTHELPERS_H |