diff options
Diffstat (limited to '')
-rw-r--r-- | src/lib/ioloop-poll.c | 221 |
1 files changed, 221 insertions, 0 deletions
diff --git a/src/lib/ioloop-poll.c b/src/lib/ioloop-poll.c new file mode 100644 index 0000000..02cdce6 --- /dev/null +++ b/src/lib/ioloop-poll.c @@ -0,0 +1,221 @@ +/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */ + +/* @UNSAFE: whole file */ + +#include "lib.h" +#include "ioloop-private.h" + +#ifdef IOLOOP_POLL + +#include <fcntl.h> +#include <sys/poll.h> + +struct ioloop_handler_context { + unsigned int fds_count, fds_pos; + struct pollfd *fds; + + unsigned int idx_count; + int *fd_index; +}; + +void io_loop_handler_init(struct ioloop *ioloop, unsigned int initial_fd_count) +{ + struct ioloop_handler_context *ctx; + + ioloop->handler_context = ctx = i_new(struct ioloop_handler_context, 1); + ctx->fds_count = initial_fd_count; + ctx->fds = i_new(struct pollfd, ctx->fds_count); + + ctx->idx_count = initial_fd_count; + ctx->fd_index = i_new(int, ctx->idx_count); + memset(ctx->fd_index, 0xff, sizeof(int) * ctx->idx_count); +} + +void io_loop_handler_deinit(struct ioloop *ioloop) +{ + i_free(ioloop->handler_context->fds); + i_free(ioloop->handler_context->fd_index); + i_free(ioloop->handler_context); +} + +#define IO_POLL_ERROR (POLLERR | POLLHUP | POLLNVAL) +#define IO_POLL_INPUT (POLLIN | POLLPRI | IO_POLL_ERROR) +#define IO_POLL_OUTPUT (POLLOUT | IO_POLL_ERROR) + +void io_loop_handle_add(struct io_file *io) +{ + struct ioloop_handler_context *ctx = io->io.ioloop->handler_context; + enum io_condition condition = io->io.condition; + unsigned int old_count; + int index, old_events, fd = io->fd; + + if ((unsigned int)fd >= ctx->idx_count) { + /* grow the fd -> index array */ + old_count = ctx->idx_count; + + ctx->idx_count = nearest_power((unsigned int) fd+1); + + ctx->fd_index = i_realloc_type(ctx->fd_index, int, + old_count, ctx->idx_count); + memset(ctx->fd_index + old_count, 0xff, + sizeof(int) * (ctx->idx_count-old_count)); + } + + if (ctx->fds_pos >= ctx->fds_count) { + /* grow the fd array */ + old_count = ctx->fds_count; + + ctx->fds_count = nearest_power(ctx->fds_count+1); + + ctx->fds = i_realloc_type(ctx->fds, struct pollfd, + old_count, ctx->fds_count); + } + + if (ctx->fd_index[fd] != -1) { + /* update existing pollfd */ + index = ctx->fd_index[fd]; + } else { + /* add new pollfd */ + index = ctx->fds_pos++; + + ctx->fd_index[fd] = index; + ctx->fds[index].fd = fd; + ctx->fds[index].events = 0; + ctx->fds[index].revents = 0; + } + + old_events = ctx->fds[index].events; + if ((condition & IO_READ) != 0) + ctx->fds[index].events |= IO_POLL_INPUT; + if ((condition & IO_WRITE) != 0) + ctx->fds[index].events |= IO_POLL_OUTPUT; + if ((condition & IO_ERROR) != 0) + ctx->fds[index].events |= IO_POLL_ERROR; + i_assert(ctx->fds[index].events != old_events); +} + +void io_loop_handle_remove(struct io_file *io, bool closed ATTR_UNUSED) +{ + struct ioloop_handler_context *ctx = io->io.ioloop->handler_context; + enum io_condition condition = io->io.condition; + int index, fd = io->fd; + + index = ctx->fd_index[fd]; + i_assert(index >= 0 && (unsigned int) index < ctx->fds_count); + +#ifdef DEBUG + if (!closed) { + /* io_remove() is required to be called before fd is closed. + This is required by epoll/kqueue, but since poll is more + commonly used while developing, this check here should catch + the error early enough not to cause problems for kqueue + users. */ + if (fcntl(io->fd, F_GETFD, 0) < 0) { + if (errno == EBADF) + i_panic("io_remove(%d) called too late", io->fd); + else + i_error("fcntl(%d, F_GETFD) failed: %m", io->fd); + } + } +#endif + i_free(io); + + if ((condition & IO_READ) != 0) { + ctx->fds[index].events &= ENUM_NEGATE(POLLIN | POLLPRI); + ctx->fds[index].revents &= ENUM_NEGATE(POLLIN | POLLPRI); + } + if ((condition & IO_WRITE) != 0) { + ctx->fds[index].events &= ENUM_NEGATE(POLLOUT); + ctx->fds[index].revents &= ENUM_NEGATE(POLLOUT); + } + + if ((ctx->fds[index].events & (POLLIN|POLLOUT)) == 0) { + /* remove the whole pollfd */ + ctx->fd_index[ctx->fds[index].fd] = -1; + if (--ctx->fds_pos == (unsigned int) index) + return; /* removing last one */ + + /* move the last pollfd over the removed one */ + ctx->fds[index] = ctx->fds[ctx->fds_pos]; + ctx->fd_index[ctx->fds[index].fd] = index; + } +} + +void io_loop_handler_run_internal(struct ioloop *ioloop) +{ + struct ioloop_handler_context *ctx = ioloop->handler_context; + struct pollfd *pollfd; + struct timeval tv; + struct io_file *io; + int msecs, ret; + bool call; + + /* get the time left for next timeout task */ + msecs = io_loop_run_get_wait_time(ioloop, &tv); +#ifdef _AIX + if (msecs > 1000) { + /* AIX seems to check IO_POLL_ERRORs only at the beginning of + the poll() call, not during it. keep timeouts short enough + so that we'll notice them pretty quickly. */ + msecs = 1000; + } +#endif + + ret = poll(ctx->fds, ctx->fds_pos, msecs); + if (ret < 0 && errno != EINTR) + i_fatal("poll(): %m"); + + /* execute timeout handlers */ + io_loop_handle_timeouts(ioloop); + + if (ret <= 0 || !ioloop->running) { + /* no I/O events */ + return; + } + + io = ioloop->io_files; + for (; io != NULL && ret > 0; io = ioloop->next_io_file) { + ioloop->next_io_file = io->next; + + if (io->fd == -1) { + /* io_add_istream() without fd */ + continue; + } + pollfd = &ctx->fds[ctx->fd_index[io->fd]]; + if (pollfd->revents != 0) { + if (pollfd->revents & POLLNVAL) { + i_error("invalid I/O fd %d, callback %p", + io->fd, (void *) io->io.callback); + pollfd->events = 0; + pollfd->revents = 0; + call = TRUE; + } else if ((io->io.condition & + (IO_READ|IO_WRITE)) == (IO_READ|IO_WRITE)) { + call = TRUE; + pollfd->revents = 0; + } else if ((io->io.condition & IO_READ) != 0) { + call = (pollfd->revents & IO_POLL_INPUT) != 0; + pollfd->revents &= ENUM_NEGATE(IO_POLL_INPUT); + } else if ((io->io.condition & IO_WRITE) != 0) { + call = (pollfd->revents & IO_POLL_OUTPUT) != 0; + pollfd->revents &= ENUM_NEGATE(IO_POLL_OUTPUT); + } else if ((io->io.condition & IO_ERROR) != 0) { + call = (pollfd->revents & IO_POLL_ERROR) != 0; + pollfd->revents &= ENUM_NEGATE(IO_POLL_ERROR); + } else { + call = FALSE; + } + + if (pollfd->revents == 0) + ret--; + + if (call) { + io_loop_call_io(&io->io); + if (!ioloop->running) + return; + } + } + } +} + +#endif |