/* * Copyright (c) 2013-2015 Todd C. Miller * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, 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. */ #include #include /* for howmany() on Linux */ #include #ifdef HAVE_SYS_SYSMACROS_H # include /* for howmany() on Solaris */ #endif #ifdef HAVE_SYS_SELECT_H # include #endif /* HAVE_SYS_SELECT_H */ #include #ifdef STDC_HEADERS # include # include #else # ifdef HAVE_STDLIB_H # include # endif #endif /* STDC_HEADERS */ #ifdef HAVE_STDBOOL_H # include #else # include "compat/stdbool.h" #endif /* HAVE_STDBOOL_H */ #ifdef HAVE_STRING_H # include #endif /* HAVE_STRING_H */ #ifdef HAVE_STRINGS_H # include #endif /* HAVE_STRINGS_H */ #ifdef HAVE_UNISTD_H # include #endif /* HAVE_UNISTD_H */ #include #include "sudo_compat.h" #include "sudo_fatal.h" #include "sudo_debug.h" #include "sudo_event.h" #include "sudo_util.h" int sudo_ev_base_alloc_impl(struct sudo_event_base *base) { debug_decl(sudo_ev_base_alloc_impl, SUDO_DEBUG_EVENT) base->maxfd = NFDBITS - 1; base->readfds_in = calloc(1, sizeof(fd_mask)); base->writefds_in = calloc(1, sizeof(fd_mask)); base->readfds_out = calloc(1, sizeof(fd_mask)); base->writefds_out = calloc(1, sizeof(fd_mask)); if (base->readfds_in == NULL || base->writefds_in == NULL || base->readfds_out == NULL || base->writefds_out == NULL) { free(base->readfds_in); free(base->writefds_in); free(base->readfds_out); free(base->writefds_out); debug_return_int(-1); } debug_return_int(0); } void sudo_ev_base_free_impl(struct sudo_event_base *base) { debug_decl(sudo_ev_base_free_impl, SUDO_DEBUG_EVENT) free(base->readfds_in); free(base->writefds_in); free(base->readfds_out); free(base->writefds_out); debug_return; } int sudo_ev_add_impl(struct sudo_event_base *base, struct sudo_event *ev) { debug_decl(sudo_ev_add_impl, SUDO_DEBUG_EVENT) /* If out of space in fd sets, realloc. */ if (ev->fd > base->maxfd) { const int o = (base->maxfd + 1) / NFDBITS; const int n = howmany(ev->fd + 1, NFDBITS); const size_t used_bytes = o * sizeof(fd_mask); const size_t new_bytes = (n - o) * sizeof(fd_mask); base->readfds_in = reallocarray(base->readfds_in, n, sizeof(fd_mask)); memset((char *)base->readfds_in + used_bytes, 0, new_bytes); base->writefds_in = reallocarray(base->writefds_in, n, sizeof(fd_mask)); memset((char *)base->writefds_in + used_bytes, 0, new_bytes); base->readfds_out = reallocarray(base->readfds_out, n, sizeof(fd_mask)); memset((char *)base->readfds_out + used_bytes, 0, new_bytes); base->writefds_out = reallocarray(base->writefds_out, n, sizeof(fd_mask)); memset((char *)base->writefds_out + used_bytes, 0, new_bytes); base->maxfd = (n * NFDBITS) - 1; } /* Set events and adjust high fd as needed. */ if (ISSET(ev->events, SUDO_EV_READ)) { sudo_debug_printf(SUDO_DEBUG_DEBUG, "%s: added fd %d to readfs", __func__, ev->fd); FD_SET(ev->fd, base->readfds_in); } if (ISSET(ev->events, SUDO_EV_WRITE)) { sudo_debug_printf(SUDO_DEBUG_DEBUG, "%s: added fd %d to writefds", __func__, ev->fd); FD_SET(ev->fd, base->writefds_in); } if (ev->fd > base->highfd) base->highfd = ev->fd; debug_return_int(0); } int sudo_ev_del_impl(struct sudo_event_base *base, struct sudo_event *ev) { debug_decl(sudo_ev_del_impl, SUDO_DEBUG_EVENT) /* Remove from readfds and writefds and adjust high fd. */ if (ISSET(ev->events, SUDO_EV_READ)) { sudo_debug_printf(SUDO_DEBUG_DEBUG, "%s: removed fd %d from readfds", __func__, ev->fd); FD_CLR(ev->fd, base->readfds_in); } if (ISSET(ev->events, SUDO_EV_WRITE)) { sudo_debug_printf(SUDO_DEBUG_DEBUG, "%s: removed fd %d from writefds", __func__, ev->fd); FD_CLR(ev->fd, base->writefds_in); } if (base->highfd == ev->fd) { for (;;) { if (FD_ISSET(base->highfd, base->readfds_in) || FD_ISSET(base->highfd, base->writefds_in)) break; if (--base->highfd < 0) break; } } debug_return_int(0); } int sudo_ev_scan_impl(struct sudo_event_base *base, int flags) { struct timeval now, tv, *timeout; struct sudo_event *ev; size_t setsize; int nready; debug_decl(sudo_ev_loop, SUDO_DEBUG_EVENT) if ((ev = TAILQ_FIRST(&base->timeouts)) != NULL) { gettimeofday(&now, NULL); sudo_timevalsub(&ev->timeout, &now, &tv); if (tv.tv_sec < 0 || (tv.tv_sec == 0 && tv.tv_usec < 0)) sudo_timevalclear(&tv); timeout = &tv; } else { if (ISSET(flags, SUDO_EVLOOP_NONBLOCK)) { sudo_timevalclear(&tv); timeout = &tv; } else { timeout = NULL; } } /* select() overwrites readfds/writefds so make a copy. */ setsize = howmany(base->highfd + 1, NFDBITS) * sizeof(fd_mask); memcpy(base->readfds_out, base->readfds_in, setsize); memcpy(base->writefds_out, base->writefds_in, setsize); sudo_debug_printf(SUDO_DEBUG_DEBUG, "%s: select high fd %d", __func__, base->highfd); nready = select(base->highfd + 1, base->readfds_out, base->writefds_out, NULL, timeout); sudo_debug_printf(SUDO_DEBUG_INFO, "%s: %d fds ready", __func__, nready); switch (nready) { case -1: /* Error or interrupted by signal. */ debug_return_int(-1); case 0: /* Front end will activate timeout events. */ break; default: /* Activate each I/O event that fired. */ TAILQ_FOREACH(ev, &base->events, entries) { if (ev->fd >= 0) { int what = 0; if (FD_ISSET(ev->fd, base->readfds_out)) what |= (ev->events & SUDO_EV_READ); if (FD_ISSET(ev->fd, base->writefds_out)) what |= (ev->events & SUDO_EV_WRITE); if (what != 0) { /* Make event active. */ sudo_debug_printf(SUDO_DEBUG_DEBUG, "%s: selected fd %d, events %d, activating %p", __func__, ev->fd, what, ev); ev->revents = what; sudo_ev_activate(base, ev); } } } break; } debug_return_int(nready); }