From d6eff3c31e8289881a3aa9b858e5710d0f741db0 Mon Sep 17 00:00:00 2001 From: Keith Packard Date: Thu, 26 May 2016 10:20:45 -0700 Subject: os: Add ospoll interface [v2] This provides a wrapper around poll or epoll providing a callback-based interface for monitoring activity on a large set of file descriptors. v2: use xserver_poll API instead of poll. Don't use WSAPoll as that is broken. Signed-off-by: Keith Packard Reviewed-by: Adam Jackson --- configure.ac | 2 +- include/dix-config.h.in | 3 + os/Makefile.am | 1 + os/ospoll.c | 442 ++++++++++++++++++++++++++++++++++++++++++++++++ os/ospoll.h | 142 ++++++++++++++++ 5 files changed, 589 insertions(+), 1 deletion(-) create mode 100644 os/ospoll.c create mode 100644 os/ospoll.h diff --git a/configure.ac b/configure.ac index 58fcf79d5..2b93a4a30 100644 --- a/configure.ac +++ b/configure.ac @@ -219,7 +219,7 @@ dnl Checks for library functions. AC_CHECK_FUNCS([backtrace ffs geteuid getuid issetugid getresuid \ getdtablesize getifaddrs getpeereid getpeerucred getprogname getzoneid \ mmap posix_fallocate seteuid shmctl64 strncasecmp vasprintf vsnprintf \ - walkcontext setitimer poll]) + walkcontext setitimer poll epoll_create1]) AC_CONFIG_LIBOBJ_DIR([os]) AC_REPLACE_FUNCS([reallocarray strcasecmp strcasestr strlcat strlcpy strndup]) AM_CONDITIONAL(POLL, [test "x$ac_cv_func_poll" = "xyes"]) diff --git a/include/dix-config.h.in b/include/dix-config.h.in index bf2a9eda3..d49af928f 100644 --- a/include/dix-config.h.in +++ b/include/dix-config.h.in @@ -530,4 +530,7 @@ /* Have poll() */ #undef HAVE_POLL +/* Have epoll_create1() */ +#undef HAVE_EPOLL_CREATE1 + #endif /* _DIX_CONFIG_H_ */ diff --git a/os/Makefile.am b/os/Makefile.am index c449076f8..d97ba30fb 100644 --- a/os/Makefile.am +++ b/os/Makefile.am @@ -22,6 +22,7 @@ libos_la_SOURCES = \ oscolor.c \ osdep.h \ osinit.c \ + ospoll.c \ utils.c \ xdmauth.c \ xsha1.c \ diff --git a/os/ospoll.c b/os/ospoll.c new file mode 100644 index 000000000..3c2b80b39 --- /dev/null +++ b/os/ospoll.c @@ -0,0 +1,442 @@ +/* + * Copyright © 2016 Keith Packard + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that copyright + * notice and this permission notice appear in supporting documentation, and + * that the name of the copyright holders not be used in advertising or + * publicity pertaining to distribution of the software without specific, + * written prior permission. The copyright holders make no representations + * about the suitability of this software for any purpose. It is provided "as + * is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO + * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, + * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE + * OF THIS SOFTWARE. + */ + +#ifdef HAVE_DIX_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include "misc.h" /* for typedef of pointer */ +#include "ospoll.h" +#include "list.h" + +#if !HAVE_OSPOLL && HAVE_EPOLL_CREATE1 +#include +#define EPOLL 1 +#define HAVE_OSPOLL 1 +#endif + +#if !HAVE_OSPOLL +#include "xserver_poll.h" +#define POLL 1 +#define HAVE_OSPOLL 1 +#endif + +#if EPOLL +#include + +/* epoll-based implementation */ +struct ospollfd { + int fd; + int xevents; + enum ospoll_trigger trigger; + void (*callback)(int fd, int xevents, void *data); + void *data; +}; + +struct ospoll { + int epoll_fd; + struct ospollfd **fds; + int num; + int size; +}; + +#endif + +#if POLL + +/* poll-based implementation */ +struct ospollfd { + short revents; + enum ospoll_trigger trigger; + void (*callback)(int fd, int revents, void *data); + void *data; +}; + +struct ospoll { + struct pollfd *fds; + struct ospollfd *osfds; + int num; + int size; +}; + +#endif + +/* Binary search for the specified file descriptor + * + * Returns position if found + * Returns -position - 1 if not found + */ + +static int +ospoll_find(struct ospoll *ospoll, int fd) +{ + int lo = 0; + int hi = ospoll->num - 1; + + while (lo <= hi) { + int m = (lo + hi) >> 1; +#if EPOLL + int t = ospoll->fds[m]->fd; +#endif +#if POLL + int t = ospoll->fds[m].fd; +#endif + + if (t < fd) + lo = m + 1; + else if (t > fd) + hi = m - 1; + else + return m; + } + return -(lo + 1); +} + +/* Insert an element into an array + * + * base: base address of array + * num: number of elements in the array before the insert + * size: size of each element + * pos: position to insert at + */ +static inline void +array_insert(void *base, size_t num, size_t size, size_t pos) +{ + char *b = base; + + memmove(b + (pos+1) * size, + b + pos * size, + (num - pos) * size); +} + +/* Delete an element from an array + * + * base: base address of array + * num: number of elements in the array before the delete + * size: size of each element + * pos: position to delete from + */ +static inline void +array_delete(void *base, size_t num, size_t size, size_t pos) +{ + char *b = base; + + memmove(b + pos * size, b + (pos + 1) * size, + (num - pos - 1) * size); +} + + +struct ospoll * +ospoll_create(void) +{ +#if EPOLL + struct ospoll *ospoll = calloc(1, sizeof (struct ospoll)); + + ospoll->epoll_fd = epoll_create1(EPOLL_CLOEXEC); + if (ospoll->epoll_fd < 0) { + free (ospoll); + return NULL; + } + return ospoll; +#endif +#if POLL + return calloc(1, sizeof (struct ospoll)); +#endif +} + +void +ospoll_destroy(struct ospoll *ospoll) +{ +#if EPOLL + if (ospoll) { + assert (ospoll->num == 0); + close(ospoll->epoll_fd); + free(ospoll->fds); + free(ospoll); + } +#endif +#if POLL + if (ospoll) { + assert (ospoll->num == 0); + free (ospoll->fds); + free (ospoll->osfds); + free (ospoll); + } +#endif +} + +Bool +ospoll_add(struct ospoll *ospoll, int fd, + enum ospoll_trigger trigger, + void (*callback)(int fd, int xevents, void *data), + void *data) +{ + int pos = ospoll_find(ospoll, fd); +#if EPOLL + struct ospollfd *osfd; + + if (pos < 0) { + + struct epoll_event ev; + + osfd = calloc(1, sizeof (struct ospollfd)); + if (!osfd) + return FALSE; + + if (ospoll->num >= ospoll->size) { + struct ospollfd **new_fds; + int new_size = ospoll->size ? ospoll->size * 2 : MAXCLIENTS * 2; + + new_fds = reallocarray(ospoll->fds, new_size, sizeof (ospoll->fds[0])); + if (!new_fds) { + free (osfd); + return FALSE; + } + ospoll->fds = new_fds; + ospoll->size = new_size; + } + + ev.events = 0; + ev.data.ptr = osfd; + if (trigger == ospoll_trigger_edge) + ev.events |= EPOLLET; + if (epoll_ctl(ospoll->epoll_fd, EPOLL_CTL_ADD, fd, &ev) == -1) { + free(osfd); + return FALSE; + } + osfd->fd = fd; + osfd->xevents = 0; + + pos = -pos - 1; + array_insert(ospoll->fds, ospoll->num, sizeof (ospoll->fds[0]), pos); + ospoll->fds[pos] = osfd; + ospoll->num++; + } else { + osfd = ospoll->fds[pos]; + } + osfd->data = data; + osfd->callback = callback; + osfd->trigger = trigger; +#endif +#if POLL + if (pos < 0) { + if (ospoll->num == ospoll->size) { + struct pollfd *new_fds; + struct ospollfd *new_osfds; + int new_size = ospoll->size ? ospoll->size * 2 : MAXCLIENTS * 2; + + new_fds = reallocarray(ospoll->fds, new_size, sizeof (ospoll->fds[0])); + if (!new_fds) + return FALSE; + ospoll->fds = new_fds; + new_osfds = reallocarray(ospoll->osfds, new_size, sizeof (ospoll->osfds[0])); + if (!new_osfds) + return FALSE; + ospoll->osfds = new_osfds; + ospoll->size = new_size; + } + pos = -pos - 1; + array_insert(ospoll->fds, ospoll->num, sizeof (ospoll->fds[0]), pos); + array_insert(ospoll->osfds, ospoll->num, sizeof (ospoll->osfds[0]), pos); + ospoll->num++; + + ospoll->fds[pos].fd = fd; + ospoll->fds[pos].events = 0; + ospoll->fds[pos].revents = 0; + ospoll->osfds[pos].revents = 0; + } + ospoll->osfds[pos].trigger = trigger; + ospoll->osfds[pos].callback = callback; + ospoll->osfds[pos].data = data; +#endif + return TRUE; +} + +void +ospoll_remove(struct ospoll *ospoll, int fd) +{ + int pos = ospoll_find(ospoll, fd); + + pos = ospoll_find(ospoll, fd); + if (pos >= 0) { +#if EPOLL + struct ospollfd *osfd = ospoll->fds[pos]; + struct epoll_event ev; + ev.events = 0; + ev.data.ptr = osfd; + (void) epoll_ctl(ospoll->epoll_fd, EPOLL_CTL_DEL, fd, &ev); + + array_delete(ospoll->fds, ospoll->num, sizeof (ospoll->fds[0]), pos); + ospoll->num--; + free (osfd); +#endif +#if POLL + array_delete(ospoll->fds, ospoll->num, sizeof (ospoll->fds[0]), pos); + array_delete(ospoll->osfds, ospoll->num, sizeof (ospoll->osfds[0]), pos); + ospoll->num--; +#endif + } +} + +#if EPOLL +static void +epoll_mod(struct ospoll *ospoll, struct ospollfd *osfd) +{ + struct epoll_event ev; + ev.events = 0; + if (osfd->xevents & X_NOTIFY_READ) + ev.events |= EPOLLIN; + if (osfd->xevents & X_NOTIFY_WRITE) + ev.events |= EPOLLOUT; + if (osfd->trigger == ospoll_trigger_edge) + ev.events |= EPOLLET; + ev.data.ptr = osfd; + (void) epoll_ctl(ospoll->epoll_fd, EPOLL_CTL_MOD, osfd->fd, &ev); +} +#endif + +void +ospoll_listen(struct ospoll *ospoll, int fd, int xevents) +{ + int pos = ospoll_find(ospoll, fd); + + if (pos >= 0) { +#if EPOLL + struct ospollfd *osfd = ospoll->fds[pos]; + osfd->xevents |= xevents; + epoll_mod(ospoll, osfd); +#endif +#if POLL + if (xevents & X_NOTIFY_READ) + ospoll->fds[pos].events |= POLLIN; + if (xevents & X_NOTIFY_WRITE) + ospoll->fds[pos].events |= POLLOUT; +#endif + } +} + +void +ospoll_mute(struct ospoll *ospoll, int fd, int xevents) +{ + int pos = ospoll_find(ospoll, fd); + + if (pos >= 0) { +#if EPOLL + struct ospollfd *osfd = ospoll->fds[pos]; + osfd->xevents &= ~xevents; + epoll_mod(ospoll, osfd); +#endif +#if POLL + if (xevents & X_NOTIFY_READ) + ospoll->fds[pos].events &= ~POLLIN; + if (xevents & X_NOTIFY_WRITE) + ospoll->fds[pos].events &= ~POLLOUT; +#endif + } +} + + +int +ospoll_wait(struct ospoll *ospoll, int timeout) +{ + int nready; +#if EPOLL +#define MAX_EVENTS 256 + struct epoll_event events[MAX_EVENTS]; + int i; + + nready = epoll_wait(ospoll->epoll_fd, events, MAX_EVENTS, timeout); + for (i = 0; i < nready; i++) { + struct epoll_event *ev = &events[i]; + struct ospollfd *osfd = ev->data.ptr; + uint32_t revents = ev->events; + int xevents = 0; + + if (revents & EPOLLIN) + xevents |= X_NOTIFY_READ; + if (revents & EPOLLOUT) + xevents |= X_NOTIFY_WRITE; + if (revents & (~(EPOLLIN|EPOLLOUT))) + xevents |= X_NOTIFY_ERROR; + + osfd->callback(osfd->fd, xevents, osfd->data); + } +#endif +#if POLL + nready = xserver_poll(ospoll->fds, ospoll->num, timeout); + if (nready > 0) { + int f; + for (f = 0; f < ospoll->num; f++) { + short revents = ospoll->fds[f].revents; + short oldevents = ospoll->osfds[f].revents; + + ospoll->osfds[f].revents = (revents & (POLLIN|POLLOUT)); + if (ospoll->osfds[f].trigger == ospoll_trigger_edge) + revents &= ~oldevents; + if (revents) { + int xevents = 0; + if (revents & POLLIN) + xevents |= X_NOTIFY_READ; + if (revents & POLLOUT) + xevents |= X_NOTIFY_WRITE; + if (revents & (~(POLLIN|POLLOUT))) + xevents |= X_NOTIFY_ERROR; + ospoll->osfds[f].callback(ospoll->fds[f].fd, xevents, + ospoll->osfds[f].data); + } + } + } +#endif + return nready; +} + +void +ospoll_reset_events(struct ospoll *ospoll, int fd) +{ +#if POLL + int pos = ospoll_find(ospoll, fd); + + if (pos < 0) + return; + + ospoll->osfds[pos].revents = 0; +#endif +} + +void * +ospoll_data(struct ospoll *ospoll, int fd) +{ + int pos = ospoll_find(ospoll, fd); + + if (pos < 0) + return NULL; +#if EPOLL + return ospoll->fds[pos]->data; +#endif +#if POLL + return ospoll->osfds[pos].data; +#endif +} diff --git a/os/ospoll.h b/os/ospoll.h new file mode 100644 index 000000000..cdc45f4d9 --- /dev/null +++ b/os/ospoll.h @@ -0,0 +1,142 @@ +/* + * Copyright © 2016 Keith Packard + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that copyright + * notice and this permission notice appear in supporting documentation, and + * that the name of the copyright holders not be used in advertising or + * publicity pertaining to distribution of the software without specific, + * written prior permission. The copyright holders make no representations + * about the suitability of this software for any purpose. It is provided "as + * is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO + * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, + * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE + * OF THIS SOFTWARE. + */ + +#ifndef _OSPOLL_H_ +#define _OSPOLL_H_ + +/* Forward declaration */ +struct ospoll; + +/** + * ospoll_wait trigger mode + * + * @ospoll_trigger_edge + * Trigger only when going from no data available + * to data available. + * + * @ospoll_trigger_level + * Trigger whenever there is data available + */ +enum ospoll_trigger { + ospoll_trigger_edge, + ospoll_trigger_level +}; + +/** + * Create a new ospoll structure + */ +struct ospoll * +ospoll_create(void); + +/** + * Destroy an ospoll structure + * + * @param ospoll ospoll to destroy + */ +void +ospoll_destroy(struct ospoll *ospoll); + +/** + * Add a file descriptor to monitor + * + * @param ospoll ospoll to add to + * @param fd File descriptor to monitor + * @param trigger Trigger mode for ospoll_wait + * @param callback Function to call when triggered + * @param data Extra data to pass callback + */ +Bool +ospoll_add(struct ospoll *ospoll, int fd, + enum ospoll_trigger trigger, + void (*callback)(int fd, int xevents, void *data), + void *data); + +/** + * Remove a monitored file descriptor + * + * @param ospoll ospoll to remove from + * @param fd File descriptor to stop monitoring + */ +void +ospoll_remove(struct ospoll *ospoll, int fd); + +/** + * Listen on additional events + * + * @param ospoll ospoll monitoring fd + * @param fd File descriptor to change + * @param events Additional events to trigger on + */ +void +ospoll_listen(struct ospoll *ospoll, int fd, int xevents); + +/** + * Stop listening on events + * + * @param ospoll ospoll monitoring fd + * @param fd File descriptor to change + * @param events events to stop triggering on + */ +void +ospoll_mute(struct ospoll *ospoll, int fd, int xevents); + +/** + * Wait for events + * + * @param ospoll ospoll to wait on + * @param timeout < 0 wait forever + * = 0 check and return + * > 0 timeout in milliseconds + * @return < 0 error + * = 0 timeout + * > 0 number of events delivered + */ +int +ospoll_wait(struct ospoll *ospoll, int timeout); + +/** + * Reset edge trigger status + * + * @param ospoll ospoll monitoring fd + * @param fd file descriptor + * + * ospoll_reset_events resets the state of an edge-triggered + * fd so that ospoll_wait calls will report events again. + * + * Call this after a read/recv operation reports no more data available. + */ +void +ospoll_reset_events(struct ospoll *ospoll, int fd); + +/** + * Fetch the data associated with an fd + * + * @param ospoll ospoll monitoring fd + * @param fd file descriptor + * + * @return data parameter passed to ospoll_add call on + * this file descriptor + */ +void * +ospoll_data(struct ospoll *ospoll, int fd); + +#endif /* _OSPOLL_H_ */ -- cgit v1.2.3