/* fdwatch.c - fd watcher routines, either select() or poll()
**
** Copyright (C) 1995-2015  Jef Poskanzer <jef@mail.acme.com>
** 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 COPYRIGHT HOLDERS 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 COPYRIGHT OWNERS 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 <config.h>

#include <sys/types.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <syslog.h>
#include <fcntl.h>

#ifndef MIN
#define MIN(a,b) ((a) < (b) ? (a) : (b))
#endif

#ifdef HAVE_POLL_H
#include <poll.h>
#else				/* HAVE_POLL_H */
#ifdef HAVE_SYS_POLL_H
#include <sys/poll.h>
#endif				/* HAVE_SYS_POLL_H */
#endif				/* HAVE_POLL_H */

#ifdef HAVE_SYS_DEVPOLL_H
#include <sys/devpoll.h>
#ifndef HAVE_DEVPOLL
#define HAVE_DEVPOLL
#endif				/* !HAVE_DEVPOLL */
#endif				/* HAVE_SYS_DEVPOLL_H */

#ifdef HAVE_SYS_EPOLL_H
#include <sys/epoll.h>
#include <errno.h>
#endif

#ifdef HAVE_SYS_EVENT_H
#include <sys/event.h>
#endif				/* HAVE_SYS_EVENT_H */

#include "fdwatch.h"

#ifdef HAVE_SELECT
#ifndef FD_SET
#define NFDBITS         32
#define FD_SETSIZE      32
#define FD_SET(n, p)    ((p)->fds_bits[(n)/NFDBITS] |= (1 << ((n) % NFDBITS)))
#define FD_CLR(n, p)    ((p)->fds_bits[(n)/NFDBITS] &= ~(1 << ((n) % NFDBITS)))
#define FD_ISSET(n, p)  ((p)->fds_bits[(n)/NFDBITS] & (1 << ((n) % NFDBITS)))
#define FD_ZERO(p)      memset((char *)(p), 0, sizeof(*(p)))
#endif				/* !FD_SET */
#endif				/* HAVE_SELECT */

static int nfiles;
static long nwatches;
static int *fd_rw;
static void **fd_data;
static int nreturned, next_ridx;

#ifdef HAVE_KQUEUE

#define WHICH                  "kevent"
#define INIT(nfiles)           kqueue_init(nfiles)
#define EXIT()                 kqueue_exit()
#define ADD_FD(fd, rw)         kqueue_add_fd(fd, rw)
#define DEL_FD(fd)             kqueue_del_fd(fd)
#define WATCH(timeout_msecs)   kqueue_watch(timeout_msecs)
#define CHECK_FD(fd)           kqueue_check_fd(fd)
#define GET_FD(ridx)           kqueue_get_fd(ridx)

static int kqueue_init(int nfiles);
static void kqueue_exit(void);
static void kqueue_add_fd(int fd, int rw);
static void kqueue_del_fd(int fd);
static int kqueue_watch(long timeout_msecs);
static int kqueue_check_fd(int fd);
static int kqueue_get_fd(int ridx);

#else				/* HAVE_KQUEUE */

# ifdef HAVE_EPOLL_CREATE1

#define WHICH                  "epoll"
#define INIT(nfiles)           epoll_init(nfiles)
#define EXIT()                 epoll_exit()
#define ADD_FD(fd, rw)         epoll_add_fd(fd, rw)
#define DEL_FD(fd)             epoll_del_fd(fd)
#define WATCH(timeout_msecs)   epoll_watch(timeout_msecs)
#define CHECK_FD(fd)           epoll_check_fd(fd)
#define GET_FD(ridx)           epoll_get_fd(ridx)

static int epoll_init(int nfiles);
static void epoll_exit(void);
static void epoll_add_fd(int fd, int rw);
static void epoll_del_fd(int fd);
static int epoll_watch(long timeout_msecs);
static int epoll_check_fd(int fd);
static int epoll_get_fd(int ridx);

# else /* HAVE_EPOLL_CREATE1 */
#  ifdef HAVE_DEVPOLL

#define WHICH                  "devpoll"
#define INIT(nfiles)           devpoll_init(nfiles)
#define EXIT()                 devpoll_exit()
#define ADD_FD(fd, rw)         devpoll_add_fd(fd, rw)
#define DEL_FD(fd)             devpoll_del_fd(fd)
#define WATCH(timeout_msecs)   devpoll_watch(timeout_msecs)
#define CHECK_FD(fd)           devpoll_check_fd(fd)
#define GET_FD(ridx)           devpoll_get_fd(ridx)

static int devpoll_init(int nfiles);
static void devpoll_exit(void);
static void devpoll_add_fd(int fd, int rw);
static void devpoll_del_fd(int fd);
static int devpoll_watch(long timeout_msecs);
static int devpoll_check_fd(int fd);
static int devpoll_get_fd(int ridx);

#  else				/* HAVE_DEVPOLL */
#   ifdef HAVE_POLL

#define WHICH                  "poll"
#define INIT(nfiles)           poll_init(nfiles)
#define EXIT()                 poll_exit()
#define ADD_FD(fd, rw)         poll_add_fd(fd, rw)
#define DEL_FD(fd)             poll_del_fd(fd)
#define WATCH(timeout_msecs)   poll_watch(timeout_msecs)
#define CHECK_FD(fd)           poll_check_fd(fd)
#define GET_FD(ridx)           poll_get_fd(ridx)

static int poll_init(int nfiles);
static void poll_exit(void);
static void poll_add_fd(int fd, int rw);
static void poll_del_fd(int fd);
static int poll_watch(long timeout_msecs);
static int poll_check_fd(int fd);
static int poll_get_fd(int ridx);

#   else				/* HAVE_POLL */
#    ifdef HAVE_SELECT

#define WHICH                  "select"
#define INIT(nfiles)           select_init(nfiles)
#define EXIT()                 select_exit()
#define ADD_FD(fd, rw)         select_add_fd(fd, rw)
#define DEL_FD(fd)             select_del_fd(fd)
#define WATCH(timeout_msecs)   select_watch(timeout_msecs)
#define CHECK_FD(fd)           select_check_fd(fd)
#define GET_FD(ridx)           select_get_fd(ridx)

static int select_init(int nfiles);
static void select_exit(void);
static void select_add_fd(int fd, int rw);
static void select_del_fd(int fd);
static int select_watch(long timeout_msecs);
static int select_check_fd(int fd);
static int select_get_fd(int ridx);

#    endif			/* HAVE_SELECT */
#   endif			/* HAVE_POLL */
#  endif			/* HAVE_DEVPOLL */
# endif /* HAVE_EPOLL_CREATE1 */
#endif				/* HAVE_KQUEUE */


/* Routines. */

/* Figure out how many file descriptors the system allows, and
** initialize the fdwatch data structures.  Returns -1 on failure.
*/
int fdwatch_get_nfiles(void)
{
	int i;
#ifdef RLIMIT_NOFILE
	struct rlimit rl;
#endif

	/* Figure out how many fd's we can have. */
	nfiles = sysconf(_SC_OPEN_MAX);
	if (nfiles == -1) {
		/* Fallback if sysconf fails */
		nfiles = 1024;
	}
#ifdef RLIMIT_NOFILE
	/* If we have getrlimit(), use that, and attempt to raise the limit. */
	if (getrlimit(RLIMIT_NOFILE, &rl) == 0) {
		nfiles = rl.rlim_cur;
		if (rl.rlim_max == RLIM_INFINITY)
			rl.rlim_cur = 8192;	/* arbitrary */
		else if (rl.rlim_max > rl.rlim_cur)
			rl.rlim_cur = rl.rlim_max;
		if (setrlimit(RLIMIT_NOFILE, &rl) == 0)
			nfiles = rl.rlim_cur;
	}
#endif

#if defined(HAVE_SELECT) && ! ( defined(HAVE_POLL) || defined(HAVE_DEVPOLL) || defined(HAVE_EPOLL_CREATE1) || defined(HAVE_KQUEUE) )
	/* If we use select(), then we must limit ourselves to FD_SETSIZE. */
	nfiles = MIN(nfiles, FD_SETSIZE);
#endif

	/* Initialize the fdwatch data structures. */
	nwatches = 0;

	fd_rw = (int *)malloc(sizeof(int) * nfiles);
	if (!fd_rw)
		return -1;

	fd_data = (void **)malloc(sizeof(void *) * nfiles);
	if (!fd_data) {
		free(fd_rw);
		return -1;
	}

	for (i = 0; i < nfiles; ++i)
		fd_rw[i] = -1;

	if (INIT(nfiles) == -1) {
		free(fd_rw);
		free(fd_data);
		return -1;
	}

	return nfiles;
}

void fdwatch_put_nfiles(void)
{
	free(fd_rw);
	free(fd_data);
	EXIT();
}

/* Add a descriptor to the watch list.  rw is either FDW_READ or FDW_WRITE.  */
void fdwatch_add_fd(int fd, void *arg, int rw)
{
	if (fd < 0 || fd >= nfiles || fd_rw[fd] != -1) {
		syslog(LOG_ERR, "bad fd (%d) passed to fdwatch_add_fd(nfiles:%d)!", fd, nfiles);
		return;
	}

	ADD_FD(fd, rw);
	fd_rw[fd] = rw;
	fd_data[fd] = arg;
}


/* Remove a descriptor from the watch list. */
void fdwatch_del_fd(int fd)
{
	if (fd < 0 || fd >= nfiles || fd_rw[fd] == -1) {
		syslog(LOG_ERR, "bad fd (%d) passed to fdwatch_del_fd!", fd);
		return;
	}

	DEL_FD(fd);
	fd_rw[fd] = -1;
	fd_data[fd] = NULL;
}

/* Do the watch.  Return value is the number of descriptors that are ready,
** or 0 if the timeout expired, or -1 on errors.  A timeout of INFTIM means
** wait indefinitely.
*/
int fdwatch(long timeout_msecs)
{
	++nwatches;
	nreturned = WATCH(timeout_msecs);
	next_ridx = 0;

	return nreturned;
}


/* Check if a descriptor was ready. */
int fdwatch_check_fd(int fd)
{
	if (fd < 0 || fd >= nfiles || fd_rw[fd] == -1) {
		syslog(LOG_ERR, "bad fd (%d) passed to fdwatch_check_fd!", fd);
		return 0;
	}

	return CHECK_FD(fd);
}


void *fdwatch_get_next_arg(void)
{
	int fd;

	if (next_ridx >= nreturned)
		return (void *)-1;

	fd = GET_FD(next_ridx++);
	if (fd < 0 || fd >= nfiles)
		return NULL;

	return fd_data[fd];
}


/* Generate debugging statistics syslog message. */
void fdwatch_logstats(long secs)
{
	if (secs > 0)
		syslog(LOG_INFO, "  fdwatch - %ld %ss (%g/sec)", nwatches, WHICH, (float)nwatches / secs);
	nwatches = 0;
}


#ifdef HAVE_KQUEUE

static int maxkqevents;
static struct kevent *kqevents;
static int nkqevents;
static struct kevent *kqrevents;
static int *kqrfdidx;
static int kq;


static int kqueue_init(int nfiles)
{
	kq = kqueue();
	if (kq == -1)
		return -1;

	maxkqevents = nfiles * 2;
	kqevents = (struct kevent *)calloc(maxkqevents, sizeof(struct kevent));
	if (!kqevents)
		return -1;

	kqrevents = (struct kevent *)calloc(nfiles, sizeof(struct kevent));
	if (!kqrevents) {
		free(kqevents);
		return -1;
	}

	kqrfdidx = (int *)calloc(nfiles, sizeof(int));
	if (!kqrfdidx) {
		free(kqevents);
		free(kqrevents);
		return -1;
	}

	return 0;
}


static void kqueue_exit(void)
{
	free(kqevents);
	free(kqrevents);
	free(kqrfdidx);
}


static void kqueue_add_fd(int fd, int rw)
{
	if (nkqevents >= maxkqevents) {
		syslog(LOG_ERR, "too many kqevents in kqueue_add_fd!");
		return;
	}

	kqevents[nkqevents].ident = fd;
	kqevents[nkqevents].flags = EV_ADD;
	switch (rw) {
	case FDW_READ:
		kqevents[nkqevents].filter = EVFILT_READ;
		break;

	case FDW_WRITE:
		kqevents[nkqevents].filter = EVFILT_WRITE;
		break;

	default:
		break;
	}

	++nkqevents;
}


static void kqueue_del_fd(int fd)
{
	if (nkqevents >= maxkqevents) {
		syslog(LOG_ERR, "too many kqevents in kqueue_del_fd!");
		return;
	}

	kqevents[nkqevents].ident = fd;
	kqevents[nkqevents].flags = EV_DELETE;
	switch (fd_rw[fd]) {
	case FDW_READ:
		kqevents[nkqevents].filter = EVFILT_READ;
		break;

	case FDW_WRITE:
		kqevents[nkqevents].filter = EVFILT_WRITE;
		break;
	}

	++nkqevents;
}


static int kqueue_watch(long timeout_msecs)
{
	int i, r;

	if (timeout_msecs == INFTIM) {
		r = kevent(kq, kqevents, nkqevents, kqrevents, nfiles, NULL);
	} else {
		struct timespec ts;

		ts.tv_sec = timeout_msecs / 1000L;
		ts.tv_nsec = (timeout_msecs % 1000L) * 1000000L;
		r = kevent(kq, kqevents, nkqevents, kqrevents, nfiles, &ts);
	}

	nkqevents = 0;
	if (r == -1)
		return -1;

	for (i = 0; i < r; ++i)
		kqrfdidx[kqrevents[i].ident] = i;

	return r;
}


static int kqueue_check_fd(int fd)
{
	int ridx = kqrfdidx[fd];

	if (ridx < 0 || ridx >= nfiles) {
		syslog(LOG_ERR, "bad ridx (%d) in kqueue_check_fd!", ridx);
		return 0;
	}

	if (ridx >= nreturned)
		return 0;

	if ((int)kqrevents[ridx].ident != fd)
		return 0;

	if (kqrevents[ridx].flags & EV_ERROR)
		return 0;

	switch (fd_rw[fd]) {
	case FDW_READ:
		return kqrevents[ridx].filter == EVFILT_READ;

	case FDW_WRITE:
		return kqrevents[ridx].filter == EVFILT_WRITE;
	}

	return 0;
}


static int kqueue_get_fd(int ridx)
{
	if (ridx < 0 || ridx >= nfiles) {
		syslog(LOG_ERR, "bad ridx (%d) in kqueue_get_fd!", ridx);
		return -1;
	}

	return kqrevents[ridx].ident;
}

#else /* HAVE_KQUEUE */


# ifdef HAVE_EPOLL_CREATE1

static int epollfd;
static struct epoll_event *epoll_revents;
static int *epoll_rfdidx;

static int epoll_init(int nfiles)
{
	int i;

	epollfd = epoll_create1(EPOLL_CLOEXEC);
	if (epollfd == -1)
		return -1;

	epoll_revents = calloc(nfiles, sizeof(struct epoll_event));
	if (!epoll_revents) {
		close(epollfd);
		return -1;
	}

	epoll_rfdidx = malloc(nfiles * sizeof(int));
	if (!epoll_rfdidx) {
		free(epoll_revents);
		close(epollfd);
		return -1;
	}

	for (i = 0; i < nfiles; i++)
		epoll_rfdidx[i] = -1;

	return 0;
}

static void epoll_exit(void)
{
	free(epoll_revents);
	free(epoll_rfdidx);
	close(epollfd);
}

static void epoll_add_fd(int fd, int rw)
{
	struct epoll_event ev;

	ev.data.fd = fd;
	ev.events  = (rw == FDW_READ) ? EPOLLIN : EPOLLOUT;
	if (epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &ev) < 0)
		syslog(LOG_ERR, "epoll_ctl(ADD, %d): %s", fd, strerror(errno));
}

static void epoll_del_fd(int fd)
{
	if (epoll_ctl(epollfd, EPOLL_CTL_DEL, fd, NULL) < 0)
		syslog(LOG_ERR, "epoll_ctl(DEL, %d): %s", fd, strerror(errno));
	epoll_rfdidx[fd] = -1;
}

static int epoll_watch(long timeout_msecs)
{
	int i, r;

	r = epoll_wait(epollfd, epoll_revents, nfiles, (int)timeout_msecs);
	if (r <= 0)
		return r;

	for (i = 0; i < r; i++)
		epoll_rfdidx[epoll_revents[i].data.fd] = i;

	return r;
}

static int epoll_check_fd(int fd)
{
	int ridx = epoll_rfdidx[fd];

	if (ridx < 0 || ridx >= nreturned)
		return 0;

	if (epoll_revents[ridx].data.fd != fd)
		return 0;

	if (epoll_revents[ridx].events & EPOLLERR)
		return 0;

	switch (fd_rw[fd]) {
	case FDW_READ:
		return epoll_revents[ridx].events & (EPOLLIN | EPOLLHUP | EPOLLRDHUP);

	case FDW_WRITE:
		return epoll_revents[ridx].events & (EPOLLOUT | EPOLLHUP);
	}

	return 0;
}

static int epoll_get_fd(int ridx)
{
	if (ridx < 0 || ridx >= nreturned) {
		syslog(LOG_ERR, "bad ridx (%d) in epoll_get_fd!", ridx);
		return -1;
	}

	return epoll_revents[ridx].data.fd;
}

# else /* HAVE_EPOLL_CREATE1 */
#  ifdef HAVE_DEVPOLL

static int maxdpevents;
static struct pollfd *dpevents;
static int ndpevents;
static struct pollfd *dprevents;
static int *dp_rfdidx;
static int dp;


static int devpoll_init(int nfiles)
{
	dp = open("/dev/poll", O_RDWR);
	if (dp == -1)
		return -1;

	fcntl(dp, F_SETFD, 1);

	maxdpevents = nfiles * 2;
	dpevents = (struct pollfd *)calloc(sizeof(maxdpevents, struct pollfd));
	if (!dpevents) {
		close(dp);
		return -1;
	}

	dprevents = (struct pollfd *)calloc(nfiles, sizeof(struct pollfd));
	if (!dprevents) {
		close(dp);
		free(dpevents);
		return -1;
	}

	dp_rfdidx = (int *)calloc(nfiles, sizeof(int));
	if (!dp_rfdidx) {
		close(dp);
		free(dpevents);
		free(dprevents);
		return -1;
	}

	return 0;
}


static void devpoll_exit(void)
{
	free(dpevents);
	free(dprevents);
	free(dp_rfdidx);
}


static void devpoll_add_fd(int fd, int rw)
{
	if (ndpevents >= maxdpevents) {
		syslog(LOG_ERR, "too many fds in devpoll_add_fd!");
		return;
	}

	dpevents[ndpevents].fd = fd;
	switch (rw) {
	case FDW_READ:
		dpevents[ndpevents].events = POLLIN;
		break;

	case FDW_WRITE:
		dpevents[ndpevents].events = POLLOUT;
		break;
	}

	++ndpevents;
}


static void devpoll_del_fd(int fd)
{
	if (ndpevents >= maxdpevents) {
		syslog(LOG_ERR, "too many fds in devpoll_del_fd!");
		return;
	}

	dpevents[ndpevents].fd = fd;
	dpevents[ndpevents].events = POLLREMOVE;
	++ndpevents;
}


static int devpoll_watch(long timeout_msecs)
{
	int i, r;
	struct dvpoll dvp;

	r = sizeof(struct pollfd) * ndpevents;
	if (r > 0 && write(dp, dpevents, r) != r)
		return -1;

	ndpevents = 0;
	dvp.dp_fds = dprevents;
	dvp.dp_nfds = nfiles;
	dvp.dp_timeout = (int)timeout_msecs;

	r = ioctl(dp, DP_POLL, &dvp);
	if (r == -1)
		return -1;

	for (i = 0; i < r; ++i)
		dp_rfdidx[dprevents[i].fd] = i;

	return r;
}


static int devpoll_check_fd(int fd)
{
	int ridx = dp_rfdidx[fd];

	if (ridx < 0 || ridx >= nfiles) {
		syslog(LOG_ERR, "bad ridx (%d) in devpoll_check_fd!", ridx);
		return 0;
	}

	if (ridx >= nreturned)
		return 0;

	if (dprevents[ridx].fd != fd)
		return 0;

	if (dprevents[ridx].revents & POLLERR)
		return 0;

	switch (fd_rw[fd]) {
	case FDW_READ:
		return dprevents[ridx].revents & (POLLIN | POLLHUP | POLLNVAL);

	case FDW_WRITE:
		return dprevents[ridx].revents & (POLLOUT | POLLHUP | POLLNVAL);
	}

	return 0;
}


static int devpoll_get_fd(int ridx)
{
	if (ridx < 0 || ridx >= nfiles) {
		syslog(LOG_ERR, "bad ridx (%d) in devpoll_get_fd!", ridx);
		return -1;
	}

	return dprevents[ridx].fd;
}


# else /* HAVE_DEVPOLL */


#  ifdef HAVE_POLL

static struct pollfd *pollfds;
static int npoll_fds;
static int *poll_fdidx;
static int *poll_rfdidx;


static int poll_init(int nfiles)
{
	int i;

	pollfds = (struct pollfd *)malloc(sizeof(struct pollfd) * nfiles);
	if (!pollfds)
		return -1;

	poll_fdidx = (int *)malloc(sizeof(int) * nfiles);
	if (!poll_fdidx) {
		free(pollfds);
		return -1;
	}

	poll_rfdidx = (int *)malloc(sizeof(int) * nfiles);
	if (!poll_rfdidx) {
		free(pollfds);
		free(poll_fdidx);
		return -1;
	}

	for (i = 0; i < nfiles; ++i)
		pollfds[i].fd = poll_fdidx[i] = poll_rfdidx[i] = -1;

	return 0;
}


static void poll_exit(void)
{
	free(pollfds);
	free(poll_fdidx);
	free(poll_rfdidx);
}


static void poll_add_fd(int fd, int rw)
{
	if (npoll_fds >= nfiles) {
		syslog(LOG_ERR, "too many fds in poll_add_fd!");
		return;
	}

	pollfds[npoll_fds].fd = fd;
	switch (rw) {
	case FDW_READ:
		pollfds[npoll_fds].events = POLLIN;
		break;

	case FDW_WRITE:
		pollfds[npoll_fds].events = POLLOUT;
		break;

	default:
		break;
	}

	poll_fdidx[fd] = npoll_fds;
	++npoll_fds;
}


static void poll_del_fd(int fd)
{
	int idx = poll_fdidx[fd];

	if (idx < 0 || idx >= nfiles) {
		syslog(LOG_ERR, "bad idx (%d) in poll_del_fd!", idx);
		return;
	}

	--npoll_fds;
	pollfds[idx] = pollfds[npoll_fds];
	poll_fdidx[pollfds[idx].fd] = idx;
	pollfds[npoll_fds].fd = -1;
	poll_fdidx[fd] = -1;
}


static int poll_watch(long timeout_msecs)
{
	int r, ridx, i;

	r = poll(pollfds, npoll_fds, (int)timeout_msecs);
	if (r <= 0)
		return r;

	ridx = 0;
	for (i = 0; i < npoll_fds; ++i) {
		if (pollfds[i].revents & (POLLIN | POLLOUT | POLLERR | POLLHUP | POLLNVAL)) {
			poll_rfdidx[ridx++] = pollfds[i].fd;
			if (ridx == r)
				break;
		}
	}

	return ridx;		/* should be equal to r */
}


static int poll_check_fd(int fd)
{
	int fdidx = poll_fdidx[fd];

	if (fdidx < 0 || fdidx >= nfiles) {
		syslog(LOG_ERR, "bad fdidx (%d) in poll_check_fd!", fdidx);
		return 0;
	}

	if (pollfds[fdidx].revents & POLLERR)
		return 0;

	switch (fd_rw[fd]) {
	case FDW_READ:
		return pollfds[fdidx].revents & (POLLIN | POLLHUP | POLLNVAL);

	case FDW_WRITE:
		return pollfds[fdidx].revents & (POLLOUT | POLLHUP | POLLNVAL);
	}

	return 0;
}


static int poll_get_fd(int ridx)
{
	if (ridx < 0 || ridx >= nfiles) {
		syslog(LOG_ERR, "bad ridx (%d) in poll_get_fd!", ridx);
		return -1;
	}

	return poll_rfdidx[ridx];
}

#  else				/* HAVE_POLL */


#   ifdef HAVE_SELECT

static fd_set master_rfdset;
static fd_set master_wfdset;
static fd_set working_rfdset;
static fd_set working_wfdset;
static int *select_fds;
static int *select_fdidx;
static int *select_rfdidx;
static int nselect_fds;
static int maxfd;
static int maxfd_changed;


static int select_init(int nfiles)
{
	int i;

	FD_ZERO(&master_rfdset);
	FD_ZERO(&master_wfdset);
	select_fds = (int *)malloc(sizeof(int) * nfiles);
	if (!select_fds)
		return -1;

	select_fdidx = (int *)malloc(sizeof(int) * nfiles);
	if (!select_fdidx) {
		free(select_fds);
		return -1;
	}

	select_rfdidx = (int *)malloc(sizeof(int) * nfiles);
	if (!select_rfdidx) {
		free(select_fds);
		free(select_fdidx);
		return -1;
	}

	nselect_fds = 0;
	maxfd = -1;
	maxfd_changed = 0;

	for (i = 0; i < nfiles; ++i)
		select_fds[i] = select_fdidx[i] = -1;

	return 0;
}


static void select_exit(void)
{
	free(select_fds);
	free(select_fdidx);
	free(select_rfdidx);
}


static void select_add_fd(int fd, int rw)
{
	if (nselect_fds >= nfiles) {
		syslog(LOG_ERR, "too many fds in select_add_fd!");
		return;
	}

	select_fds[nselect_fds] = fd;
	switch (rw) {
	case FDW_READ:
		FD_SET(fd, &master_rfdset);
		break;

	case FDW_WRITE:
		FD_SET(fd, &master_wfdset);
		break;

	default:
		break;
	}

	if (fd > maxfd)
		maxfd = fd;
	select_fdidx[fd] = nselect_fds;
	++nselect_fds;
}


static void select_del_fd(int fd)
{
	int idx = select_fdidx[fd];

	if (idx < 0 || idx >= nfiles) {
		syslog(LOG_ERR, "bad idx (%d) in select_del_fd!", idx);
		return;
	}

	--nselect_fds;
	select_fds[idx] = select_fds[nselect_fds];
	select_fdidx[select_fds[idx]] = idx;
	select_fds[nselect_fds] = -1;
	select_fdidx[fd] = -1;

	FD_CLR(fd, &master_rfdset);
	FD_CLR(fd, &master_wfdset);

	if (fd >= maxfd)
		maxfd_changed = 1;
}


static int select_get_maxfd(void)
{
	if (maxfd_changed) {
		int i;

		maxfd = -1;
		for (i = 0; i < nselect_fds; ++i) {
			if (select_fds[i] > maxfd)
				maxfd = select_fds[i];
		}
		maxfd_changed = 0;
	}

	return maxfd;
}


static int select_watch(long timeout_msecs)
{
	int mfd;
	int r, idx, ridx;

	working_rfdset = master_rfdset;
	working_wfdset = master_wfdset;
	mfd = select_get_maxfd();
	if (timeout_msecs == INFTIM) {
		r = select(mfd + 1, &working_rfdset, &working_wfdset, NULL, NULL);
	} else {
		struct timeval timeout;

		timeout.tv_sec = timeout_msecs / 1000L;
		timeout.tv_usec = (timeout_msecs % 1000L) * 1000L;
		r = select(mfd + 1, &working_rfdset, &working_wfdset, NULL, &timeout);
	}

	if (r <= 0)
		return r;

	ridx = 0;
	for (idx = 0; idx < nselect_fds; ++idx) {
		if (select_check_fd(select_fds[idx])) {
			select_rfdidx[ridx++] = select_fds[idx];
			if (ridx == r)
				break;
		}
	}

	return ridx;		/* should be equal to r */
}


static int select_check_fd(int fd)
{
	switch (fd_rw[fd]) {
	case FDW_READ:
		return FD_ISSET(fd, &working_rfdset);

	case FDW_WRITE:
		return FD_ISSET(fd, &working_wfdset);
	}

	return 0;
}


static int select_get_fd(int ridx)
{
	if (ridx < 0 || ridx >= nfiles) {
		syslog(LOG_ERR, "bad ridx (%d) in select_get_fd!", ridx);
		return -1;
	}

	return select_rfdidx[ridx];
}

#   endif			/* HAVE_SELECT */

#  endif			/* HAVE_POLL */

#  endif			/* HAVE_DEVPOLL */

# endif /* HAVE_EPOLL_CREATE1 */

#endif				/* HAVE_KQUEUE */
