- fix binary search args order - fix missing pollcat_dump() if POLLCAT_DEBUG=0 - safety check on null fp if stderr/stdout closed
375 lines
8.8 KiB
C
375 lines
8.8 KiB
C
/** Wrapper structure for poll()
|
|
*
|
|
* This file is part of the Pollcat Library.
|
|
* Copyright (C) 2022 Expatria Technologies Inc.
|
|
* Contact: Morgan Hughes <morgan@expatria.ca>
|
|
*
|
|
* 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 <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <fcntl.h>
|
|
#include <unistd.h>
|
|
#include <errno.h>
|
|
#include <poll.h>
|
|
|
|
#include <pollcat.h>
|
|
#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;
|
|
}
|
|
|
|
|
|
/** 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();
|
|
|
|
if ( pollcat_time_current < pollcat_time_minimum )
|
|
pollcat_time_current = pollcat_time_minimum;
|
|
|
|
ret = poll(pollcat_list, pollcat_used, pollcat_time_current);
|
|
pollcat_time_current = pollcat_time_base;
|
|
|
|
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
|
|
}
|
|
|