/*
 * bsd_epoll.c - Linux epoll.h emulation via kevent API
 *
 * Copyright (c) 2009 Boaz Harrosh <bharrosh@panasas.com>
 * All rights reserved.
 *
 * Inspired by code written by Roman Divacky
 * from the freebsd's linux emulation project at linux_epoll.c file
 * However none of that code ended up here.
 *
 * description:
 *    This file implements a workable set of the <sys/epoll.h> header file
 *    on a linux distribution. The implementation translates back and forth from
 *    epoll calls and constants to Kevent calls and constants.
 *
 *   This file is copyrighted under the "New BSD License" (see below) so it can
 *   be included in the tirpc library project.
 *   But I fully expect that if you make any fixes/enhancements to bsd_epoll.c
 *   you shall send these changes to me for inclusion in the next version.
 *   (Or I'll hunt your dreams, and you will not have peace)
 *
 * License: "New BSD License"
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *    * Redistributions of source code must retain the above copyright
 *      notice, this list of conditions and the following disclaimer.
 *    * 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.
 *    * Neither the name of the <organization> nor the
 *      names of its contributors may be used to endorse or promote products
 *      derived from this software without specific prior written permission.
 *
 * 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
 * <COPYRIGHT HOLDER> 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.
 */
#if defined(__FreeBSD__) || defined(__APPLE__)

#include <sys/types.h>
#include <sys/event.h>
#include <sys/time.h>
#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#include <misc/bsd_epoll.h>
#include <rpc/types.h>

/* Create a new epoll file descriptor. */
int
epoll_create(int size)
{
	if (size <= 0)
		return -EINVAL;
	/*
	 * args->size is unused. Linux just tests it
	 * and then forgets it as well.
	 */

	return kqueue();
}

static void
kevent_to_epoll(struct kevent *kevent, struct epoll_event *event)
{
	/* if (kevent->flags & EV_ERROR) {
	   ... Any special event flags on Errors?
	   } */

	memset(event, 0, sizeof(*event));
	switch (kevent->filter) {
	case EVFILT_READ:
		/* in Linux EPOLLIN with Zero Data will signal a close
		 * condition. So do that if EV_EOF.
		 */
		if ((kevent->data > 0) || (kevent->flags & EV_EOF))
			event->events = EPOLLIN;
		break;
	case EVFILT_WRITE:
		if (kevent->data > 0)
			event->events = EPOLLOUT;
		break;
	default:
		fprintf(stderr,
			"Unsupported kevent_to_epoll event=%d"
			" data=%lx flags=0x%x ident=%ld fflags=0x%x\n",
			kevent->filter, (long)kevent->data, kevent->flags,
			(long)kevent->ident, kevent->fflags);
	}
	event->data.ptr = kevent->udata;
}

static void
kevent_to_epoll_arr(struct kevent *kevp, int count,
		    struct epoll_event *eep)
{
	int i;

	for (i = 0; i < count; i++) {
		kevent_to_epoll(&kevp[i], &eep[i]);
		/*printf("eep[%d]=%p\n", i, eep[i].data.ptr); */
	}
}

/*
 * Always create two filters An EVFILT_READ and an EVFILT_WRITE.
 * EV_ENABLE/EV_DISABLE according to epoll flags. On EPOLL_CTL_MOD
 * first delete then add a new. On EPOLL_CTL_DEL remove both.
 */
int
epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)
{
	struct kevent kev[2] = { {.flags = 0}, };
	int read_flags, write_flags;

	switch (op) {
	case EPOLL_CTL_MOD:
		epoll_ctl(epfd, EPOLL_CTL_DEL, fd, event);
		/* fall through */
	case EPOLL_CTL_ADD:
		{
			int flags;

			if ((event->events & EPOLLIN)
			    || event->events & EPOLLPRI)
				read_flags = EV_ENABLE;
			else
				read_flags = EV_DISABLE;

			if (event->events & EPOLLOUT)
				write_flags = EV_ENABLE;
			else
				write_flags = EV_DISABLE;

			flags = EV_ADD;
			if (event->events & EPOLLET)
				flags |= EV_CLEAR;
			if (event->events & EPOLLONESHOT)
				flags |= EV_ONESHOT;

			read_flags |= flags;
			write_flags |= flags;
/*		printf("%s(fd=%d data.ptr=%p\n",
			op == EPOLL_CTL_MOD ? "EPOLL_CTL_MOD" : "EPOLL_CTL_ADD",
			fd ,event->data.ptr);
		printf("    EVFILT_READ(on=%d read_flags=0x%x\n",
			read_flags & EV_ENABLE, read_flags);
		printf("    EVFILT_WRITE(on=%d write_flags=0x%x);\n",
			write_flags & EV_ENABLE, write_flags);*/
		}
		break;
	case EPOLL_CTL_DEL:
		read_flags = write_flags = EV_DELETE | EV_DISABLE;
		/*printf("EPOLL_CTL_DEL(fd=%d\n",fd); */
		break;
	default:
		fprintf(stderr,
			"Unsupported epoll operation=%d"
			" epfd=%d, fd=%d events=%x\n", op, epfd, fd,
			event->events);
		errno = EINVAL;
		return -1;
	}

	EV_SET(&kev[0], fd, EVFILT_READ, read_flags, 0, 0,
	       event ? event->data.ptr : 0);
	EV_SET(&kev[1], fd, EVFILT_WRITE, write_flags, 0, 0,
	       event ? event->data.ptr : 0);

	return kevent(epfd, kev, 2, NULL, 0, NULL);
}

/*
 * Wait for a filter to be triggered on the epoll file descriptor.
 */
int
epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout)
{
	struct kevent *kevp;
	struct timespec ts;
	int rc;

	/* Convert from miliseconds to timespec. */
	ts.tv_sec = timeout / 1000;
	ts.tv_nsec = (timeout % 1000) * 1000000;

	kevp = mem_calloc(maxevents, sizeof(*kevp));
	/*
	 * ENOMEM is not expected from epoll_wait.
	 * Maybe we should translate that but I don't think it matters at all.
	 */

	rc = kevent(epfd, NULL, 0, kevp, maxevents, &ts);

	if (rc > 0) {
		/*printf("epoll_wait LEAVE %d\n", rc); */
		kevent_to_epoll_arr(kevp, rc, events);
	}

	mem_free(kevp, sizeof(struct kevent) * maxevents);
	return rc;
}

#endif				/* __FreeBSD__ || __APPLE__ */
