/*
 *   Copyright (c) 2008 Vijay Kumar B. <vijaykumar@bravegnu.org>
 *   Copyright (c) Linux Test Project, 2008-2022
 *
 *   Based on testcases/kernel/syscalls/waitpid/waitpid01.c
 *   Original copyright message:
 *
 *   Copyright (c) International Business Machines  Corp., 2001
 *
 *   This program is free software;  you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation; either version 2 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 a copy of the GNU General Public License
 *   along with this program;  if not, write to the Free Software
 *   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 */

/*
 * NAME
 *	eventfd01.c
 *
 * DESCRIPTION
 *      Test cases for eventfd syscall.
 *
 * USAGE:  <for command-line>
 *      eventfd01 [-c n] [-i n] [-I x] [-P x] [-t]
 *      where,  -c n : Run n copies concurrently.
 *              -i n : Execute test n times.
 *              -I x : Execute test for x seconds.
 *              -P x : Pause for x seconds between iterations.
 *
 * History
 *	07/2008 Vijay Kumar
 *		Initial Version.
 *
 * Restrictions
 *	None
 */

#include "config.h"

#include <sys/types.h>
#include <sys/select.h>
#include <sys/wait.h>
#include <errno.h>
#include <fcntl.h>
#include <inttypes.h>
#include <poll.h>
#include <signal.h>
#include <stdint.h>
#include <string.h>
#include <unistd.h>

#include "test.h"
#define CLEANUP cleanup
#include "lapi/syscalls.h"

TCID_DEFINE(eventfd01);
int TST_TOTAL = 15;

#ifdef HAVE_LIBAIO
#include <libaio.h>

static void setup(void);

static int myeventfd(unsigned int initval, int flags)
{
	/* eventfd2 uses FLAGS but eventfd doesn't take FLAGS. */
	return tst_syscall(__NR_eventfd, initval);
}

/*
 * clear_counter() - clears the counter by performing a dummy read
 * @fd: the eventfd
 *
 * RETURNS:
 * 0 on success, and -1 on failure
 */
static int clear_counter(int fd)
{
	uint64_t dummy;
	int ret;

	ret = read(fd, &dummy, sizeof(dummy));
	if (ret == -1) {
		if (errno != EAGAIN) {
			tst_resm(TINFO | TERRNO, "error clearing counter");
			return -1;
		}
	}

	return 0;
}

/*
 * set_counter() - sets the count to specified value
 * @fd: the eventfd
 * @val: the value to be set
 *
 * Clears the counter and sets the counter to @val.
 *
 * RETURNS:
 * 0 on success, -1 on failure
 */
static int set_counter(int fd, uint64_t val)
{
	int ret;

	ret = clear_counter(fd);
	if (ret == -1)
		return -1;

	ret = write(fd, &val, sizeof(val));
	if (ret == -1) {
		tst_resm(TINFO | TERRNO, "error setting counter value");
		return -1;
	}

	return 0;
}

/*
 * Test whether the current value of the counter matches @required.
 */
static void read_test(int fd, uint64_t required)
{
	int ret;
	uint64_t val;

	ret = read(fd, &val, sizeof(val));
	if (ret == -1) {
		tst_resm(TBROK | TERRNO, "error reading eventfd");
		return;
	}

	if (val == required)
		tst_resm(TPASS, "counter value matches required");
	else
		tst_resm(TFAIL, "counter value mismatch: "
			 "required: %" PRIu64 ", got: %" PRIu64, required, val);
}

/*
 * Test whether read returns with error EAGAIN when counter is at 0.
 */
static void read_eagain_test(int fd)
{
	int ret;
	uint64_t val;

	ret = clear_counter(fd);
	if (ret == -1) {
		tst_resm(TBROK, "error clearing counter");
		return;
	}

	ret = read(fd, &val, sizeof(val));
	if (ret == -1) {
		if (errno == EAGAIN)
			tst_resm(TPASS, "read failed with EAGAIN as expected");
		else
			tst_resm(TFAIL | TERRNO, "read failed (wanted EAGAIN)");
	} else
		tst_resm(TFAIL, "read returned with %d", ret);
}

/*
 * Test whether writing to counter works.
 */
static void write_test(int fd)
{
	int ret;
	uint64_t val;

	val = 12;

	ret = set_counter(fd, val);
	if (ret == -1) {
		tst_resm(TBROK, "error setting counter value to %" PRIu64, val);
		return;
	}

	read_test(fd, val);
}

/*
 * Test whether write returns with error EAGAIN when counter is at
 * (UINT64_MAX - 1).
 */
static void write_eagain_test(int fd)
{
	int ret;
	uint64_t val;

	ret = set_counter(fd, UINT64_MAX - 1);
	if (ret == -1) {
		tst_resm(TBROK, "error setting counter value to UINT64_MAX-1");
		return;
	}

	val = 1;
	ret = write(fd, &val, sizeof(val));
	if (ret == -1) {
		if (errno == EAGAIN)
			tst_resm(TPASS, "write failed with EAGAIN as expected");
		else
			tst_resm(TFAIL, "write failed (wanted EAGAIN)");
	} else
		tst_resm(TFAIL, "write returned with %d", ret);
}

/*
 * Test whether read returns with error EINVAL, if buffer size is less
 * than 8 bytes.
 */
static void read_einval_test(int fd)
{
	uint32_t invalid;
	int ret;

	ret = read(fd, &invalid, sizeof(invalid));
	if (ret == -1) {
		if (errno == EINVAL)
			tst_resm(TPASS, "read failed with EINVAL as expected");
		else
			tst_resm(TFAIL | TERRNO, "read failed (wanted EINVAL)");
	} else
		tst_resm(TFAIL, "read returned with %d", ret);
}

/*
 * Test whether write returns with error EINVAL, if buffer size is
 * less than 8 bytes.
 */
static void write_einval_test(int fd)
{
	uint32_t invalid;
	int ret;

	ret = write(fd, &invalid, sizeof(invalid));
	if (ret == -1) {
		if (errno == EINVAL)
			tst_resm(TPASS, "write failed with EINVAL as expected");
		else
			tst_resm(TFAIL | TERRNO,
				 "write failed (wanted EINVAL)");
	} else
		tst_resm(TFAIL, "write returned with %d", ret);
}

/*
 * Test wheter write returns with error EINVAL, when the written value
 * is 0xFFFFFFFFFFFFFFFF.
 */
static void write_einval2_test(int fd)
{
	int ret;
	uint64_t val;

	ret = clear_counter(fd);
	if (ret == -1) {
		tst_resm(TBROK, "error clearing counter");
		return;
	}

	val = 0xffffffffffffffffLL;
	ret = write(fd, &val, sizeof(val));
	if (ret == -1) {
		if (errno == EINVAL)
			tst_resm(TPASS, "write failed with EINVAL as expected");
		else
			tst_resm(TFAIL | TERRNO,
				 "write failed (wanted EINVAL)");
	} else {
		tst_resm(TFAIL, "write returned with %d", ret);
	}
}

/*
 * Test whether readfd is set by select when counter value is
 * non-zero.
 */
static void readfd_set_test(int fd)
{
	int ret;
	fd_set readfds;
	struct timeval timeout = { 0, 0 };
	uint64_t non_zero = 10;

	FD_ZERO(&readfds);
	FD_SET(fd, &readfds);

	ret = set_counter(fd, non_zero);
	if (ret == -1) {
		tst_resm(TBROK, "error setting counter value to %" PRIu64,
			 non_zero);
		return;
	}

	ret = select(fd + 1, &readfds, NULL, NULL, &timeout);
	if (ret == -1) {
		/* EINTR cannot occur, since we don't block. */
		tst_resm(TBROK | TERRNO, "select() failed");
		return;
	}

	if (FD_ISSET(fd, &readfds))
		tst_resm(TPASS, "fd is set in readfds");
	else
		tst_resm(TFAIL, "fd is not set in readfds");
}

/*
 * Test whether readfd is not set by select when counter value is
 * zero.
 */
static void readfd_not_set_test(int fd)
{
	int ret;
	fd_set readfds;
	struct timeval timeout = { 0, 0 };

	FD_ZERO(&readfds);
	FD_SET(fd, &readfds);

	ret = clear_counter(fd);
	if (ret == -1) {
		tst_resm(TBROK, "error clearing counter");
		return;
	}

	ret = select(fd + 1, &readfds, NULL, NULL, &timeout);
	if (ret == -1) {
		/* EINTR cannot occur, since we don't block. */
		tst_resm(TBROK | TERRNO, "select() failed");
		return;
	}

	if (!FD_ISSET(fd, &readfds))
		tst_resm(TPASS, "fd is not set in readfds");
	else
		tst_resm(TFAIL, "fd is set in readfds");
}

/*
 * Test whether writefd is set by select when counter value is not the
 * maximum counter value.
 */
static void writefd_set_test(int fd)
{
	int ret;
	fd_set writefds;
	struct timeval timeout = { 0, 0 };
	uint64_t non_max = 10;

	FD_ZERO(&writefds);
	FD_SET(fd, &writefds);

	ret = set_counter(fd, non_max);
	if (ret == -1) {
		tst_resm(TBROK, "error setting counter value to %" PRIu64,
			 non_max);
		return;
	}

	ret = select(fd + 1, NULL, &writefds, NULL, &timeout);
	if (ret == -1) {
		/* EINTR cannot occur, since we don't block. */
		tst_resm(TBROK | TERRNO, "select: error getting fd status");
		return;
	}

	if (FD_ISSET(fd, &writefds))
		tst_resm(TPASS, "fd is set in writefds");
	else
		tst_resm(TFAIL, "fd is not set in writefds");
}

/*
 * Test whether writefd is not set by select when counter value is at
 * (UINT64_MAX - 1).
 */
static void writefd_not_set_test(int fd)
{
	int ret;
	fd_set writefds;
	struct timeval timeout = { 0, 0 };

	FD_ZERO(&writefds);
	FD_SET(fd, &writefds);

	ret = set_counter(fd, UINT64_MAX - 1);
	if (ret == -1) {
		tst_resm(TBROK, "error setting counter value to UINT64_MAX-1");
		return;
	}

	ret = select(fd + 1, NULL, &writefds, NULL, &timeout);
	if (ret == -1) {
		/* EINTR cannot occur, since we don't block. */
		tst_resm(TBROK | TERRNO, "select: error getting fd status");
		return;
	}

	if (!FD_ISSET(fd, &writefds))
		tst_resm(TPASS, "fd is not set in writefds");
	else
		tst_resm(TFAIL, "fd is set in writefds");
}

/*
 * Test whether counter update in child is reflected in the parent.
 */
static void child_inherit_test(int fd)
{
	uint64_t val;
	pid_t cpid;
	int ret;
	int status;
	uint64_t to_parent = 0xdeadbeef;
	uint64_t dummy;

	cpid = fork();
	if (cpid == -1)
		tst_resm(TBROK | TERRNO, "fork failed");
	else if (cpid != 0) {
		ret = wait(&status);
		if (ret == -1) {
			tst_resm(TBROK, "error getting child exit status");
			return;
		}

		if (WEXITSTATUS(status) == 1) {
			tst_resm(TBROK, "counter value write not "
				 "successful in child");
			return;
		}

		ret = read(fd, &val, sizeof(val));
		if (ret == -1) {
			tst_resm(TBROK | TERRNO, "error reading eventfd");
			return;
		}

		if (val == to_parent)
			tst_resm(TPASS, "counter value write from "
				 "child successful");
		else
			tst_resm(TFAIL, "counter value write in child "
				 "failed");
	} else {
		/* Child */
		ret = read(fd, &dummy, sizeof(dummy));
		if (ret == -1 && errno != EAGAIN) {
			tst_resm(TWARN | TERRNO, "error clearing counter");
			exit(1);
		}

		ret = write(fd, &to_parent, sizeof(to_parent));
		if (ret == -1) {
			tst_resm(TWARN | TERRNO, "error writing eventfd");
			exit(1);
		}

		exit(0);
	}
}

/*
 * Test whether counter overflow is detected and handled correctly.
 *
 * It is not possible to directly overflow the counter using the
 * write() syscall. Overflows occur when the counter is incremented
 * from kernel space, in an irq context, when it is not possible to
 * block the calling thread of execution.
 *
 * The AIO subsystem internally uses eventfd mechanism for
 * notification of completion of read or write requests. In this test
 * we trigger a counter overflow, by setting the counter value to the
 * max possible value initially. When the AIO subsystem notifies
 * through the eventfd counter, the counter overflows.
 *
 * NOTE: If the the counter starts from an initial value of 0, it will
 * take decades for an overflow to occur. But since we set the initial
 * value to the max possible counter value, we are able to cause it to
 * overflow with a single increment.
 *
 * When the counter overflows, the following are tested
 *   1. Check whether POLLERR event occurs in poll() for the eventfd.
 *   2. Check whether readfd_set/writefd_set is set in select() for the
        eventfd.
 *   3. The counter value is UINT64_MAX.
 */
static int trigger_eventfd_overflow(int evfd, int *fd, io_context_t * ctx)
{
	int ret;
	struct iocb iocb;
	struct iocb *iocbap[1];
	struct io_event ioev;
	static char buf[4 * 1024];

	*ctx = 0;
	ret = io_setup(16, ctx);
	if (ret < 0) {
		errno = -ret;
		if (errno == ENOSYS) {
			tst_brkm(TCONF | TERRNO, cleanup,
				 "io_setup(): AIO not supported by kernel");
		}

		tst_resm(TINFO | TERRNO, "io_setup error");
		return -1;
	}

	*fd = open("testfile", O_RDWR | O_CREAT, 0644);
	if (*fd == -1) {
		tst_resm(TINFO | TERRNO, "open(testfile) failed");
		goto err_io_destroy;
	}

	ret = set_counter(evfd, UINT64_MAX - 1);
	if (ret == -1) {
		tst_resm(TINFO, "error setting counter to UINT64_MAX-1");
		goto err_close_file;
	}

	io_prep_pwrite(&iocb, *fd, buf, sizeof(buf), 0);
	io_set_eventfd(&iocb, evfd);

	iocbap[0] = &iocb;
	ret = io_submit(*ctx, 1, iocbap);
	if (ret < 0) {
		errno = -ret;
		tst_resm(TINFO | TERRNO, "error submitting iocb");
		goto err_close_file;
	}

	ret = io_getevents(*ctx, 1, 1, &ioev, NULL);
	if (ret < 0) {
		errno = -ret;
		tst_resm(TINFO | TERRNO, "error waiting for event");
		goto err_close_file;
	}

	return 0;

err_close_file:
	close(*fd);

err_io_destroy:
	io_destroy(*ctx);

	return -1;
}

static void cleanup_overflow(int fd, io_context_t ctx)
{
	close(fd);
	io_destroy(ctx);
}

static void overflow_select_test(int evfd)
{
	struct timeval timeout = { 10, 0 };
	fd_set readfds;
	int fd;
	io_context_t ctx;
	int ret;

	ret = trigger_eventfd_overflow(evfd, &fd, &ctx);
	if (ret == -1) {
		tst_resm(TBROK, "error triggering eventfd overflow");
		return;
	}

	FD_ZERO(&readfds);
	FD_SET(evfd, &readfds);
	ret = select(evfd + 1, &readfds, NULL, NULL, &timeout);
	if (ret == -1)
		tst_resm(TBROK | TERRNO,
			 "error getting evfd status with select");
	else {
		if (FD_ISSET(evfd, &readfds))
			tst_resm(TPASS, "read fd set as expected");
		else
			tst_resm(TFAIL, "read fd not set");
	}
	cleanup_overflow(fd, ctx);
}

static void overflow_poll_test(int evfd)
{
	struct pollfd pollfd;
	int fd;
	io_context_t ctx;
	int ret;

	ret = trigger_eventfd_overflow(evfd, &fd, &ctx);
	if (ret == -1) {
		tst_resm(TBROK, "error triggering eventfd overflow");
		return;
	}

	pollfd.fd = evfd;
	pollfd.events = POLLIN;
	pollfd.revents = 0;
	ret = poll(&pollfd, 1, 10000);
	if (ret == -1)
		tst_resm(TBROK | TERRNO, "error getting evfd status with poll");
	else {
		if (pollfd.revents & POLLERR)
			tst_resm(TPASS, "POLLERR occurred as expected");
		else
			tst_resm(TFAIL, "POLLERR did not occur");
	}
	cleanup_overflow(fd, ctx);
}

static void overflow_read_test(int evfd)
{
	uint64_t count;
	io_context_t ctx;
	int fd;
	int ret;

	ret = trigger_eventfd_overflow(evfd, &fd, &ctx);
	if (ret == -1) {
		tst_resm(TBROK, "error triggering eventfd overflow");
		return;
	}

	ret = read(evfd, &count, sizeof(count));
	if (ret == -1)
		tst_resm(TBROK | TERRNO, "error reading eventfd");
	else {

		if (count == UINT64_MAX)
			tst_resm(TPASS, "overflow occurred as expected");
		else
			tst_resm(TFAIL, "overflow did not occur");
	}
	cleanup_overflow(fd, ctx);
}

int main(int argc, char **argv)
{
	int lc;
	int fd;

	tst_parse_opts(argc, argv, NULL, NULL);

	setup();

	for (lc = 0; TEST_LOOPING(lc); lc++) {
		int ret;
		uint64_t einit = 10;

		tst_count = 0;

		fd = myeventfd(einit, 0);
		if (fd == -1)
			tst_brkm(TBROK | TERRNO, CLEANUP,
				 "error creating eventfd");

		ret = fcntl(fd, F_SETFL, O_NONBLOCK);
		if (ret == -1)
			tst_brkm(TBROK | TERRNO, CLEANUP,
				 "error setting non-block mode");

		read_test(fd, einit);
		read_eagain_test(fd);
		write_test(fd);
		write_eagain_test(fd);
		read_einval_test(fd);
		write_einval_test(fd);
		write_einval2_test(fd);
		readfd_set_test(fd);
		readfd_not_set_test(fd);
		writefd_set_test(fd);
		writefd_not_set_test(fd);
		child_inherit_test(fd);
		overflow_select_test(fd);
		overflow_poll_test(fd);
		overflow_read_test(fd);

		close(fd);
	}

	cleanup();

	tst_exit();
}

static void setup(void)
{

	tst_sig(FORK, DEF_HANDLER, cleanup);

	if (tst_kvercmp(2, 6, 22) < 0)
		tst_brkm(TCONF, NULL, "2.6.22 or greater kernel required");

	tst_tmpdir();

	TEST_PAUSE;
}

static void cleanup(void)
{
	tst_rmdir();
}

#else
int main(void)
{
	tst_brkm(TCONF, NULL, "test requires libaio and it's development packages");
}
#endif
