diff options
Diffstat (limited to 'src/eloop.c')
| -rw-r--r-- | src/eloop.c | 787 | 
1 files changed, 787 insertions, 0 deletions
| diff --git a/src/eloop.c b/src/eloop.c new file mode 100644 index 000000000000..a6ab43fbaef7 --- /dev/null +++ b/src/eloop.c @@ -0,0 +1,787 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ +/* + * eloop - portable event based main loop. + * Copyright (c) 2006-2021 Roy Marples <roy@marples.name> + * All rights reserved. + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + *    notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + *    notice, this list of conditions and the following disclaimer in the + *    documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/time.h> + +#include <assert.h> +#include <errno.h> +#include <limits.h> +#include <poll.h> +#include <stdbool.h> +#include <signal.h> +#include <stdint.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +/* config.h should define HAVE_PPOLL, etc. */ +#if defined(HAVE_CONFIG_H) && !defined(NO_CONFIG_H) +#include "config.h" +#endif + +#if defined(HAVE_PPOLL) +#elif defined(HAVE_POLLTS) +#define ppoll pollts +#elif !defined(HAVE_PSELECT) +#pragma message("Compiling eloop with pselect(2) support.") +#define HAVE_PSELECT +#define ppoll eloop_ppoll +#endif + +#include "eloop.h" + +#ifndef UNUSED +#define UNUSED(a) (void)((a)) +#endif +#ifndef __unused +#ifdef __GNUC__ +#define __unused   __attribute__((__unused__)) +#else +#define __unused +#endif +#endif + +#ifdef HAVE_PSELECT +#include <sys/select.h> +#endif + +/* Our structures require TAILQ macros, which really every libc should + * ship as they are useful beyond belief. + * Sadly some libc's don't have sys/queue.h and some that do don't have + * the TAILQ_FOREACH macro. For those that don't, the application using + * this implementation will need to ship a working queue.h somewhere. + * If we don't have sys/queue.h found in config.h, then + * allow QUEUE_H to override loading queue.h in the current directory. */ +#ifndef TAILQ_FOREACH +#ifdef HAVE_SYS_QUEUE_H +#include <sys/queue.h> +#elif defined(QUEUE_H) +#define __QUEUE_HEADER(x) #x +#define _QUEUE_HEADER(x) __QUEUE_HEADER(x) +#include _QUEUE_HEADER(QUEUE_H) +#else +#include "queue.h" +#endif +#endif + +#ifdef ELOOP_DEBUG +#include <stdio.h> +#endif + +/* + * Allow a backlog of signals. + * If you use many eloops in the same process, they should all + * use the same signal handler or have the signal handler unset. + * Otherwise the signal might not behave as expected. + */ +#define ELOOP_NSIGNALS	5 + +/* + * time_t is a signed integer of an unspecified size. + * To adjust for time_t wrapping, we need to work the maximum signed + * value and use that as a maximum. + */ +#ifndef TIME_MAX +#define	TIME_MAX	((1ULL << (sizeof(time_t) * NBBY - 1)) - 1) +#endif +/* The unsigned maximum is then simple - multiply by two and add one. */ +#ifndef UTIME_MAX +#define	UTIME_MAX	(TIME_MAX * 2) + 1 +#endif + +struct eloop_event { +	TAILQ_ENTRY(eloop_event) next; +	int fd; +	void (*read_cb)(void *); +	void *read_cb_arg; +	void (*write_cb)(void *); +	void *write_cb_arg; +	struct pollfd *pollfd; +}; + +struct eloop_timeout { +	TAILQ_ENTRY(eloop_timeout) next; +	unsigned int seconds; +	unsigned int nseconds; +	void (*callback)(void *); +	void *arg; +	int queue; +}; + +struct eloop { +	TAILQ_HEAD (event_head, eloop_event) events; +	size_t nevents; +	struct event_head free_events; +	bool events_need_setup; + +	struct timespec now; +	TAILQ_HEAD (timeout_head, eloop_timeout) timeouts; +	struct timeout_head free_timeouts; + +	const int *signals; +	size_t signals_len; +	void (*signal_cb)(int, void *); +	void *signal_cb_ctx; + +	struct pollfd *fds; +	size_t nfds; + +	int exitnow; +	int exitcode; +}; + +#ifdef HAVE_REALLOCARRAY +#define	eloop_realloca	reallocarray +#else +/* Handy routing to check for potential overflow. + * reallocarray(3) and reallocarr(3) are not portable. */ +#define SQRT_SIZE_MAX (((size_t)1) << (sizeof(size_t) * CHAR_BIT / 2)) +static void * +eloop_realloca(void *ptr, size_t n, size_t size) +{ + +	if ((n | size) >= SQRT_SIZE_MAX && n > SIZE_MAX / size) { +		errno = EOVERFLOW; +		return NULL; +	} +	return realloc(ptr, n * size); +} +#endif + +#ifdef HAVE_PSELECT +/* Wrapper around pselect, to imitate the ppoll call. */ +static int +eloop_ppoll(struct pollfd * fds, nfds_t nfds, +    const struct timespec *ts, const sigset_t *sigmask) +{ +	fd_set read_fds, write_fds; +	nfds_t n; +	int maxfd, r; + +	FD_ZERO(&read_fds); +	FD_ZERO(&write_fds); +	maxfd = 0; +	for (n = 0; n < nfds; n++) { +		if (fds[n].events & POLLIN) { +			FD_SET(fds[n].fd, &read_fds); +			if (fds[n].fd > maxfd) +				maxfd = fds[n].fd; +		} +		if (fds[n].events & POLLOUT) { +			FD_SET(fds[n].fd, &write_fds); +			if (fds[n].fd > maxfd) +				maxfd = fds[n].fd; +		} +	} + +	r = pselect(maxfd + 1, &read_fds, &write_fds, NULL, ts, sigmask); +	if (r > 0) { +		for (n = 0; n < nfds; n++) { +			fds[n].revents = +			    FD_ISSET(fds[n].fd, &read_fds) ? POLLIN : 0; +			if (FD_ISSET(fds[n].fd, &write_fds)) +				fds[n].revents |= POLLOUT; +		} +	} + +	return r; +} +#endif + +unsigned long long +eloop_timespec_diff(const struct timespec *tsp, const struct timespec *usp, +    unsigned int *nsp) +{ +	unsigned long long tsecs, usecs, secs; +	long nsecs; + +	if (tsp->tv_sec < 0) /* time wreapped */ +		tsecs = UTIME_MAX - (unsigned long long)(-tsp->tv_sec); +	else +		tsecs = (unsigned long long)tsp->tv_sec; +	if (usp->tv_sec < 0) /* time wrapped */ +		usecs = UTIME_MAX - (unsigned long long)(-usp->tv_sec); +	else +		usecs = (unsigned long long)usp->tv_sec; + +	if (usecs > tsecs) /* time wrapped */ +		secs = (UTIME_MAX - usecs) + tsecs; +	else +		secs = tsecs - usecs; + +	nsecs = tsp->tv_nsec - usp->tv_nsec; +	if (nsecs < 0) { +		if (secs == 0) +			nsecs = 0; +		else { +			secs--; +			nsecs += NSEC_PER_SEC; +		} +	} +	if (nsp != NULL) +		*nsp = (unsigned int)nsecs; +	return secs; +} + +static void +eloop_reduce_timers(struct eloop *eloop) +{ +	struct timespec now; +	unsigned long long secs; +	unsigned int nsecs; +	struct eloop_timeout *t; + +	clock_gettime(CLOCK_MONOTONIC, &now); +	secs = eloop_timespec_diff(&now, &eloop->now, &nsecs); + +	TAILQ_FOREACH(t, &eloop->timeouts, next) { +		if (secs > t->seconds) { +			t->seconds = 0; +			t->nseconds = 0; +		} else { +			t->seconds -= (unsigned int)secs; +			if (nsecs > t->nseconds) { +				if (t->seconds == 0) +					t->nseconds = 0; +				else { +					t->seconds--; +					t->nseconds = NSEC_PER_SEC +					    - (nsecs - t->nseconds); +				} +			} else +				t->nseconds -= nsecs; +		} +	} + +	eloop->now = now; +} + +static void +eloop_event_setup_fds(struct eloop *eloop) +{ +	struct eloop_event *e, *ne; +	struct pollfd *pfd; + +	pfd = eloop->fds; +	TAILQ_FOREACH_SAFE(e, &eloop->events, next, ne) { +		if (e->fd == -1) { +			TAILQ_REMOVE(&eloop->events, e, next); +			TAILQ_INSERT_TAIL(&eloop->free_events, e, next); +			continue; +		} +#ifdef ELOOP_DEBUG +		fprintf(stderr, "%s(%d) fd=%d, rcb=%p, wcb=%p\n", +		    __func__, getpid(), e->fd, e->read_cb, e->write_cb); +#endif +		e->pollfd = pfd; +		pfd->fd = e->fd; +		pfd->events = 0; +		if (e->read_cb != NULL) +			pfd->events |= POLLIN; +		if (e->write_cb != NULL) +			pfd->events |= POLLOUT; +		pfd->revents = 0; +		pfd++; +	} +	eloop->events_need_setup = false; +} + +size_t +eloop_event_count(const struct eloop *eloop) +{ + +	return eloop->nevents; +} + +int +eloop_event_add_rw(struct eloop *eloop, int fd, +    void (*read_cb)(void *), void *read_cb_arg, +    void (*write_cb)(void *), void *write_cb_arg) +{ +	struct eloop_event *e; +	struct pollfd *pfd; + +	assert(eloop != NULL); +	assert(read_cb != NULL || write_cb != NULL); +	if (fd == -1) { +		errno = EINVAL; +		return -1; +	} + +	TAILQ_FOREACH(e, &eloop->events, next) { +		if (e->fd == fd) +			break; +	} + +	if (e == NULL) { +		if (eloop->nevents + 1 > eloop->nfds) { +			pfd = eloop_realloca(eloop->fds, eloop->nevents + 1, +			    sizeof(*pfd)); +			if (pfd == NULL) +				return -1; +			eloop->fds = pfd; +			eloop->nfds++; +		} + +		e = TAILQ_FIRST(&eloop->free_events); +		if (e != NULL) +			TAILQ_REMOVE(&eloop->free_events, e, next); +		else { +			e = malloc(sizeof(*e)); +			if (e == NULL) +				return -1; +		} +		TAILQ_INSERT_HEAD(&eloop->events, e, next); +		eloop->nevents++; +		e->fd = fd; +		e->read_cb = read_cb; +		e->read_cb_arg = read_cb_arg; +		e->write_cb = write_cb; +		e->write_cb_arg = write_cb_arg; +		goto setup; +	} + +	if (read_cb) { +		e->read_cb = read_cb; +		e->read_cb_arg = read_cb_arg; +	} +	if (write_cb) { +		e->write_cb = write_cb; +		e->write_cb_arg = write_cb_arg; +	} + +setup: +	e->pollfd = NULL; +	eloop->events_need_setup = true; +	return 0; +} + +int +eloop_event_add(struct eloop *eloop, int fd, +    void (*read_cb)(void *), void *read_cb_arg) +{ + +	return eloop_event_add_rw(eloop, fd, read_cb, read_cb_arg, NULL, NULL); +} + +int +eloop_event_add_w(struct eloop *eloop, int fd, +    void (*write_cb)(void *), void *write_cb_arg) +{ + +	return eloop_event_add_rw(eloop, fd, NULL,NULL, write_cb, write_cb_arg); +} + +int +eloop_event_delete_write(struct eloop *eloop, int fd, int write_only) +{ +	struct eloop_event *e; + +	assert(eloop != NULL); +	if (fd == -1) { +		errno = EINVAL; +		return -1; +	} + +	TAILQ_FOREACH(e, &eloop->events, next) { +		if (e->fd == fd) +			break; +	} +	if (e == NULL) { +		errno = ENOENT; +		return -1; +	} + +	if (write_only) { +		if (e->read_cb == NULL) +			goto remove; +		e->write_cb = NULL; +		e->write_cb_arg = NULL; +		if (e->pollfd != NULL) { +			e->pollfd->events &= ~POLLOUT; +			e->pollfd->revents &= ~POLLOUT; +		} +		return 1; +	} + +remove: +	e->fd = -1; +	eloop->nevents--; +	eloop->events_need_setup = true; +	return 1; +} + +/* + * This implementation should cope with UINT_MAX seconds on a system + * where time_t is INT32_MAX. It should also cope with the monotonic timer + * wrapping, although this is highly unlikely. + * unsigned int should match or be greater than any on wire specified timeout. + */ +static int +eloop_q_timeout_add(struct eloop *eloop, int queue, +    unsigned int seconds, unsigned int nseconds, +    void (*callback)(void *), void *arg) +{ +	struct eloop_timeout *t, *tt = NULL; + +	assert(eloop != NULL); +	assert(callback != NULL); +	assert(nseconds <= NSEC_PER_SEC); + +	/* Remove existing timeout if present. */ +	TAILQ_FOREACH(t, &eloop->timeouts, next) { +		if (t->callback == callback && t->arg == arg) { +			TAILQ_REMOVE(&eloop->timeouts, t, next); +			break; +		} +	} + +	if (t == NULL) { +		/* No existing, so allocate or grab one from the free pool. */ +		if ((t = TAILQ_FIRST(&eloop->free_timeouts))) { +			TAILQ_REMOVE(&eloop->free_timeouts, t, next); +		} else { +			if ((t = malloc(sizeof(*t))) == NULL) +				return -1; +		} +	} + +	eloop_reduce_timers(eloop); + +	t->seconds = seconds; +	t->nseconds = nseconds; +	t->callback = callback; +	t->arg = arg; +	t->queue = queue; + +	/* The timeout list should be in chronological order, +	 * soonest first. */ +	TAILQ_FOREACH(tt, &eloop->timeouts, next) { +		if (t->seconds < tt->seconds || +		    (t->seconds == tt->seconds && t->nseconds < tt->nseconds)) +		{ +			TAILQ_INSERT_BEFORE(tt, t, next); +			return 0; +		} +	} +	TAILQ_INSERT_TAIL(&eloop->timeouts, t, next); +	return 0; +} + +int +eloop_q_timeout_add_tv(struct eloop *eloop, int queue, +    const struct timespec *when, void (*callback)(void *), void *arg) +{ + +	if (when->tv_sec < 0 || (unsigned long)when->tv_sec > UINT_MAX) { +		errno = EINVAL; +		return -1; +	} +	if (when->tv_nsec < 0 || when->tv_nsec > NSEC_PER_SEC) { +		errno = EINVAL; +		return -1; +	} + +	return eloop_q_timeout_add(eloop, queue, +	    (unsigned int)when->tv_sec, (unsigned int)when->tv_sec, +	    callback, arg); +} + +int +eloop_q_timeout_add_sec(struct eloop *eloop, int queue, unsigned int seconds, +    void (*callback)(void *), void *arg) +{ + +	return eloop_q_timeout_add(eloop, queue, seconds, 0, callback, arg); +} + +int +eloop_q_timeout_add_msec(struct eloop *eloop, int queue, unsigned long when, +    void (*callback)(void *), void *arg) +{ +	unsigned long seconds, nseconds; + +	seconds = when / MSEC_PER_SEC; +	if (seconds > UINT_MAX) { +		errno = EINVAL; +		return -1; +	} + +	nseconds = (when % MSEC_PER_SEC) * NSEC_PER_MSEC; +	return eloop_q_timeout_add(eloop, queue, +		(unsigned int)seconds, (unsigned int)nseconds, callback, arg); +} + +int +eloop_q_timeout_delete(struct eloop *eloop, int queue, +    void (*callback)(void *), void *arg) +{ +	struct eloop_timeout *t, *tt; +	int n; + +	assert(eloop != NULL); + +	n = 0; +	TAILQ_FOREACH_SAFE(t, &eloop->timeouts, next, tt) { +		if ((queue == 0 || t->queue == queue) && +		    t->arg == arg && +		    (!callback || t->callback == callback)) +		{ +			TAILQ_REMOVE(&eloop->timeouts, t, next); +			TAILQ_INSERT_TAIL(&eloop->free_timeouts, t, next); +			n++; +		} +	} +	return n; +} + +void +eloop_exit(struct eloop *eloop, int code) +{ + +	assert(eloop != NULL); + +	eloop->exitcode = code; +	eloop->exitnow = 1; +} + +void +eloop_enter(struct eloop *eloop) +{ + +	eloop->exitnow = 0; +} + +void +eloop_signal_set_cb(struct eloop *eloop, +    const int *signals, size_t signals_len, +    void (*signal_cb)(int, void *), void *signal_cb_ctx) +{ + +	assert(eloop != NULL); + +	eloop->signals = signals; +	eloop->signals_len = signals_len; +	eloop->signal_cb = signal_cb; +	eloop->signal_cb_ctx = signal_cb_ctx; +} + +static volatile int _eloop_sig[ELOOP_NSIGNALS]; +static volatile size_t _eloop_nsig; + +static void +eloop_signal3(int sig, __unused siginfo_t *siginfo, __unused void *arg) +{ + +	if (_eloop_nsig == __arraycount(_eloop_sig)) { +#ifdef ELOOP_DEBUG +		fprintf(stderr, "%s: signal storm, discarding signal %d\n", +		    __func__, sig); +#endif +		return; +	} + +	_eloop_sig[_eloop_nsig++] = sig; +} + +int +eloop_signal_mask(struct eloop *eloop, sigset_t *oldset) +{ +	sigset_t newset; +	size_t i; +	struct sigaction sa = { +	    .sa_sigaction = eloop_signal3, +	    .sa_flags = SA_SIGINFO, +	}; + +	assert(eloop != NULL); + +	sigemptyset(&newset); +	for (i = 0; i < eloop->signals_len; i++) +		sigaddset(&newset, eloop->signals[i]); +	if (sigprocmask(SIG_SETMASK, &newset, oldset) == -1) +		return -1; + +	sigemptyset(&sa.sa_mask); + +	for (i = 0; i < eloop->signals_len; i++) { +		if (sigaction(eloop->signals[i], &sa, NULL) == -1) +			return -1; +	} +	return 0; +} + +struct eloop * +eloop_new(void) +{ +	struct eloop *eloop; + +	eloop = calloc(1, sizeof(*eloop)); +	if (eloop == NULL) +		return NULL; + +	/* Check we have a working monotonic clock. */ +	if (clock_gettime(CLOCK_MONOTONIC, &eloop->now) == -1) { +		free(eloop); +		return NULL; +	} + +	TAILQ_INIT(&eloop->events); +	TAILQ_INIT(&eloop->free_events); +	TAILQ_INIT(&eloop->timeouts); +	TAILQ_INIT(&eloop->free_timeouts); +	eloop->exitcode = EXIT_FAILURE; + +	return eloop; +} + +void +eloop_clear(struct eloop *eloop) +{ +	struct eloop_event *e; +	struct eloop_timeout *t; + +	if (eloop == NULL) +		return; + +	eloop->nevents = 0; +	eloop->signals = NULL; +	eloop->signals_len = 0; + +	while ((e = TAILQ_FIRST(&eloop->events))) { +		TAILQ_REMOVE(&eloop->events, e, next); +		free(e); +	} +	while ((e = TAILQ_FIRST(&eloop->free_events))) { +		TAILQ_REMOVE(&eloop->free_events, e, next); +		free(e); +	} +	while ((t = TAILQ_FIRST(&eloop->timeouts))) { +		TAILQ_REMOVE(&eloop->timeouts, t, next); +		free(t); +	} +	while ((t = TAILQ_FIRST(&eloop->free_timeouts))) { +		TAILQ_REMOVE(&eloop->free_timeouts, t, next); +		free(t); +	} + +	free(eloop->fds); +	eloop->fds = NULL; +	eloop->nfds = 0; +} + +void +eloop_free(struct eloop *eloop) +{ + +	eloop_clear(eloop); +	free(eloop); +} + +int +eloop_start(struct eloop *eloop, sigset_t *signals) +{ +	int n; +	struct eloop_event *e; +	struct eloop_timeout *t; +	struct timespec ts, *tsp; + +	assert(eloop != NULL); + +	for (;;) { +		if (eloop->exitnow) +			break; + +		if (_eloop_nsig != 0) { +			n = _eloop_sig[--_eloop_nsig]; +			if (eloop->signal_cb != NULL) +				eloop->signal_cb(n, eloop->signal_cb_ctx); +			continue; +		} + +		t = TAILQ_FIRST(&eloop->timeouts); +		if (t == NULL && eloop->nevents == 0) +			break; + +		if (t != NULL) +			eloop_reduce_timers(eloop); + +		if (t != NULL && t->seconds == 0 && t->nseconds == 0) { +			TAILQ_REMOVE(&eloop->timeouts, t, next); +			t->callback(t->arg); +			TAILQ_INSERT_TAIL(&eloop->free_timeouts, t, next); +			continue; +		} + +		if (t != NULL) { +			if (t->seconds > INT_MAX) { +				ts.tv_sec = (time_t)INT_MAX; +				ts.tv_nsec = 0; +			} else { +				ts.tv_sec = (time_t)t->seconds; +				ts.tv_nsec = (long)t->nseconds; +			} +			tsp = &ts; +		} else +			tsp = NULL; + +		if (eloop->events_need_setup) +			eloop_event_setup_fds(eloop); + +		n = ppoll(eloop->fds, (nfds_t)eloop->nevents, tsp, signals); +		if (n == -1) { +			if (errno == EINTR) +				continue; +			return -errno; +		} +		if (n == 0) +			continue; + +		TAILQ_FOREACH(e, &eloop->events, next) { +			/* Skip freshly added events */ +			if (e->pollfd == NULL) +				continue; +			if (e->pollfd->revents) +				n--; +			if (e->fd != -1 && e->pollfd->revents & POLLOUT) { +				if (e->write_cb != NULL) +					e->write_cb(e->write_cb_arg); +			} +			if (e->fd != -1 && +			    e->pollfd != NULL && e->pollfd->revents) +			{ +				if (e->read_cb != NULL) +					e->read_cb(e->read_cb_arg); +			} +			if (n == 0) +				break; +		} +	} + +	return eloop->exitcode; +} | 
