/* * Copyright (c) 2008 Vijay Kumar B. * * 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: * 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 #include #include #include #include #include #include #include #include #include #include #include "test.h" #define CLEANUP cleanup #include "lapi/syscalls.h" TCID_DEFINE(eventfd01); int TST_TOTAL = 15; #ifdef HAVE_LIBAIO #include static void setup(void); static int myeventfd(unsigned int initval, int flags) { /* eventfd2 uses FLAGS but eventfd doesn't take FLAGS. */ return ltp_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); } } #ifdef HAVE_IO_SET_EVENTFD /* * 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); } #else static void overflow_select_test(int evfd) { tst_resm(TCONF, "eventfd support is not available in AIO subsystem"); } static void overflow_poll_test(int evfd) { tst_resm(TCONF, "eventfd support is not available in AIO subsystem"); } static void overflow_read_test(int evfd) { tst_resm(TCONF, "eventfd support is not available in AIO subsystem"); } #endif 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