1/*
2 * Copyright (C) 2007 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#define LOG_TAG "selector"
18
19#include <assert.h>
20#include <errno.h>
21#include <pthread.h>
22#include <stdlib.h>
23#include <string.h>
24#include <sys/types.h>
25#include <unistd.h>
26
27#include <cutils/array.h>
28#include <cutils/selector.h>
29
30#include "loghack.h"
31
32struct Selector {
33    Array* selectableFds;
34    bool looping;
35    fd_set readFds;
36    fd_set writeFds;
37    fd_set exceptFds;
38    int maxFd;
39    int wakeupPipe[2];
40    SelectableFd* wakeupFd;
41
42    bool inSelect;
43    pthread_mutex_t inSelectLock;
44};
45
46/** Reads and ignores wake up data. */
47static void eatWakeupData(SelectableFd* wakeupFd) {
48    static char garbage[64];
49    if (read(wakeupFd->fd, garbage, sizeof(garbage)) < 0) {
50        if (errno == EINTR) {
51            ALOGI("read() interrupted.");
52        } else {
53            LOG_ALWAYS_FATAL("This should never happen: %s", strerror(errno));
54        }
55    }
56}
57
58static void setInSelect(Selector* selector, bool inSelect) {
59    pthread_mutex_lock(&selector->inSelectLock);
60    selector->inSelect = inSelect;
61    pthread_mutex_unlock(&selector->inSelectLock);
62}
63
64static bool isInSelect(Selector* selector) {
65    pthread_mutex_lock(&selector->inSelectLock);
66    bool inSelect = selector->inSelect;
67    pthread_mutex_unlock(&selector->inSelectLock);
68    return inSelect;
69}
70
71void selectorWakeUp(Selector* selector) {
72    if (!isInSelect(selector)) {
73        // We only need to write wake-up data if we're blocked in select().
74        return;
75    }
76
77    static char garbage[1];
78    if (write(selector->wakeupPipe[1], garbage, sizeof(garbage)) < 0) {
79        if (errno == EINTR) {
80            ALOGI("read() interrupted.");
81        } else {
82            LOG_ALWAYS_FATAL("This should never happen: %s", strerror(errno));
83        }
84    }
85}
86
87Selector* selectorCreate(void) {
88    Selector* selector = calloc(1, sizeof(Selector));
89    if (selector == NULL) {
90        LOG_ALWAYS_FATAL("malloc() error.");
91    }
92    selector->selectableFds = arrayCreate();
93
94    // Set up wake-up pipe.
95    if (pipe(selector->wakeupPipe) < 0) {
96        LOG_ALWAYS_FATAL("pipe() error: %s", strerror(errno));
97    }
98
99    ALOGD("Wakeup fd: %d", selector->wakeupPipe[0]);
100
101    SelectableFd* wakeupFd = selectorAdd(selector, selector->wakeupPipe[0]);
102    if (wakeupFd == NULL) {
103        LOG_ALWAYS_FATAL("malloc() error.");
104    }
105    wakeupFd->onReadable = &eatWakeupData;
106
107    pthread_mutex_init(&selector->inSelectLock, NULL);
108
109    return selector;
110}
111
112SelectableFd* selectorAdd(Selector* selector, int fd) {
113    assert(selector != NULL);
114
115    SelectableFd* selectableFd = calloc(1, sizeof(SelectableFd));
116    if (selectableFd != NULL) {
117        selectableFd->selector = selector;
118        selectableFd->fd = fd;
119
120        arrayAdd(selector->selectableFds, selectableFd);
121    }
122
123    return selectableFd;
124}
125
126/**
127 * Adds an fd to the given set if the callback is non-null. Returns true
128 * if the fd was added.
129 */
130static inline bool maybeAdd(SelectableFd* selectableFd,
131        void (*callback)(SelectableFd*), fd_set* fdSet) {
132    if (callback != NULL) {
133        FD_SET(selectableFd->fd, fdSet);
134        return true;
135    }
136    return false;
137}
138
139/**
140 * Removes stale file descriptors and initializes file descriptor sets.
141 */
142static void prepareForSelect(Selector* selector) {
143    fd_set* exceptFds = &selector->exceptFds;
144    fd_set* readFds = &selector->readFds;
145    fd_set* writeFds = &selector->writeFds;
146
147    FD_ZERO(exceptFds);
148    FD_ZERO(readFds);
149    FD_ZERO(writeFds);
150
151    Array* selectableFds = selector->selectableFds;
152    int i = 0;
153    selector->maxFd = 0;
154    int size = arraySize(selectableFds);
155    while (i < size) {
156        SelectableFd* selectableFd = arrayGet(selectableFds, i);
157        if (selectableFd->remove) {
158            // This descriptor should be removed.
159            arrayRemove(selectableFds, i);
160            size--;
161            if (selectableFd->onRemove != NULL) {
162                selectableFd->onRemove(selectableFd);
163            }
164            free(selectableFd);
165        } else {
166            if (selectableFd->beforeSelect != NULL) {
167                selectableFd->beforeSelect(selectableFd);
168            }
169
170            bool inSet = false;
171            if (maybeAdd(selectableFd, selectableFd->onExcept, exceptFds)) {
172                ALOGD("Selecting fd %d for writing...", selectableFd->fd);
173                inSet = true;
174            }
175            if (maybeAdd(selectableFd, selectableFd->onReadable, readFds)) {
176                ALOGD("Selecting fd %d for reading...", selectableFd->fd);
177                inSet = true;
178            }
179            if (maybeAdd(selectableFd, selectableFd->onWritable, writeFds)) {
180                inSet = true;
181            }
182
183            if (inSet) {
184                // If the fd is in a set, check it against max.
185                int fd = selectableFd->fd;
186                if (fd > selector->maxFd) {
187                    selector->maxFd = fd;
188                }
189            }
190
191            // Move to next descriptor.
192            i++;
193        }
194    }
195}
196
197/**
198 * Invokes a callback if the callback is non-null and the fd is in the given
199 * set.
200 */
201static inline void maybeInvoke(SelectableFd* selectableFd,
202        void (*callback)(SelectableFd*), fd_set* fdSet) {
203    if (callback != NULL && !selectableFd->remove &&
204            FD_ISSET(selectableFd->fd, fdSet)) {
205        ALOGD("Selected fd %d.", selectableFd->fd);
206        callback(selectableFd);
207    }
208}
209
210/**
211 * Notifies user if file descriptors are readable or writable, or if
212 * out-of-band data is present.
213 */
214static void fireEvents(Selector* selector) {
215    Array* selectableFds = selector->selectableFds;
216    int size = arraySize(selectableFds);
217    int i;
218    for (i = 0; i < size; i++) {
219        SelectableFd* selectableFd = arrayGet(selectableFds, i);
220        maybeInvoke(selectableFd, selectableFd->onExcept,
221                &selector->exceptFds);
222        maybeInvoke(selectableFd, selectableFd->onReadable,
223                &selector->readFds);
224        maybeInvoke(selectableFd, selectableFd->onWritable,
225                &selector->writeFds);
226    }
227}
228
229void selectorLoop(Selector* selector) {
230    // Make sure we're not already looping.
231    if (selector->looping) {
232        LOG_ALWAYS_FATAL("Already looping.");
233    }
234    selector->looping = true;
235
236    while (true) {
237        setInSelect(selector, true);
238
239        prepareForSelect(selector);
240
241        ALOGD("Entering select().");
242
243        // Select file descriptors.
244        int result = select(selector->maxFd + 1, &selector->readFds,
245                &selector->writeFds, &selector->exceptFds, NULL);
246
247        ALOGD("Exiting select().");
248
249        setInSelect(selector, false);
250
251        if (result == -1) {
252            // Abort on everything except EINTR.
253            if (errno == EINTR) {
254                ALOGI("select() interrupted.");
255            } else {
256                LOG_ALWAYS_FATAL("select() error: %s",
257                        strerror(errno));
258            }
259        } else if (result > 0) {
260            fireEvents(selector);
261        }
262    }
263}
264