|  | /* | 
|  | * 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 "selector" | 
|  |  | 
|  | #include <assert.h> | 
|  | #include <errno.h> | 
|  | #include <pthread.h> | 
|  | #include <stdlib.h> | 
|  | #include <string.h> | 
|  | #include <sys/types.h> | 
|  | #include <unistd.h> | 
|  |  | 
|  | #include <cutils/array.h> | 
|  | #include <cutils/selector.h> | 
|  |  | 
|  | #include "loghack.h" | 
|  |  | 
|  | struct Selector { | 
|  | Array* selectableFds; | 
|  | bool looping; | 
|  | fd_set readFds; | 
|  | fd_set writeFds; | 
|  | fd_set exceptFds; | 
|  | int maxFd; | 
|  | int wakeupPipe[2]; | 
|  | SelectableFd* wakeupFd; | 
|  |  | 
|  | bool inSelect; | 
|  | pthread_mutex_t inSelectLock; | 
|  | }; | 
|  |  | 
|  | /** Reads and ignores wake up data. */ | 
|  | static void eatWakeupData(SelectableFd* wakeupFd) { | 
|  | static char garbage[64]; | 
|  | if (read(wakeupFd->fd, garbage, sizeof(garbage)) < 0) { | 
|  | if (errno == EINTR) { | 
|  | LOGI("read() interrupted."); | 
|  | } else { | 
|  | LOG_ALWAYS_FATAL("This should never happen: %s", strerror(errno)); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | static void setInSelect(Selector* selector, bool inSelect) { | 
|  | pthread_mutex_lock(&selector->inSelectLock); | 
|  | selector->inSelect = inSelect; | 
|  | pthread_mutex_unlock(&selector->inSelectLock); | 
|  | } | 
|  |  | 
|  | static bool isInSelect(Selector* selector) { | 
|  | pthread_mutex_lock(&selector->inSelectLock); | 
|  | bool inSelect = selector->inSelect; | 
|  | pthread_mutex_unlock(&selector->inSelectLock); | 
|  | return inSelect; | 
|  | } | 
|  |  | 
|  | void selectorWakeUp(Selector* selector) { | 
|  | if (!isInSelect(selector)) { | 
|  | // We only need to write wake-up data if we're blocked in select(). | 
|  | return; | 
|  | } | 
|  |  | 
|  | static char garbage[1]; | 
|  | if (write(selector->wakeupPipe[1], garbage, sizeof(garbage)) < 0) { | 
|  | if (errno == EINTR) { | 
|  | LOGI("read() interrupted."); | 
|  | } else { | 
|  | LOG_ALWAYS_FATAL("This should never happen: %s", strerror(errno)); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | Selector* selectorCreate(void) { | 
|  | Selector* selector = calloc(1, sizeof(Selector)); | 
|  | if (selector == NULL) { | 
|  | LOG_ALWAYS_FATAL("malloc() error."); | 
|  | } | 
|  | selector->selectableFds = arrayCreate(); | 
|  |  | 
|  | // Set up wake-up pipe. | 
|  | if (pipe(selector->wakeupPipe) < 0) { | 
|  | LOG_ALWAYS_FATAL("pipe() error: %s", strerror(errno)); | 
|  | } | 
|  |  | 
|  | LOGD("Wakeup fd: %d", selector->wakeupPipe[0]); | 
|  |  | 
|  | SelectableFd* wakeupFd = selectorAdd(selector, selector->wakeupPipe[0]); | 
|  | if (wakeupFd == NULL) { | 
|  | LOG_ALWAYS_FATAL("malloc() error."); | 
|  | } | 
|  | wakeupFd->onReadable = &eatWakeupData; | 
|  |  | 
|  | pthread_mutex_init(&selector->inSelectLock, NULL); | 
|  |  | 
|  | return selector; | 
|  | } | 
|  |  | 
|  | SelectableFd* selectorAdd(Selector* selector, int fd) { | 
|  | assert(selector != NULL); | 
|  |  | 
|  | SelectableFd* selectableFd = calloc(1, sizeof(SelectableFd)); | 
|  | if (selectableFd != NULL) { | 
|  | selectableFd->selector = selector; | 
|  | selectableFd->fd = fd; | 
|  |  | 
|  | arrayAdd(selector->selectableFds, selectableFd); | 
|  | } | 
|  |  | 
|  | return selectableFd; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Adds an fd to the given set if the callback is non-null. Returns true | 
|  | * if the fd was added. | 
|  | */ | 
|  | static inline bool maybeAdd(SelectableFd* selectableFd, | 
|  | void (*callback)(SelectableFd*), fd_set* fdSet) { | 
|  | if (callback != NULL) { | 
|  | FD_SET(selectableFd->fd, fdSet); | 
|  | return true; | 
|  | } | 
|  | return false; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Removes stale file descriptors and initializes file descriptor sets. | 
|  | */ | 
|  | static void prepareForSelect(Selector* selector) { | 
|  | fd_set* exceptFds = &selector->exceptFds; | 
|  | fd_set* readFds = &selector->readFds; | 
|  | fd_set* writeFds = &selector->writeFds; | 
|  |  | 
|  | FD_ZERO(exceptFds); | 
|  | FD_ZERO(readFds); | 
|  | FD_ZERO(writeFds); | 
|  |  | 
|  | Array* selectableFds = selector->selectableFds; | 
|  | int i = 0; | 
|  | selector->maxFd = 0; | 
|  | int size = arraySize(selectableFds); | 
|  | while (i < size) { | 
|  | SelectableFd* selectableFd = arrayGet(selectableFds, i); | 
|  | if (selectableFd->remove) { | 
|  | // This descriptor should be removed. | 
|  | arrayRemove(selectableFds, i); | 
|  | size--; | 
|  | if (selectableFd->onRemove != NULL) { | 
|  | selectableFd->onRemove(selectableFd); | 
|  | } | 
|  | free(selectableFd); | 
|  | } else { | 
|  | if (selectableFd->beforeSelect != NULL) { | 
|  | selectableFd->beforeSelect(selectableFd); | 
|  | } | 
|  |  | 
|  | bool inSet = false; | 
|  | if (maybeAdd(selectableFd, selectableFd->onExcept, exceptFds)) { | 
|  | LOGD("Selecting fd %d for writing...", selectableFd->fd); | 
|  | inSet = true; | 
|  | } | 
|  | if (maybeAdd(selectableFd, selectableFd->onReadable, readFds)) { | 
|  | LOGD("Selecting fd %d for reading...", selectableFd->fd); | 
|  | inSet = true; | 
|  | } | 
|  | if (maybeAdd(selectableFd, selectableFd->onWritable, writeFds)) { | 
|  | inSet = true; | 
|  | } | 
|  |  | 
|  | if (inSet) { | 
|  | // If the fd is in a set, check it against max. | 
|  | int fd = selectableFd->fd; | 
|  | if (fd > selector->maxFd) { | 
|  | selector->maxFd = fd; | 
|  | } | 
|  | } | 
|  |  | 
|  | // Move to next descriptor. | 
|  | i++; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Invokes a callback if the callback is non-null and the fd is in the given | 
|  | * set. | 
|  | */ | 
|  | static inline void maybeInvoke(SelectableFd* selectableFd, | 
|  | void (*callback)(SelectableFd*), fd_set* fdSet) { | 
|  | if (callback != NULL && !selectableFd->remove && | 
|  | FD_ISSET(selectableFd->fd, fdSet)) { | 
|  | LOGD("Selected fd %d.", selectableFd->fd); | 
|  | callback(selectableFd); | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Notifies user if file descriptors are readable or writable, or if | 
|  | * out-of-band data is present. | 
|  | */ | 
|  | static void fireEvents(Selector* selector) { | 
|  | Array* selectableFds = selector->selectableFds; | 
|  | int size = arraySize(selectableFds); | 
|  | int i; | 
|  | for (i = 0; i < size; i++) { | 
|  | SelectableFd* selectableFd = arrayGet(selectableFds, i); | 
|  | maybeInvoke(selectableFd, selectableFd->onExcept, | 
|  | &selector->exceptFds); | 
|  | maybeInvoke(selectableFd, selectableFd->onReadable, | 
|  | &selector->readFds); | 
|  | maybeInvoke(selectableFd, selectableFd->onWritable, | 
|  | &selector->writeFds); | 
|  | } | 
|  | } | 
|  |  | 
|  | void selectorLoop(Selector* selector) { | 
|  | // Make sure we're not already looping. | 
|  | if (selector->looping) { | 
|  | LOG_ALWAYS_FATAL("Already looping."); | 
|  | } | 
|  | selector->looping = true; | 
|  |  | 
|  | while (true) { | 
|  | setInSelect(selector, true); | 
|  |  | 
|  | prepareForSelect(selector); | 
|  |  | 
|  | LOGD("Entering select()."); | 
|  |  | 
|  | // Select file descriptors. | 
|  | int result = select(selector->maxFd + 1, &selector->readFds, | 
|  | &selector->writeFds, &selector->exceptFds, NULL); | 
|  |  | 
|  | LOGD("Exiting select()."); | 
|  |  | 
|  | setInSelect(selector, false); | 
|  |  | 
|  | if (result == -1) { | 
|  | // Abort on everything except EINTR. | 
|  | if (errno == EINTR) { | 
|  | LOGI("select() interrupted."); | 
|  | } else { | 
|  | LOG_ALWAYS_FATAL("select() error: %s", | 
|  | strerror(errno)); | 
|  | } | 
|  | } else if (result > 0) { | 
|  | fireEvents(selector); | 
|  | } | 
|  | } | 
|  | } |