/** Wrapper structure for poll() * * This file is part of the Pollcat Library. * Copyright (C) 2022,2025 Expatria Technologies Inc. * Contact: Morgan Hughes * * The Pollcat Library is free software: you can redistribute it and/or modify it under * the terms of the the GNU Lesser General Public License as published by the Free * Software Foundation; either version 3 of the License, or (at your option) any later * version. * * This program is distributed in the hope that it will be useful, but WITHOUT ANY * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * PARTICULAR PURPOSE. See the GNU General Public License for more details. * * You should have received copies of the GNU General Public License and the GNU Lesser * General Public License along with the Pollcat Library. If not, see * https://www.gnu.org/licenses/ * * vim:ts=4:noexpandtab */ #include #include #include #include #include #include #include #include #include "private.h" static struct pollfd *pollcat_list = NULL; static size_t pollcat_size = 0; static size_t pollcat_used = 0; static int pollcat_last = -1; static int pollcat_time_current = -1; /** Optional function to call on fatal error; if unset an assert()-style message will be * printed on stderr and abort() called. */ typedef void (* pollcat_assert_f) (const char *file, int line, const char *mesg); pollcat_assert_f pollcat_assert_func = NULL; /** Base timeout for wrapped poll() calls in milliseconds; pollcat_time_reduce() may be * called to reduce this on a per-loop basis to improve latency of things like timers. * The default is POLLCAT_TIME_BASE. */ int pollcat_time_base = POLLCAT_TIME_BASE; /** Minimum timeout for wrapped poll() calls in milliseconds; pollcat_time_reduce() will * not reduce the per-loop timeout below this value. The default is POLLCAT_TIME_MINIMUM. */ int pollcat_time_minimum = POLLCAT_TIME_MINIMUM; /** Initial size of the pollfd array. The default is POLLCAT_SIZE_INITIAL. */ size_t pollcat_size_initial = POLLCAT_SIZE_INITIAL; /** Incremental size of the pollfd array. The default is POLLCAT_SIZE_INCREMENT. */ size_t pollcat_size_increment = POLLCAT_SIZE_INCREMENT; /** Cleanup function and atexit flag */ static int pollcat_exit = 0; static void pollcat_done (void) { free(pollcat_list); pollcat_list = NULL; pollcat_size = 0; pollcat_used = 0; pollcat_last = -1; } /** Init function */ static void pollcat_init (void) { /* Register cleanup */ if ( !pollcat_exit ) { atexit(pollcat_done); pollcat_exit = 1; } if ( pollcat_list ) return; if ( pollcat_size_initial < 1 ) pollcat_size_initial = 1; if ( pollcat_size_increment < 1 ) pollcat_size_increment = 1; pollcat_list = malloc(pollcat_size_initial * sizeof(struct pollfd)); memset(pollcat_list, 0xFF, pollcat_size_initial * sizeof(struct pollfd)); POLLCAT_ASSERT(pollcat_list != NULL); pollcat_size = pollcat_size_initial; pollcat_used = 0; pollcat_last = -1; pollcat_time_current = pollcat_time_base; } /** Binary search compare function */ static int pollcat_comp (const void *va, const void *vb) { const struct pollfd *pa = (const struct pollfd *)va; const struct pollfd *pb = (const struct pollfd *)vb; POLLCAT_ASSERT(va != NULL); POLLCAT_ASSERT(vb != NULL); return pa->fd - pb->fd; } /** Binary search function */ static struct pollfd *pollcat_search (int fd) { struct pollfd key = { 0 }; struct pollfd *ret; if ( pollcat_used < 1 ) return NULL; /* Check cached index first */ if ( pollcat_last > -1 && pollcat_last < pollcat_used && pollcat_list[pollcat_last].fd == fd ) return &pollcat_list[pollcat_last]; /* Do binary search and cache result */ key.fd = fd; ret = bsearch(&key, pollcat_list, pollcat_used, sizeof(struct pollfd), pollcat_comp); pollcat_last = ret ? ret - pollcat_list : -1; return ret; } /** Add an FD to the poll array and set the events requested * * \param fd File descriptor * \param events Bitmap of events from poll.h, such as POLLIN */ void pollcat_fd_add (int fd, short int events) { struct pollfd *pfd; /* Auto init, no-return if failed */ pollcat_init(); /* Update existing if found */ if ( (pfd = pollcat_search(fd)) ) { pfd->events = events; return; } /* Grow list if full */ if ( pollcat_used == pollcat_size ) { size_t new = pollcat_size + pollcat_size_increment; pfd = malloc(new * sizeof(struct pollfd)); POLLCAT_ASSERT(pfd != NULL); memcpy(pfd, pollcat_list, pollcat_size * sizeof(struct pollfd)); memset(pfd + pollcat_size, 0xFF, pollcat_size_increment * sizeof(struct pollfd)); free(pollcat_list); pollcat_list = pfd; pollcat_size += pollcat_size_increment; } /* Add new entry and increment used */ pollcat_list[pollcat_used].fd = fd; pollcat_list[pollcat_used].events = events; pollcat_used++; /* Re-sort list */ qsort(pollcat_list, pollcat_used, sizeof(struct pollfd), pollcat_comp); } /** Get request bits of an FD in the poll array * * \param fd File descriptor * * \return bitmap on success, <0 if not found */ short int pollcat_events_get (int fd) { struct pollfd *pfd; /* Auto init, no-return if failed */ pollcat_init(); if ( !(pfd = pollcat_search(fd)) ) return -1; return pfd->events; } /** Set request bits on an FD in the poll array * * \param fd File descriptor * \param events Bitmap of events from poll.h, such as POLLIN */ void pollcat_events_set (int fd, short int events) { struct pollfd *pfd; /* Auto init, no-return if failed */ pollcat_init(); if ( !(pfd = pollcat_search(fd)) ) return; pfd->events = events; } /** Get result bits of an FD in the poll array * * \param fd File descriptor * * \return bitmap on success, 0 on error */ short int pollcat_revents (int fd) { struct pollfd *pfd; /* Auto init, no-return if failed */ pollcat_init(); if ( !(pfd = pollcat_search(fd)) ) return 0; return pfd->revents; } /** Remove an FD from the poll array * * \param fd File descriptor */ void pollcat_fd_remove (int fd) { struct pollfd *pfd; /* Auto init, no-return if failed */ pollcat_init(); if ( !(pfd = pollcat_search(fd)) ) return; /* Last entry in list is cheap */ if ( 1 == pollcat_used ) { pollcat_used--; pollcat_last = -1; return; } /* Decrement used and move last entry into vacant slot */ pollcat_used--; pfd->fd = pollcat_list[pollcat_used].fd; pfd->events = pollcat_list[pollcat_used].events; memset(&pollcat_list[pollcat_used], 0xFF, sizeof(struct pollfd)); /* Re-sort list */ qsort(pollcat_list, pollcat_used, sizeof(struct pollfd), pollcat_comp); } /** Reduce the current timeout * * \param time New timeout in milliseconds */ void pollcat_time_reduce (int time) { if ( time < pollcat_time_current ) pollcat_time_current = time; } /** Return the current timeout and reset the current timeout to the base value * * \return Timeout in milliseconds */ int pollcat_time_value (void) { /* latch current value, which may have been reduced by pollcat_time_reduce() */ int value = pollcat_time_current; /* enforce minimum */ if ( value < pollcat_time_minimum ) value = pollcat_time_minimum; /* reset to base before return, for next call */ pollcat_time_current = pollcat_time_base; return value; } /** Wraps the system poll() command using the internal pollfd array and timeout * * \return as per poll(), <0 on error */ int pollcat_poll (void) { int ret; /* Auto init, no-return if failed */ pollcat_init(); ret = poll(pollcat_list, pollcat_used, pollcat_time_value()); return ret; } #if POLLCAT_DEBUG static const char *pollcat_bits (short int events) { static char buff[20] = { 0 }; char *ins = buff; *ins++ = events & POLLIN ? 'I' : '_'; *ins++ = events & POLLPRI ? 'P' : '_'; *ins++ = events & POLLOUT ? 'O' : '_'; #ifdef POLLRDHUP *ins++ = events & POLLRDHUP ? 'R' : '_'; #endif *ins++ = events & POLLERR ? 'E' : '_'; *ins++ = events & POLLHUP ? 'H' : '_'; *ins++ = events & POLLNVAL ? 'N' : '_'; #ifdef POLLMSG *ins++ = events & POLLMSG ? 'M' : '_'; #endif #ifdef _XOPEN_SOURCE *ins++ = ','; *ins++ = 'R'; *ins++ = events & POLLRDNORM ? 'N' : '_'; *ins++ = events & POLLRDBAND ? 'B' : '_'; *ins++ = ','; *ins++ = 'W'; *ins++ = events & POLLWRNORM ? 'N' : '_'; *ins++ = events & POLLWRBAND ? 'B' : '_'; #endif return buff; } #endif void pollcat_dump (FILE *fp) { #if POLLCAT_DEBUG int i; if ( !fp && stderr ) fp = stderr; if ( !fp && stdout ) fp = stdout; if ( !fp ) return; fprintf(fp, "pollcat: list %p, used %zu / size %zu\n", pollcat_list, pollcat_used, pollcat_size); fprintf(fp, "fd events revents path\n"); for ( i = 0; i < pollcat_used; i++ ) { char path[256]; char link[256]; snprintf(path, sizeof(path), "/proc/self/fd/%d", pollcat_list[i].fd); memset(link, 0, sizeof(link)); if ( readlink(path, link, sizeof(link) - 1) < 0 ) snprintf(link, sizeof(link), "err:%s", strerror(errno)); fprintf(fp, "%2d %-8s ", pollcat_list[i].fd, pollcat_bits(pollcat_list[i].events)); fprintf(fp, "%-8s %s\n", pollcat_bits(pollcat_list[i].revents), link); } #endif }