Initial commit of Pollcat library
This commit is contained in:
370
src/pollcat.c
Normal file
370
src/pollcat.c
Normal file
@@ -0,0 +1,370 @@
|
||||
/** 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, sizeof(struct pollfd), pollcat_used, 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;
|
||||
}
|
||||
|
||||
void pollcat_dump (FILE *fp)
|
||||
{
|
||||
int i;
|
||||
|
||||
if ( !fp && stderr )
|
||||
fp = stderr;
|
||||
if ( !fp && stdout )
|
||||
fp = stdout;
|
||||
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user