From: Jérémie Galarneau Date: Thu, 10 Feb 2022 16:59:31 +0000 (-0500) Subject: Tests: fix: select_poll_epoll: test assumes epoll fd value X-Git-Url: https://git.lttng.org/?p=lttng-tools.git;a=commitdiff_plain;h=d40c26206a44d9e247bbcdc01afb34c41d0c490d Tests: fix: select_poll_epoll: test assumes epoll fd value The test currently assumes that epoll fds are always == 3, which is not always the case depending on the execution environment. This change causes `select_poll_epoll` to produce a JSON file containing the application's pid and epoll fd values that is then used by the validation script. Note that the test is converted to C++ to allow the use of internal utils (common/error.h/cpp) without changing their linkage. However, the code is still regular C to ease the backport of this fix. Signed-off-by: Jérémie Galarneau Change-Id: Ie373c63f6e6b9267ae2d785c9f0a532a5de37905 --- diff --git a/tests/regression/kernel/Makefile.am b/tests/regression/kernel/Makefile.am index 040b76423..dfbed73f4 100644 --- a/tests/regression/kernel/Makefile.am +++ b/tests/regression/kernel/Makefile.am @@ -17,8 +17,8 @@ EXTRA_DIST = test_all_events \ validate_select_poll_epoll.py noinst_PROGRAMS = select_poll_epoll -select_poll_epoll_SOURCES = select_poll_epoll.c -select_poll_epoll_LDADD = $(POPT_LIBS) +select_poll_epoll_SOURCES = select_poll_epoll.cpp +select_poll_epoll_LDADD = $(POPT_LIBS) $(top_builddir)/src/common/libcommon-lgpl.la select_poll_epoll_CFLAGS = $(POPT_CFLAGS) -fno-stack-protector -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=0 $(AM_CFLAGS) all-local: diff --git a/tests/regression/kernel/select_poll_epoll.c b/tests/regression/kernel/select_poll_epoll.c deleted file mode 100644 index d0d7e4aea..000000000 --- a/tests/regression/kernel/select_poll_epoll.c +++ /dev/null @@ -1,986 +0,0 @@ -/* - * Copyright (C) 2016 Julien Desfossez - * - * SPDX-License-Identifier: GPL-2.0-only - * - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define BUF_SIZE 256 -#define NB_FD 1 -#define MAX_FDS 2047 -#define NR_ITER 1000 /* for stress-tests */ - -#define MIN_NR_FDS 5 /* the minimum number of open FDs required for the test to run */ -#define BIG_SELECT_FD 1022 - -#define MSEC_PER_USEC 1000 -#define MSEC_PER_NSEC (MSEC_PER_USEC * 1000) - -static int timeout; /* seconds, -1 to disable */ -static volatile int stop_thread; -static int wait_fd; - -struct ppoll_thread_data { - struct pollfd *ufds; - int value; -}; - -static -void test_select_big(void) -{ - fd_set rfds, wfds, exfds; - struct timeval tv; - int ret; - int fd2; - char buf[BUF_SIZE]; - - FD_ZERO(&rfds); - FD_ZERO(&wfds); - FD_ZERO(&exfds); - - fd2 = dup2(wait_fd, BIG_SELECT_FD); - if (fd2 < 0) { - perror("dup2"); - goto end; - } - FD_SET(fd2, &rfds); - - tv.tv_sec = 0; - tv.tv_usec = timeout * MSEC_PER_USEC; - - if (timeout > 0) { - ret = select(fd2 + 1, &rfds, &wfds, &exfds, &tv); - } else { - ret = select(fd2 + 1, &rfds, &wfds, &exfds, NULL); - } - - if (ret == -1) { - perror("select()"); - } else if (ret) { - printf("# [select] data available\n"); - ret = read(wait_fd, buf, BUF_SIZE); - if (ret < 0) { - perror("[select] read"); - } - } else { - printf("# [select] timeout\n"); - } - - ret = close(BIG_SELECT_FD); - if (ret) { - perror("close"); - } - -end: - return; -} - -static -void test_pselect(void) -{ - fd_set rfds; - struct timespec tv; - int ret; - char buf[BUF_SIZE]; - - FD_ZERO(&rfds); - FD_SET(wait_fd, &rfds); - - tv.tv_sec = 0; - tv.tv_nsec = timeout * MSEC_PER_NSEC; - - if (timeout > 0) { - ret = pselect(1, &rfds, NULL, NULL, &tv, NULL); - } else { - ret = pselect(1, &rfds, NULL, NULL, NULL, NULL); - } - - if (ret == -1) { - perror("pselect()"); - } else if (ret) { - printf("# [pselect] data available\n"); - ret = read(wait_fd, buf, BUF_SIZE); - if (ret < 0) { - perror("[pselect] read"); - } - } else { - printf("# [pselect] timeout\n"); - } - -} - -static -void test_select(void) -{ - fd_set rfds; - struct timeval tv; - int ret; - char buf[BUF_SIZE]; - - FD_ZERO(&rfds); - FD_SET(wait_fd, &rfds); - - tv.tv_sec = 0; - tv.tv_usec = timeout * MSEC_PER_USEC; - - if (timeout > 0) { - ret = select(1, &rfds, NULL, NULL, &tv); - } else { - ret = select(1, &rfds, NULL, NULL, NULL); - } - - if (ret == -1) { - perror("select()"); - } else if (ret) { - printf("# [select] data available\n"); - ret = read(wait_fd, buf, BUF_SIZE); - if (ret < 0) { - perror("[select] read"); - } - } else { - printf("# [select] timeout\n"); - } - -} - -static -void test_poll(void) -{ - struct pollfd ufds[NB_FD]; - char buf[BUF_SIZE]; - int ret; - - ufds[0].fd = wait_fd; - ufds[0].events = POLLIN|POLLPRI; - - ret = poll(ufds, 1, timeout); - - if (ret < 0) { - perror("poll"); - } else if (ret > 0) { - printf("# [poll] data available\n"); - ret = read(wait_fd, buf, BUF_SIZE); - if (ret < 0) { - perror("[poll] read"); - } - } else { - printf("# [poll] timeout\n"); - } -} - -static -void test_ppoll(void) -{ - struct pollfd ufds[NB_FD]; - char buf[BUF_SIZE]; - int ret; - struct timespec ts; - - ufds[0].fd = wait_fd; - ufds[0].events = POLLIN|POLLPRI; - - if (timeout > 0) { - ts.tv_sec = 0; - ts.tv_nsec = timeout * MSEC_PER_NSEC; - ret = ppoll(ufds, 1, &ts, NULL); - } else { - ret = ppoll(ufds, 1, NULL, NULL); - } - - - if (ret < 0) { - perror("ppoll"); - } else if (ret > 0) { - printf("# [ppoll] data available\n"); - ret = read(wait_fd, buf, BUF_SIZE); - if (ret < 0) { - perror("[ppoll] read"); - } - } else { - printf("# [ppoll] timeout\n"); - } -} - -static -void test_ppoll_big(void) -{ - struct pollfd ufds[MAX_FDS]; - char buf[BUF_SIZE]; - int ret, i, fds[MAX_FDS]; - - for (i = 0; i < MAX_FDS; i++) { - fds[i] = dup(wait_fd); - if (fds[i] < 0) { - perror("dup"); - } - ufds[i].fd = fds[i]; - ufds[i].events = POLLIN|POLLPRI; - } - - ret = ppoll(ufds, MAX_FDS, NULL, NULL); - - if (ret < 0) { - perror("ppoll"); - } else if (ret > 0) { - printf("# [ppoll] data available\n"); - ret = read(wait_fd, buf, BUF_SIZE); - if (ret < 0) { - perror("[ppoll] read"); - } - } else { - printf("# [ppoll] timeout\n"); - } - - for (i = 0; i < MAX_FDS; i++) { - ret = close(fds[i]); - if (ret != 0) { - perror("close"); - } - } - - return; -} - -static -void test_epoll(void) -{ - int ret, epollfd; - char buf[BUF_SIZE]; - struct epoll_event epoll_event; - - epollfd = epoll_create(NB_FD); - if (epollfd < 0) { - perror("[epoll] create"); - goto end; - } - - epoll_event.events = EPOLLIN | EPOLLPRI | EPOLLET; - epoll_event.data.fd = wait_fd; - ret = epoll_ctl(epollfd, EPOLL_CTL_ADD, wait_fd, &epoll_event); - if (ret < 0) { - perror("[epoll] add"); - goto error; - } - - if (timeout > 0) { - ret = epoll_wait(epollfd, &epoll_event, 1, timeout); - } else { - ret = epoll_wait(epollfd, &epoll_event, 1, -1); - } - - if (ret == 1) { - printf("# [epoll] data available\n"); - ret = read(wait_fd, buf, BUF_SIZE); - if (ret < 0) { - perror("[epoll] read"); - } - } else if (ret == 0) { - printf("# [epoll] timeout\n"); - } else { - perror("epoll_wait"); - } - -error: - ret = close(epollfd); - if (ret) { - perror("close"); - } -end: - return; -} - -static -void test_pepoll(void) -{ - int ret, epollfd; - char buf[BUF_SIZE]; - struct epoll_event epoll_event; - - epollfd = epoll_create(NB_FD); - if (epollfd < 0) { - perror("[eppoll] create"); - goto end; - } - - epoll_event.events = EPOLLIN | EPOLLPRI | EPOLLET; - epoll_event.data.fd = wait_fd; - ret = epoll_ctl(epollfd, EPOLL_CTL_ADD, wait_fd, &epoll_event); - if (ret < 0) { - perror("[eppoll] add"); - goto error; - } - - if (timeout > 0) { - ret = epoll_pwait(epollfd, &epoll_event, 1, timeout, NULL); - } else { - ret = epoll_pwait(epollfd, &epoll_event, 1, -1, NULL); - } - - if (ret == 1) { - printf("# [eppoll] data available\n"); - ret = read(wait_fd, buf, BUF_SIZE); - if (ret < 0) { - perror("[eppoll] read"); - } - } else if (ret == 0) { - printf("# [eppoll] timeout\n"); - } else { - perror("epoll_pwait"); - } - -error: - ret = close(epollfd); - if (ret) { - perror("close"); - } -end: - return; -} - -static -void run_working_cases(void) -{ - int ret; - int pipe_fds[2]; - - if (timeout > 0) { - /* - * We need an input pipe for some cases and stdin might - * have random data, so we create a dummy pipe for this - * test to make sure we are running under clean conditions. - */ - ret = pipe(pipe_fds); - if (ret != 0) { - perror("pipe"); - goto end; - } - wait_fd = pipe_fds[0]; - } - test_select(); - test_pselect(); - test_select_big(); - test_poll(); - test_ppoll(); - test_epoll(); - test_pepoll(); - - if (timeout > 0) { - ret = close(pipe_fds[0]); - if (ret) { - perror("close"); - } - ret = close(pipe_fds[1]); - if (ret) { - perror("close"); - } - } - -end: - return; -} - -/* - * Ask for 100 FDs in a buffer for allocated for only 1 FD, should - * segfault (eventually with a "*** stack smashing detected ***" message). - * The event should contain an array of 100 FDs filled with garbage. - */ -static -void ppoll_fds_buffer_overflow(void) -{ - struct pollfd ufds[NB_FD]; - char buf[BUF_SIZE]; - int ret; - - ufds[0].fd = wait_fd; - ufds[0].events = POLLIN|POLLPRI; - - ret = syscall(SYS_ppoll, ufds, 100, NULL, NULL); - - if (ret < 0) { - perror("ppoll"); - } else if (ret > 0) { - printf("# [ppoll] data available\n"); - ret = read(wait_fd, buf, BUF_SIZE); - if (ret < 0) { - perror("[ppoll] read"); - } - } else { - printf("# [ppoll] timeout\n"); - } - - return; -} - -/* - * Ask for ULONG_MAX FDs in a buffer for allocated for only 1 FD, should - * cleanly fail with a "Invalid argument". - * The event should contain an empty array of FDs and overflow = 1. - */ -static -void ppoll_fds_ulong_max(void) -{ - struct pollfd ufds[NB_FD]; - char buf[BUF_SIZE]; - int ret; - - ufds[0].fd = wait_fd; - ufds[0].events = POLLIN|POLLPRI; - - ret = syscall(SYS_ppoll, ufds, ULONG_MAX, NULL, NULL); - - if (ret < 0) { - perror("# ppoll"); - } else if (ret > 0) { - printf("# [ppoll] data available\n"); - ret = read(wait_fd, buf, BUF_SIZE); - if (ret < 0) { - perror("[ppoll] read"); - } - } else { - printf("# [ppoll] timeout\n"); - } - - return; -} - -/* - * Pass an invalid file descriptor to pselect6(). The syscall should return - * -EBADF. The recorded event should contain a "ret = -EBADF (-9)". - */ -static -void pselect_invalid_fd(void) -{ - fd_set rfds; - int ret; - int fd; - char buf[BUF_SIZE]; - - /* - * Open a file, close it and use the closed FD in the pselect6 call. - */ - - fd = open("/dev/null", O_RDONLY); - if (fd == -1) { - perror("open"); - goto error; - } - - ret = close(fd); - if (ret == -1) { - perror("close"); - goto error; - } - - FD_ZERO(&rfds); - FD_SET(fd, &rfds); - - ret = syscall(SYS_pselect6, fd + 1, &rfds, NULL, NULL, NULL, NULL); - if (ret == -1) { - perror("# pselect()"); - } else if (ret) { - printf("# [pselect] data available\n"); - ret = read(wait_fd, buf, BUF_SIZE); - if (ret < 0) { - perror("[pselect] read"); - } - } else { - printf("# [pselect] timeout\n"); - } -error: - return; -} - -/* - * Invalid pointer as writefds, should output a ppoll event - * with 0 FDs. - */ -static -void pselect_invalid_pointer(void) -{ - fd_set rfds; - int ret; - char buf[BUF_SIZE]; - void *invalid = (void *) 0x42; - - FD_ZERO(&rfds); - FD_SET(wait_fd, &rfds); - - ret = syscall(SYS_pselect6, 1, &rfds, (fd_set *) invalid, NULL, NULL, - NULL); - - if (ret == -1) { - perror("# pselect()"); - } else if (ret) { - printf("# [pselect] data available\n"); - ret = read(wait_fd, buf, BUF_SIZE); - if (ret < 0) { - perror("[pselect] read"); - } - } else { - printf("# [pselect] timeout\n"); - } - -} - -/* - * Pass an invalid pointer to epoll_pwait, should fail with - * "Bad address", the event returns 0 FDs. - */ -static -void epoll_pwait_invalid_pointer(void) -{ - int ret, epollfd; - char buf[BUF_SIZE]; - struct epoll_event epoll_event; - void *invalid = (void *) 0x42; - - epollfd = epoll_create(NB_FD); - if (epollfd < 0) { - perror("[eppoll] create"); - goto end; - } - - epoll_event.events = EPOLLIN | EPOLLPRI | EPOLLET; - epoll_event.data.fd = wait_fd; - ret = epoll_ctl(epollfd, EPOLL_CTL_ADD, wait_fd, &epoll_event); - if (ret < 0) { - perror("[eppoll] add"); - goto error; - } - - ret = syscall(SYS_epoll_pwait, epollfd, - (struct epoll_event *) invalid, 1, -1, NULL); - - if (ret == 1) { - printf("# [eppoll] data available\n"); - ret = read(wait_fd, buf, BUF_SIZE); - if (ret < 0) { - perror("[eppoll] read"); - } - } else if (ret == 0) { - printf("# [eppoll] timeout\n"); - } else { - perror("# epoll_pwait"); - } - -error: - ret = close(epollfd); - if (ret) { - perror("close"); - } -end: - return; -} - -/* - * Set maxevents to INT_MAX, should output "Invalid argument" - * The event should return an empty array. - */ -static -void epoll_pwait_int_max(void) -{ - int ret, epollfd; - char buf[BUF_SIZE]; - struct epoll_event epoll_event; - - epollfd = epoll_create(NB_FD); - if (epollfd < 0) { - perror("[eppoll] create"); - goto end; - } - - epoll_event.events = EPOLLIN | EPOLLPRI | EPOLLET; - epoll_event.data.fd = wait_fd; - ret = epoll_ctl(epollfd, EPOLL_CTL_ADD, wait_fd, &epoll_event); - if (ret < 0) { - perror("[eppoll] add"); - goto error; - } - - ret = syscall(SYS_epoll_pwait, epollfd, &epoll_event, INT_MAX, -1, - NULL); - - if (ret == 1) { - printf("# [eppoll] data available\n"); - ret = read(wait_fd, buf, BUF_SIZE); - if (ret < 0) { - perror("[eppoll] read"); - } - } else if (ret == 0) { - printf("# [eppoll] timeout\n"); - } else { - perror("# epoll_pwait"); - } - -error: - ret = close(epollfd); - if (ret) { - perror("close"); - } -end: - return; -} - -static -void *ppoll_writer(void *arg) -{ - struct ppoll_thread_data *data = (struct ppoll_thread_data *) arg; - - while (!stop_thread) { - memset(data->ufds, data->value, - MAX_FDS * sizeof(struct pollfd)); - usleep(100); - } - - return NULL; -} - -static -void do_ppoll(int *fds, struct pollfd *ufds) -{ - int i, ret; - struct timespec ts; - char buf[BUF_SIZE]; - - ts.tv_sec = 0; - ts.tv_nsec = 1 * MSEC_PER_NSEC; - - for (i = 0; i < MAX_FDS; i++) { - ufds[i].fd = fds[i]; - ufds[i].events = POLLIN|POLLPRI; - } - - ret = ppoll(ufds, MAX_FDS, &ts, NULL); - - if (ret < 0) { - perror("ppoll"); - } else if (ret > 0) { - printf("# [ppoll] data available\n"); - ret = read(wait_fd, buf, BUF_SIZE); - if (ret < 0) { - perror("[ppoll] read"); - } - } else { - printf("# [ppoll] timeout\n"); - } -} - -static -void stress_ppoll(int *fds, int value) -{ - pthread_t writer; - int iter, ret; - struct ppoll_thread_data thread_data; - struct pollfd ufds[MAX_FDS]; - - thread_data.ufds = ufds; - thread_data.value = value; - - stop_thread = 0; - ret = pthread_create(&writer, NULL, &ppoll_writer, (void *) &thread_data); - if (ret != 0) { - fprintf(stderr, "[error] pthread_create\n"); - goto end; - } - for (iter = 0; iter < NR_ITER; iter++) { - do_ppoll(fds, ufds); - } - stop_thread = 1; - ret = pthread_join(writer, NULL); - if (ret) { - fprintf(stderr, "[error] pthread_join\n"); - goto end; - } -end: - return; -} - -/* - * 3 rounds of NR_ITER iterations with concurrent updates of the pollfd - * structure: - * - memset to 0 - * - memset to 1 - * - memset to INT_MAX - * Waits for input, but also set a timeout in case the input FD is overwritten - * before entering in the syscall. We use MAX_FDS FDs (dup of stdin), so the - * resulting trace is big (20MB). - * - * ppoll should work as expected and the trace should be readable at the end. - */ -static -void ppoll_concurrent_write(void) -{ - int i, ret, fds[MAX_FDS]; - - for (i = 0; i < MAX_FDS; i++) { - fds[i] = dup(wait_fd); - if (fds[i] < 0) { - perror("dup"); - } - } - - stress_ppoll(fds, 0); - stress_ppoll(fds, 1); - stress_ppoll(fds, INT_MAX); - - for (i = 0; i < MAX_FDS; i++) { - ret = close(fds[i]); - if (ret != 0) { - perror("close"); - } - } - - return; -} - -static -void *epoll_pwait_writer(void *addr) -{ - srand(time(NULL)); - - while (!stop_thread) { - usleep(rand() % 30); - munmap(addr, MAX_FDS * sizeof(struct epoll_event)); - } - - return NULL; -} - -/* - * epoll_pwait on MAX_FDS fds while a concurrent thread munmaps the - * buffer allocated for the returned data. This should randomly segfault. - * The trace should be readable and no kernel OOPS should occur. - */ -static -void epoll_pwait_concurrent_munmap(void) -{ - int ret, epollfd, i, fds[MAX_FDS]; - char buf[BUF_SIZE]; - struct epoll_event *epoll_event; - pthread_t writer; - - for (i = 0; i < MAX_FDS; i++) { - fds[i] = -1; - } - epollfd = epoll_create(MAX_FDS); - if (epollfd < 0) { - perror("[eppoll] create"); - goto end; - } - - epoll_event = mmap(NULL, MAX_FDS * sizeof(struct epoll_event), - PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, - -1, 0); - if (epoll_event == MAP_FAILED) { - perror("mmap"); - goto error; - } - - for (i = 0; i < MAX_FDS; i++) { - fds[i] = dup(wait_fd); - if (fds[i] < 0) { - perror("dup"); - } - epoll_event[i].events = EPOLLIN | EPOLLPRI | EPOLLET; - epoll_event[i].data.fd = fds[i]; - ret = epoll_ctl(epollfd, EPOLL_CTL_ADD, fds[i], epoll_event); - if (ret < 0) { - perror("[eppoll] add"); - goto error_unmap; - } - } - stop_thread = 0; - ret = pthread_create(&writer, NULL, &epoll_pwait_writer, - (void *) epoll_event); - if (ret != 0) { - fprintf(stderr, "[error] pthread_create\n"); - goto error_unmap; - } - - ret = epoll_pwait(epollfd, epoll_event, 1, 1, NULL); - - if (ret == 1) { - printf("# [eppoll] data available\n"); - ret = read(wait_fd, buf, BUF_SIZE); - if (ret < 0) { - perror("[eppoll] read"); - } - } else if (ret == 0) { - printf("# [eppoll] timeout\n"); - } else { - perror("# epoll_pwait"); - } - - stop_thread = 1; - ret = pthread_join(writer, NULL); - if (ret) { - fprintf(stderr, "[error] pthread_join\n"); - goto error_unmap; - } -error_unmap: - for (i = 0; i < MAX_FDS; i++) { - ret = close(fds[i]); - if (ret != 0) { - perror("close"); - } - } - - ret = munmap(epoll_event, MAX_FDS * sizeof(struct epoll_event)); - if (ret != 0) { - perror("munmap"); - } - -error: - ret = close(epollfd); - if (ret) { - perror("close"); - } -end: - return; -} - -static -void print_list(void) -{ - fprintf(stderr, "Test list (-t X):\n"); - fprintf(stderr, "\t1: Working cases for select, pselect6, poll, ppoll " - "and epoll, waiting for input\n"); - fprintf(stderr, "\t2: Timeout cases (1ms) for select, pselect6, poll, " - "ppoll and epoll\n"); - fprintf(stderr, "\t3: pselect with an invalid fd\n"); - fprintf(stderr, "\t4: ppoll with %d FDs\n", MAX_FDS); - fprintf(stderr, "\t5: ppoll buffer overflow, should segfault, waits " - "for input\n"); - fprintf(stderr, "\t6: pselect with an invalid pointer, waits for " - "input\n"); - fprintf(stderr, "\t7: ppoll with ulong_max fds, waits for input\n"); - fprintf(stderr, "\t8: epoll_pwait with an invalid pointer, waits for " - "input\n"); - fprintf(stderr, "\t9: epoll_pwait with maxevents set to INT_MAX, " - "waits for input\n"); - fprintf(stderr, "\t10: ppoll with concurrent updates of the structure " - "from user-space, stress test (3000 iterations), " - "waits for input + timeout 1ms\n"); - fprintf(stderr, "\t11: epoll_pwait with concurrent munmap of the buffer " - "from user-space, should randomly segfault, run " - "multiple times, waits for input + timeout 1ms\n"); -} - -int main(int argc, const char **argv) -{ - int c, ret, test = -1; - poptContext optCon; - struct rlimit open_lim; - - struct poptOption optionsTable[] = { - { "test", 't', POPT_ARG_INT, &test, 0, - "Test to run", NULL }, - { "list", 'l', 0, 0, 'l', - "List of tests (-t X)", NULL }, - POPT_AUTOHELP - { NULL, 0, 0, NULL, 0 } - }; - - optCon = poptGetContext(NULL, argc, argv, optionsTable, 0); - - if (argc < 2) { - poptPrintUsage(optCon, stderr, 0); - ret = -1; - goto end; - } - - ret = 0; - - while ((c = poptGetNextOpt(optCon)) >= 0) { - switch(c) { - case 'l': - print_list(); - goto end; - } - } - - open_lim.rlim_cur = MAX_FDS + MIN_NR_FDS; - open_lim.rlim_max = MAX_FDS + MIN_NR_FDS; - - ret = setrlimit(RLIMIT_NOFILE, &open_lim); - if (ret < 0) { - perror("setrlimit"); - goto end; - } - - /* - * Some tests might segfault, but we need the getpid() to be output - * for the validation, disabling the buffering on stdout works. - */ - setbuf(stdout, NULL); - printf("%d\n", getpid()); - - wait_fd = STDIN_FILENO; - - switch(test) { - case 1: - timeout = -1; - run_working_cases(); - break; - case 2: - timeout = 1; - run_working_cases(); - break; - case 3: - pselect_invalid_fd(); - break; - case 4: - test_ppoll_big(); - break; - case 5: - ppoll_fds_buffer_overflow(); - break; - case 6: - pselect_invalid_pointer(); - break; - case 7: - ppoll_fds_ulong_max(); - break; - case 8: - epoll_pwait_invalid_pointer(); - break; - case 9: - epoll_pwait_int_max(); - break; - case 10: - ppoll_concurrent_write(); - break; - case 11: - epoll_pwait_concurrent_munmap(); - break; - default: - poptPrintUsage(optCon, stderr, 0); - ret = -1; - break; - } - -end: - poptFreeContext(optCon); - return ret; -} diff --git a/tests/regression/kernel/select_poll_epoll.cpp b/tests/regression/kernel/select_poll_epoll.cpp new file mode 100644 index 000000000..abedc0aa9 --- /dev/null +++ b/tests/regression/kernel/select_poll_epoll.cpp @@ -0,0 +1,1021 @@ +/* + * Copyright (C) 2016 Julien Desfossez + * + * SPDX-License-Identifier: GPL-2.0-only + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define BUF_SIZE 256 +#define NB_FD 1 +#define MAX_FDS 2047 +#define NR_ITER 1000 /* for stress-tests */ + +#define MIN_NR_FDS 5 /* the minimum number of open FDs required for the test to run */ +#define BIG_SELECT_FD 1022 + +#define MSEC_PER_USEC 1000 +#define MSEC_PER_NSEC (MSEC_PER_USEC * 1000) + +static int timeout; /* seconds, -1 to disable */ +static volatile int stop_thread; +static int wait_fd; + +/* Used by logging utils. */ +int lttng_opt_quiet, lttng_opt_verbose, lttng_opt_mi; + +static void run_working_cases(FILE *validation_output_file); +static void pselect_invalid_fd(FILE *validation_output_file); +static void test_ppoll_big(FILE *validation_output_file); +static void ppoll_fds_buffer_overflow(FILE *validation_output_file); +static void pselect_invalid_pointer(FILE *validation_output_file); +static void ppoll_fds_ulong_max(FILE *validation_output_file); +static void epoll_pwait_invalid_pointer(FILE *validation_output_file); +static void epoll_pwait_int_max(FILE *validation_output_file); +static void ppoll_concurrent_write(FILE *validation_output_file); +static void epoll_pwait_concurrent_munmap(FILE *validation_output_file); + +typedef void (*test_case_cb)(FILE *output_file); + +static const struct test_case { + test_case_cb run; + bool produces_validation_info; + int timeout; +} test_cases [] = +{ + { .run = run_working_cases, .produces_validation_info = true, .timeout = -1 }, + { .run = run_working_cases, .produces_validation_info = true, .timeout = 1 }, + { .run = pselect_invalid_fd, .produces_validation_info = false }, + { .run = test_ppoll_big, .produces_validation_info = false }, + { .run = ppoll_fds_buffer_overflow, .produces_validation_info = false }, + { .run = pselect_invalid_pointer, .produces_validation_info = false }, + { .run = ppoll_fds_ulong_max, .produces_validation_info = false }, + { .run = epoll_pwait_invalid_pointer, .produces_validation_info = true }, + { .run = epoll_pwait_int_max, .produces_validation_info = true }, + { .run = ppoll_concurrent_write, .produces_validation_info = false }, + { .run = epoll_pwait_concurrent_munmap, .produces_validation_info = true }, +}; + +struct ppoll_thread_data { + struct pollfd *ufds; + int value; +}; + +static +void test_select_big(void) +{ + fd_set rfds, wfds, exfds; + struct timeval tv; + int ret; + int fd2; + char buf[BUF_SIZE]; + + FD_ZERO(&rfds); + FD_ZERO(&wfds); + FD_ZERO(&exfds); + + fd2 = dup2(wait_fd, BIG_SELECT_FD); + if (fd2 < 0) { + PERROR("dup2"); + goto end; + } + FD_SET(fd2, &rfds); + + tv.tv_sec = 0; + tv.tv_usec = timeout * MSEC_PER_USEC; + + if (timeout > 0) { + ret = select(fd2 + 1, &rfds, &wfds, &exfds, &tv); + } else { + ret = select(fd2 + 1, &rfds, &wfds, &exfds, NULL); + } + + if (ret == -1) { + PERROR("select()"); + } else if (ret) { + ret = read(wait_fd, buf, BUF_SIZE); + if (ret < 0) { + PERROR("[select] read"); + } + } + + ret = close(BIG_SELECT_FD); + if (ret) { + PERROR("close"); + } + +end: + return; +} + +static +void test_pselect(void) +{ + fd_set rfds; + struct timespec tv; + int ret; + char buf[BUF_SIZE]; + + FD_ZERO(&rfds); + FD_SET(wait_fd, &rfds); + + tv.tv_sec = 0; + tv.tv_nsec = timeout * MSEC_PER_NSEC; + + if (timeout > 0) { + ret = pselect(1, &rfds, NULL, NULL, &tv, NULL); + } else { + ret = pselect(1, &rfds, NULL, NULL, NULL, NULL); + } + + if (ret == -1) { + PERROR("pselect()"); + } else if (ret) { + ret = read(wait_fd, buf, BUF_SIZE); + if (ret < 0) { + PERROR("[pselect] read"); + } + } +} + +static +void test_select(void) +{ + fd_set rfds; + struct timeval tv; + int ret; + char buf[BUF_SIZE]; + + FD_ZERO(&rfds); + FD_SET(wait_fd, &rfds); + + tv.tv_sec = 0; + tv.tv_usec = timeout * MSEC_PER_USEC; + + if (timeout > 0) { + ret = select(1, &rfds, NULL, NULL, &tv); + } else { + ret = select(1, &rfds, NULL, NULL, NULL); + } + + if (ret == -1) { + PERROR("select()"); + } else if (ret) { + ret = read(wait_fd, buf, BUF_SIZE); + if (ret < 0) { + PERROR("[select] read"); + } + } +} + +static +void test_poll(void) +{ + struct pollfd ufds[NB_FD]; + char buf[BUF_SIZE]; + int ret; + + ufds[0].fd = wait_fd; + ufds[0].events = POLLIN|POLLPRI; + + ret = poll(ufds, 1, timeout); + + if (ret < 0) { + PERROR("poll"); + } else if (ret > 0) { + ret = read(wait_fd, buf, BUF_SIZE); + if (ret < 0) { + PERROR("[poll] read"); + } + } +} + +static +void test_ppoll(void) +{ + struct pollfd ufds[NB_FD]; + char buf[BUF_SIZE]; + int ret; + struct timespec ts; + + ufds[0].fd = wait_fd; + ufds[0].events = POLLIN|POLLPRI; + + if (timeout > 0) { + ts.tv_sec = 0; + ts.tv_nsec = timeout * MSEC_PER_NSEC; + ret = ppoll(ufds, 1, &ts, NULL); + } else { + ret = ppoll(ufds, 1, NULL, NULL); + } + + + if (ret < 0) { + PERROR("ppoll"); + } else if (ret > 0) { + ret = read(wait_fd, buf, BUF_SIZE); + if (ret < 0) { + PERROR("[ppoll] read"); + } + } +} + +static +void test_ppoll_big(FILE *validation_output_file) +{ + struct pollfd ufds[MAX_FDS]; + char buf[BUF_SIZE]; + int ret, i, fds[MAX_FDS]; + + for (i = 0; i < MAX_FDS; i++) { + fds[i] = dup(wait_fd); + if (fds[i] < 0) { + PERROR("dup"); + } + ufds[i].fd = fds[i]; + ufds[i].events = POLLIN|POLLPRI; + } + + ret = ppoll(ufds, MAX_FDS, NULL, NULL); + + if (ret < 0) { + PERROR("ppoll"); + } else if (ret > 0) { + ret = read(wait_fd, buf, BUF_SIZE); + if (ret < 0) { + PERROR("[ppoll] read"); + } + } + + for (i = 0; i < MAX_FDS; i++) { + ret = close(fds[i]); + if (ret != 0) { + PERROR("close"); + } + } + + return; +} + +static +void test_epoll(FILE *validation_output_file) +{ + int ret, epollfd; + char buf[BUF_SIZE]; + struct epoll_event epoll_event; + + epollfd = epoll_create(NB_FD); + if (epollfd < 0) { + PERROR("[epoll] create"); + goto end; + } + + ret = fprintf(validation_output_file, + ", \"epoll_wait_fd\": %i", epollfd); + if (ret < 0) { + PERROR("[epoll] Failed to write test validation output"); + goto error; + } + + epoll_event.events = EPOLLIN | EPOLLPRI | EPOLLET; + epoll_event.data.fd = wait_fd; + ret = epoll_ctl(epollfd, EPOLL_CTL_ADD, wait_fd, &epoll_event); + if (ret < 0) { + PERROR("[epoll] add"); + goto error; + } + + if (timeout > 0) { + ret = epoll_wait(epollfd, &epoll_event, 1, timeout); + } else { + ret = epoll_wait(epollfd, &epoll_event, 1, -1); + } + + if (ret == 1) { + ret = read(wait_fd, buf, BUF_SIZE); + if (ret < 0) { + PERROR("[epoll] read"); + } + } else if (ret != 0) { + PERROR("epoll_wait"); + } + +error: + ret = close(epollfd); + if (ret) { + PERROR("close"); + } +end: + return; +} + +static +void test_epoll_pwait(FILE *validation_output_file) +{ + int ret, epollfd; + char buf[BUF_SIZE]; + struct epoll_event epoll_event; + + epollfd = epoll_create(NB_FD); + if (epollfd < 0) { + PERROR("[epoll_pwait] create"); + goto end; + } + + ret = fprintf(validation_output_file, + ", \"epoll_pwait_fd\": %i", epollfd); + if (ret < 0) { + PERROR("[epoll_pwait] Failed to write test validation output"); + goto error; + } + + epoll_event.events = EPOLLIN | EPOLLPRI | EPOLLET; + epoll_event.data.fd = wait_fd; + ret = epoll_ctl(epollfd, EPOLL_CTL_ADD, wait_fd, &epoll_event); + if (ret < 0) { + PERROR("[epoll_pwait] add"); + goto error; + } + + if (timeout > 0) { + ret = epoll_pwait(epollfd, &epoll_event, 1, timeout, NULL); + } else { + ret = epoll_pwait(epollfd, &epoll_event, 1, -1, NULL); + } + + if (ret == 1) { + ret = read(wait_fd, buf, BUF_SIZE); + if (ret < 0) { + PERROR("[epoll_pwait] read"); + } + } else if (ret != 0) { + PERROR("epoll_pwait"); + } + +error: + ret = close(epollfd); + if (ret) { + PERROR("close"); + } +end: + return; +} + +static +void run_working_cases(FILE *validation_output_file) +{ + int ret; + int pipe_fds[2]; + + if (timeout > 0) { + /* + * We need an input pipe for some cases and stdin might + * have random data, so we create a dummy pipe for this + * test to make sure we are running under clean conditions. + */ + ret = pipe(pipe_fds); + if (ret != 0) { + PERROR("pipe"); + goto end; + } + wait_fd = pipe_fds[0]; + } + test_select(); + test_pselect(); + test_select_big(); + test_poll(); + test_ppoll(); + + ret = fprintf(validation_output_file, "{ \"pid\": %i", getpid()); + if (ret < 0) { + PERROR("Failed to write pid to test validation file"); + goto end; + } + + test_epoll(validation_output_file); + test_epoll_pwait(validation_output_file); + + if (timeout > 0) { + ret = close(pipe_fds[0]); + if (ret) { + PERROR("close"); + } + ret = close(pipe_fds[1]); + if (ret) { + PERROR("close"); + } + } + + ret = fputs(" }", validation_output_file); + if (ret < 0) { + PERROR("Failed to close JSON dictionary in test validation file"); + goto end; + } + +end: + return; +} + +/* + * Ask for 100 FDs in a buffer for allocated for only 1 FD, should + * segfault (eventually with a "*** stack smashing detected ***" message). + * The event should contain an array of 100 FDs filled with garbage. + */ +static +void ppoll_fds_buffer_overflow(FILE *validation_output_file) +{ + struct pollfd ufds[NB_FD]; + char buf[BUF_SIZE]; + int ret; + + ufds[0].fd = wait_fd; + ufds[0].events = POLLIN|POLLPRI; + + ret = syscall(SYS_ppoll, ufds, 100, NULL, NULL); + + if (ret < 0) { + PERROR("ppoll"); + } else if (ret > 0) { + ret = read(wait_fd, buf, BUF_SIZE); + if (ret < 0) { + PERROR("[ppoll] read"); + } + } +} + +/* + * Ask for ULONG_MAX FDs in a buffer for allocated for only 1 FD, should + * cleanly fail with a "Invalid argument". + * The event should contain an empty array of FDs and overflow = 1. + */ +static +void ppoll_fds_ulong_max(FILE *validation_output_file) +{ + struct pollfd ufds[NB_FD]; + char buf[BUF_SIZE]; + int ret; + + ufds[0].fd = wait_fd; + ufds[0].events = POLLIN|POLLPRI; + + ret = syscall(SYS_ppoll, ufds, ULONG_MAX, NULL, NULL); + if (ret < 0) { + /* Expected error. */ + } else if (ret > 0) { + ret = read(wait_fd, buf, BUF_SIZE); + if (ret < 0) { + PERROR("[ppoll] read"); + } + } +} + +/* + * Pass an invalid file descriptor to pselect6(). The syscall should return + * -EBADF. The recorded event should contain a "ret = -EBADF (-9)". + */ +static +void pselect_invalid_fd(FILE *validation_output_file) +{ + fd_set rfds; + int ret; + int fd; + char buf[BUF_SIZE]; + + /* + * Open a file, close it and use the closed FD in the pselect6 call. + */ + fd = open("/dev/null", O_RDONLY); + if (fd == -1) { + PERROR("open"); + goto error; + } + + ret = close(fd); + if (ret == -1) { + PERROR("close"); + goto error; + } + + FD_ZERO(&rfds); + FD_SET(fd, &rfds); + + ret = syscall(SYS_pselect6, fd + 1, &rfds, NULL, NULL, NULL, NULL); + if (ret == -1) { + /* Expected error. */ + } else if (ret) { + ret = read(wait_fd, buf, BUF_SIZE); + if (ret < 0) { + PERROR("[pselect] read"); + } + } +error: + return; +} + +/* + * Invalid pointer as writefds, should output a ppoll event + * with 0 FDs. + */ +static +void pselect_invalid_pointer(FILE *validation_output_file) +{ + fd_set rfds; + int ret; + char buf[BUF_SIZE]; + void *invalid = (void *) 0x42; + + FD_ZERO(&rfds); + FD_SET(wait_fd, &rfds); + + ret = syscall(SYS_pselect6, 1, &rfds, (fd_set *) invalid, NULL, NULL, + NULL); + if (ret == -1) { + /* Expected error. */ + } else if (ret) { + ret = read(wait_fd, buf, BUF_SIZE); + if (ret < 0) { + PERROR("[pselect] read"); + } + } +} + +/* + * Pass an invalid pointer to epoll_pwait, should fail with + * "Bad address", the event returns 0 FDs. + */ +static +void epoll_pwait_invalid_pointer(FILE *validation_output_file) +{ + int ret, epollfd; + char buf[BUF_SIZE]; + struct epoll_event epoll_event; + void *invalid = (void *) 0x42; + + epollfd = epoll_create(NB_FD); + if (epollfd < 0) { + PERROR("[epoll_pwait] create"); + goto end; + } + + ret = fprintf(validation_output_file, + "{ \"epollfd\": %i, \"pid\": %i }", epollfd, + getpid()); + if (ret < 0) { + PERROR("[epoll_pwait] Failed to write test validation output"); + goto error; + } + + epoll_event.events = EPOLLIN | EPOLLPRI | EPOLLET; + epoll_event.data.fd = wait_fd; + ret = epoll_ctl(epollfd, EPOLL_CTL_ADD, wait_fd, &epoll_event); + if (ret < 0) { + PERROR("[epoll_pwait] add"); + goto error; + } + + ret = syscall(SYS_epoll_pwait, epollfd, + (struct epoll_event *) invalid, 1, -1, NULL); + + if (ret == 1) { + ret = read(wait_fd, buf, BUF_SIZE); + if (ret < 0) { + PERROR("[epoll_pwait] read"); + } + } else if (ret != 0) { + /* Expected error. */ + } + +error: + ret = close(epollfd); + if (ret) { + PERROR("close"); + } +end: + return; +} + +/* + * Set maxevents to INT_MAX, should output "Invalid argument" + * The event should return an empty array. + */ +static +void epoll_pwait_int_max(FILE *validation_output_file) +{ + int ret, epollfd; + char buf[BUF_SIZE]; + struct epoll_event epoll_event; + + epollfd = epoll_create(NB_FD); + if (epollfd < 0) { + PERROR("[epoll_pwait] create"); + goto end; + } + + ret = fprintf(validation_output_file, + "{ \"epollfd\": %i, \"pid\": %i }", epollfd, + getpid()); + if (ret < 0) { + PERROR("[epoll_pwait] Failed to write test validation output"); + goto error; + } + + epoll_event.events = EPOLLIN | EPOLLPRI | EPOLLET; + epoll_event.data.fd = wait_fd; + ret = epoll_ctl(epollfd, EPOLL_CTL_ADD, wait_fd, &epoll_event); + if (ret < 0) { + PERROR("[epoll_pwait] add"); + goto error; + } + + ret = syscall(SYS_epoll_pwait, epollfd, &epoll_event, INT_MAX, -1, + NULL); + + if (ret == 1) { + ret = read(wait_fd, buf, BUF_SIZE); + if (ret < 0) { + PERROR("[epoll_pwait] read"); + } + } else if (ret != 0) { + /* Expected error. */ + } + +error: + ret = close(epollfd); + if (ret) { + PERROR("close"); + } +end: + return; +} + +static +void *ppoll_writer(void *arg) +{ + struct ppoll_thread_data *data = (struct ppoll_thread_data *) arg; + + while (!stop_thread) { + memset(data->ufds, data->value, + MAX_FDS * sizeof(struct pollfd)); + usleep(100); + } + + return NULL; +} + +static +void do_ppoll(int *fds, struct pollfd *ufds) +{ + int i, ret; + struct timespec ts; + char buf[BUF_SIZE]; + + ts.tv_sec = 0; + ts.tv_nsec = 1 * MSEC_PER_NSEC; + + for (i = 0; i < MAX_FDS; i++) { + ufds[i].fd = fds[i]; + ufds[i].events = POLLIN|POLLPRI; + } + + ret = ppoll(ufds, MAX_FDS, &ts, NULL); + + if (ret < 0) { + PERROR("ppoll"); + } else if (ret > 0) { + ret = read(wait_fd, buf, BUF_SIZE); + if (ret < 0) { + PERROR("[ppoll] read"); + } + } +} + +static +void stress_ppoll(int *fds, int value) +{ + pthread_t writer; + int iter, ret; + struct ppoll_thread_data thread_data; + struct pollfd ufds[MAX_FDS]; + + thread_data.ufds = ufds; + thread_data.value = value; + + stop_thread = 0; + ret = pthread_create(&writer, NULL, &ppoll_writer, (void *) &thread_data); + if (ret != 0) { + fprintf(stderr, "[error] pthread_create\n"); + goto end; + } + for (iter = 0; iter < NR_ITER; iter++) { + do_ppoll(fds, ufds); + } + stop_thread = 1; + ret = pthread_join(writer, NULL); + if (ret) { + fprintf(stderr, "[error] pthread_join\n"); + goto end; + } +end: + return; +} + +/* + * 3 rounds of NR_ITER iterations with concurrent updates of the pollfd + * structure: + * - memset to 0 + * - memset to 1 + * - memset to INT_MAX + * Waits for input, but also set a timeout in case the input FD is overwritten + * before entering in the syscall. We use MAX_FDS FDs (dup of stdin), so the + * resulting trace is big (20MB). + * + * ppoll should work as expected and the trace should be readable at the end. + */ +static +void ppoll_concurrent_write(FILE *validation_output_file) +{ + int i, ret, fds[MAX_FDS]; + + for (i = 0; i < MAX_FDS; i++) { + fds[i] = dup(wait_fd); + if (fds[i] < 0) { + PERROR("dup"); + } + } + + stress_ppoll(fds, 0); + stress_ppoll(fds, 1); + stress_ppoll(fds, INT_MAX); + + for (i = 0; i < MAX_FDS; i++) { + ret = close(fds[i]); + if (ret != 0) { + PERROR("close"); + } + } + + return; +} + +static +void *epoll_pwait_writer(void *addr) +{ + srand(time(NULL)); + + while (!stop_thread) { + usleep(rand() % 30); + munmap(addr, MAX_FDS * sizeof(struct epoll_event)); + } + + return NULL; +} + +/* + * epoll_pwait on MAX_FDS fds while a concurrent thread munmaps the + * buffer allocated for the returned data. This should randomly segfault. + * The trace should be readable and no kernel OOPS should occur. + */ +static +void epoll_pwait_concurrent_munmap(FILE *validation_output_file) +{ + int ret, epollfd, i, fds[MAX_FDS]; + char buf[BUF_SIZE]; + struct epoll_event *epoll_event; + pthread_t writer; + + for (i = 0; i < MAX_FDS; i++) { + fds[i] = -1; + } + epollfd = epoll_create(MAX_FDS); + if (epollfd < 0) { + PERROR("[epoll_pwait] create"); + goto end; + } + + ret = fprintf(validation_output_file, + "{ \"epollfd\": %i, \"pid\": %i }", epollfd, + getpid()); + if (ret < 0) { + PERROR("[epoll_pwait] Failed to write test validation output"); + goto error; + } + + epoll_event = (struct epoll_event *) mmap(NULL, + MAX_FDS * sizeof(struct epoll_event), + PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, + 0); + if (epoll_event == MAP_FAILED) { + PERROR("mmap"); + goto error; + } + + for (i = 0; i < MAX_FDS; i++) { + fds[i] = dup(wait_fd); + if (fds[i] < 0) { + PERROR("dup"); + } + epoll_event[i].events = EPOLLIN | EPOLLPRI | EPOLLET; + epoll_event[i].data.fd = fds[i]; + ret = epoll_ctl(epollfd, EPOLL_CTL_ADD, fds[i], epoll_event); + if (ret < 0) { + PERROR("[epoll_pwait] add"); + goto error_unmap; + } + } + stop_thread = 0; + ret = pthread_create(&writer, NULL, &epoll_pwait_writer, + (void *) epoll_event); + if (ret != 0) { + fprintf(stderr, "[error] pthread_create\n"); + goto error_unmap; + } + + ret = epoll_pwait(epollfd, epoll_event, 1, 1, NULL); + + if (ret == 1) { + ret = read(wait_fd, buf, BUF_SIZE); + if (ret < 0) { + PERROR("[epoll_pwait] read"); + } + } else if (ret != 0) { + /* Expected error. */ + } + + stop_thread = 1; + ret = pthread_join(writer, NULL); + if (ret) { + fprintf(stderr, "[error] pthread_join\n"); + goto error_unmap; + } +error_unmap: + for (i = 0; i < MAX_FDS; i++) { + ret = close(fds[i]); + if (ret != 0) { + PERROR("close"); + } + } + + ret = munmap(epoll_event, MAX_FDS * sizeof(struct epoll_event)); + if (ret != 0) { + PERROR("munmap"); + } + +error: + ret = close(epollfd); + if (ret) { + PERROR("close"); + } +end: + return; +} + +static +void print_list(void) +{ + fprintf(stderr, "Test list (-t X):\n"); + fprintf(stderr, "\t1: Working cases for select, pselect6, poll, ppoll " + "and epoll, waiting for input\n"); + fprintf(stderr, "\t2: Timeout cases (1ms) for select, pselect6, poll, " + "ppoll and epoll\n"); + fprintf(stderr, "\t3: pselect with an invalid fd\n"); + fprintf(stderr, "\t4: ppoll with %d FDs\n", MAX_FDS); + fprintf(stderr, "\t5: ppoll buffer overflow, should segfault, waits " + "for input\n"); + fprintf(stderr, "\t6: pselect with an invalid pointer, waits for " + "input\n"); + fprintf(stderr, "\t7: ppoll with ulong_max fds, waits for input\n"); + fprintf(stderr, "\t8: epoll_pwait with an invalid pointer, waits for " + "input\n"); + fprintf(stderr, "\t9: epoll_pwait with maxevents set to INT_MAX, " + "waits for input\n"); + fprintf(stderr, "\t10: ppoll with concurrent updates of the structure " + "from user-space, stress test (3000 iterations), " + "waits for input + timeout 1ms\n"); + fprintf(stderr, "\t11: epoll_pwait with concurrent munmap of the buffer " + "from user-space, should randomly segfault, run " + "multiple times, waits for input + timeout 1ms\n"); +} + +int main(int argc, const char **argv) +{ + int c, ret, test = -1; + poptContext optCon; + struct rlimit open_lim; + FILE *test_validation_output_file = NULL; + const char *test_validation_output_file_path = NULL; + struct poptOption optionsTable[] = { + { "test", 't', POPT_ARG_INT, &test, 0, + "Test to run", NULL }, + { "list", 'l', 0, 0, 'l', + "List of tests (-t X)", NULL }, + { "validation-file", 'o', POPT_ARG_STRING, &test_validation_output_file_path, 0, + "Test case output", NULL }, + POPT_AUTOHELP + { NULL, 0, 0, NULL, 0 } + }; + const struct test_case *test_case; + + optCon = poptGetContext(NULL, argc, argv, optionsTable, 0); + + if (argc < 2) { + poptPrintUsage(optCon, stderr, 0); + ret = -1; + goto end; + } + + ret = 0; + + while ((c = poptGetNextOpt(optCon)) >= 0) { + switch (c) { + case 'l': + print_list(); + goto end; + } + } + + if (!test_validation_output_file_path) { + fprintf(stderr, "A test validation file path is required (--validation-file/-o)\n"); + ret = -1; + goto end; + } + + test_validation_output_file = fopen(test_validation_output_file_path, "w+"); + if (!test_validation_output_file) { + PERROR("Failed to create test validation output file at '%s'", + test_validation_output_file_path); + ret = -1; + goto end; + } + + open_lim.rlim_cur = MAX_FDS + MIN_NR_FDS; + open_lim.rlim_max = MAX_FDS + MIN_NR_FDS; + + ret = setrlimit(RLIMIT_NOFILE, &open_lim); + if (ret < 0) { + PERROR("setrlimit"); + goto end; + } + + /* + * Some tests might segfault, but we need the getpid() to be output + * for the validation, disabling the buffering on the validation file + * works. + */ + setbuf(test_validation_output_file, NULL); + wait_fd = STDIN_FILENO; + + /* Test case id is 1-based. */ + if (test < 1 || test > ARRAY_SIZE(test_cases)) { + poptPrintUsage(optCon, stderr, 0); + ret = -1; + } + + test_case = &test_cases[test - 1]; + + timeout = test_case->timeout; + if (!test_case->produces_validation_info) { + /* + * All test cases need to provide, at minimum, the pid of the + * test application. + */ + ret = fprintf(test_validation_output_file, "{ \"pid\": %i }", getpid()); + if (ret < 0) { + PERROR("Failed to write application pid to test validation file"); + goto end; + } + } + + test_case->run(test_validation_output_file); + +end: + if (test_validation_output_file) { + const int close_ret = fclose(test_validation_output_file); + + if (close_ret) { + PERROR("Failed to close test output file"); + } + } + poptFreeContext(optCon); + return ret; +} diff --git a/tests/regression/kernel/test_select_poll_epoll b/tests/regression/kernel/test_select_poll_epoll index 16b0da525..6d5509b25 100755 --- a/tests/regression/kernel/test_select_poll_epoll +++ b/tests/regression/kernel/test_select_poll_epoll @@ -7,7 +7,7 @@ TEST_DESC="Kernel tracer - select, poll and epoll payload extraction" -CURDIR=$(dirname $0)/ +CURDIR=$(dirname "$0")/ TESTDIR=$CURDIR/../.. VALIDATE_SCRIPT="$CURDIR/validate_select_poll_epoll.py" NUM_TESTS=102 @@ -33,6 +33,7 @@ LAST_WARNING=$(dmesg | grep " WARNING:" | cut -d' ' -f1 | tail -1) LAST_OOPS=$(dmesg | grep " OOPS:" | cut -d' ' -f1 | tail -1) LAST_BUG=$(dmesg | grep " BUG:" | cut -d' ' -f1 | tail -1) +# shellcheck source=../../utils/utils.sh source $TESTDIR/utils/utils.sh function check_trace_content() @@ -52,8 +53,9 @@ function check_trace_content() function test_working_cases() { - TRACE_PATH=$(mktemp --tmpdir -d "tmp.${FUNCNAME[0]}_trace_path.XXXXXX") SESSION_NAME="syscall_payload" + TRACE_PATH=$(mktemp --tmpdir -d "tmp.test_kernel_select_poll_epoll_${FUNCNAME[0]}_trace_path.XXXXXX") + TEST_VALIDATION_OUTPUT_PATH=$(mktemp --tmpdir -u "tmp.test_kernel_select_poll_epoll_${FUNCNAME[0]}_validation.XXXXXX") # arm64 does not have epoll_wait uname -m | grep -E "aarch64" >/dev/null 2>&1 @@ -65,28 +67,29 @@ function test_working_cases() diag "Working cases for select, pselect6, poll, ppoll and epoll, waiting for input" - create_lttng_session_ok $SESSION_NAME $TRACE_PATH + create_lttng_session_ok $SESSION_NAME "$TRACE_PATH" lttng_enable_kernel_syscall_ok $SESSION_NAME $SYSCALL_LIST add_context_kernel_ok $SESSION_NAME channel0 pid start_lttng_tracing_ok - { out=$(yes | $CURDIR/select_poll_epoll -t 1); } 2>/dev/null + yes | "$CURDIR"/select_poll_epoll --validation-file "$TEST_VALIDATION_OUTPUT_PATH" -t 1 stop_lttng_tracing_ok - pid=$(echo $out | cut -d' ' -f1) - validate_trace "$SYSCALL_LIST" $TRACE_PATH - check_trace_content -t 1 -p $pid $TRACE_PATH + validate_trace "$SYSCALL_LIST" "$TRACE_PATH" + check_trace_content -t 1 --validation-file "$TEST_VALIDATION_OUTPUT_PATH" "$TRACE_PATH" destroy_lttng_session_ok $SESSION_NAME - rm -rf $TRACE_PATH + rm -rf "$TRACE_PATH" + rm -f "$TEST_VALIDATION_OUTPUT_PATH" } function test_timeout_cases() { - TRACE_PATH=$(mktemp --tmpdir -d "tmp.${FUNCNAME[0]}_trace_path.XXXXXX") SESSION_NAME="syscall_payload" + TRACE_PATH=$(mktemp --tmpdir -d "tmp.test_kernel_select_poll_epoll_${FUNCNAME[0]}_trace_path.XXXXXX") + TEST_VALIDATION_OUTPUT_PATH=$(mktemp --tmpdir -u "tmp.test_kernel_select_poll_epoll_${FUNCNAME[0]}_validation.XXXXXX") # arm64 does not have epoll_wait uname -m | grep -E "aarch64" >/dev/null 2>&1 @@ -98,244 +101,252 @@ function test_timeout_cases() diag "Timeout cases (1ms) for select, pselect6, poll, ppoll and epoll" - create_lttng_session_ok $SESSION_NAME $TRACE_PATH + create_lttng_session_ok $SESSION_NAME "$TRACE_PATH" lttng_enable_kernel_syscall_ok $SESSION_NAME "$SYSCALL_LIST" add_context_kernel_ok $SESSION_NAME channel0 pid start_lttng_tracing_ok - { out=$($CURDIR/select_poll_epoll -t 2); } 2>/dev/null + yes | "$CURDIR"/select_poll_epoll --validation-file "$TEST_VALIDATION_OUTPUT_PATH" -t 2 stop_lttng_tracing_ok - pid=$(echo $out | cut -d' ' -f1) - validate_trace "$SYSCALL_LIST" $TRACE_PATH - check_trace_content -t 2 -p $pid $TRACE_PATH 2>/dev/null + validate_trace "$SYSCALL_LIST" "$TRACE_PATH" + check_trace_content -t 2 --validation-file "$TEST_VALIDATION_OUTPUT_PATH" "$TRACE_PATH" 2>/dev/null destroy_lttng_session_ok $SESSION_NAME - rm -rf $TRACE_PATH + rm -rf "$TRACE_PATH" + rm -f "$TEST_VALIDATION_OUTPUT_PATH" } function test_pselect_invalid_fd() { - TRACE_PATH=$(mktemp --tmpdir -d "tmp.${FUNCNAME[0]}_trace_path.XXXXXX") SESSION_NAME="syscall_payload" SYSCALL_LIST="pselect6" + TRACE_PATH=$(mktemp --tmpdir -d "tmp.test_kernel_select_poll_epoll_${FUNCNAME[0]}_trace_path.XXXXXX") + TEST_VALIDATION_OUTPUT_PATH=$(mktemp --tmpdir -u "tmp.test_kernel_select_poll_epoll_${FUNCNAME[0]}_validation.XXXXXX") diag "pselect with invalid FD" - create_lttng_session_ok $SESSION_NAME $TRACE_PATH + create_lttng_session_ok $SESSION_NAME "$TRACE_PATH" lttng_enable_kernel_syscall_ok $SESSION_NAME $SYSCALL_LIST add_context_kernel_ok $SESSION_NAME channel0 pid start_lttng_tracing_ok - { out=$($CURDIR/select_poll_epoll -t 3); } 2>/dev/null + yes | "$CURDIR"/select_poll_epoll --validation-file "$TEST_VALIDATION_OUTPUT_PATH" -t 3 stop_lttng_tracing_ok - pid=$(echo $out | cut -d' ' -f1) - validate_trace "$SYSCALL_LIST" $TRACE_PATH - check_trace_content -t 3 -p $pid $TRACE_PATH 2>/dev/null + validate_trace "$SYSCALL_LIST" "$TRACE_PATH" + check_trace_content -t 3 --validation-file "$TEST_VALIDATION_OUTPUT_PATH" "$TRACE_PATH" 2>/dev/null destroy_lttng_session_ok $SESSION_NAME - rm -rf $TRACE_PATH + rm -rf "$TRACE_PATH" + rm -f "$TEST_VALIDATION_OUTPUT_PATH" } function test_big_ppoll() { - TRACE_PATH=$(mktemp --tmpdir -d "tmp.${FUNCNAME[0]}_trace_path.XXXXXX") SESSION_NAME="syscall_payload" SYSCALL_LIST="ppoll" + TRACE_PATH=$(mktemp --tmpdir -d "tmp.test_kernel_select_poll_epoll_${FUNCNAME[0]}_trace_path.XXXXXX") + TEST_VALIDATION_OUTPUT_PATH=$(mktemp --tmpdir -u "tmp.test_kernel_select_poll_epoll_${FUNCNAME[0]}_validation.XXXXXX") diag "ppoll with 2047 FDs" - create_lttng_session_ok $SESSION_NAME $TRACE_PATH + create_lttng_session_ok $SESSION_NAME "$TRACE_PATH" lttng_enable_kernel_syscall_ok $SESSION_NAME $SYSCALL_LIST add_context_kernel_ok $SESSION_NAME channel0 pid start_lttng_tracing_ok - { out=$(yes | $CURDIR/select_poll_epoll -t 4); } 2>/dev/null + yes | "$CURDIR"/select_poll_epoll --validation-file "$TEST_VALIDATION_OUTPUT_PATH" -t 4 stop_lttng_tracing_ok - pid=$(echo $out | cut -d' ' -f1) - validate_trace "$SYSCALL_LIST" $TRACE_PATH - check_trace_content -t 4 -p $pid $TRACE_PATH 2>/dev/null + validate_trace "$SYSCALL_LIST" "$TRACE_PATH" + check_trace_content -t 4 --validation-file "$TEST_VALIDATION_OUTPUT_PATH" "$TRACE_PATH" 2>/dev/null destroy_lttng_session_ok $SESSION_NAME - rm -rf $TRACE_PATH + rm -rf "$TRACE_PATH" + rm -f "$TEST_VALIDATION_OUTPUT_PATH" } function test_ppoll_overflow() { - TRACE_PATH=$(mktemp --tmpdir -d "tmp.${FUNCNAME[0]}_trace_path.XXXXXX") SESSION_NAME="syscall_payload" SYSCALL_LIST="ppoll" + TRACE_PATH=$(mktemp --tmpdir -d "tmp.test_kernel_select_poll_epoll_${FUNCNAME[0]}_trace_path.XXXXXX") + TEST_VALIDATION_OUTPUT_PATH=$(mktemp --tmpdir -u "tmp.test_kernel_select_poll_epoll_${FUNCNAME[0]}_validation.XXXXXX") diag "ppoll buffer overflow, should segfault, waits for input" - create_lttng_session_ok $SESSION_NAME $TRACE_PATH + create_lttng_session_ok $SESSION_NAME "$TRACE_PATH" lttng_enable_kernel_syscall_ok $SESSION_NAME $SYSCALL_LIST add_context_kernel_ok $SESSION_NAME channel0 pid start_lttng_tracing_ok diag "Expect segfaults" - { out=$(yes | $CURDIR/select_poll_epoll -t 5); } 2>/dev/null + yes | "$CURDIR"/select_poll_epoll --validation-file "$TEST_VALIDATION_OUTPUT_PATH" -t 5 stop_lttng_tracing_ok - echo $out - pid=$(echo $out | cut -d' ' -f1) - validate_trace "$SYSCALL_LIST" $TRACE_PATH + validate_trace "$SYSCALL_LIST" "$TRACE_PATH" - check_trace_content -t 5 -p $pid $TRACE_PATH 2>/dev/null + check_trace_content -t 5 --validation-file "$TEST_VALIDATION_OUTPUT_PATH" "$TRACE_PATH" 2>/dev/null destroy_lttng_session_ok $SESSION_NAME - rm -rf $TRACE_PATH + rm -rf "$TRACE_PATH" + rm -f "$TEST_VALIDATION_OUTPUT_PATH" } function test_pselect_invalid_ptr() { - TRACE_PATH=$(mktemp --tmpdir -d "tmp.${FUNCNAME[0]}_trace_path.XXXXXX") SESSION_NAME="syscall_payload" SYSCALL_LIST="pselect6" + TRACE_PATH=$(mktemp --tmpdir -d "tmp.test_kernel_select_poll_epoll_${FUNCNAME[0]}_trace_path.XXXXXX") + TEST_VALIDATION_OUTPUT_PATH=$(mktemp --tmpdir -u "tmp.test_kernel_select_poll_epoll_${FUNCNAME[0]}_validation.XXXXXX") diag "pselect with invalid pointer, waits for input" - create_lttng_session_ok $SESSION_NAME $TRACE_PATH + create_lttng_session_ok $SESSION_NAME "$TRACE_PATH" lttng_enable_kernel_syscall_ok $SESSION_NAME $SYSCALL_LIST add_context_kernel_ok $SESSION_NAME channel0 pid start_lttng_tracing_ok - { out=$(yes | $CURDIR/select_poll_epoll -t 6); } 2>/dev/null + yes | "$CURDIR"/select_poll_epoll --validation-file "$TEST_VALIDATION_OUTPUT_PATH" -t 6 stop_lttng_tracing_ok - pid=$(echo $out | cut -d' ' -f1) - validate_trace "$SYSCALL_LIST" $TRACE_PATH - check_trace_content -t 6 -p $pid $TRACE_PATH 2>/dev/null + validate_trace "$SYSCALL_LIST" "$TRACE_PATH" + check_trace_content -t 6 --validation-file "$TEST_VALIDATION_OUTPUT_PATH" "$TRACE_PATH" 2>/dev/null destroy_lttng_session_ok $SESSION_NAME - rm -rf $TRACE_PATH + rm -rf "$TRACE_PATH" + rm -f "$TEST_VALIDATION_OUTPUT_PATH" } function test_ppoll_ulong_max() { - TRACE_PATH=$(mktemp --tmpdir -d "tmp.${FUNCNAME[0]}_trace_path.XXXXXX") SESSION_NAME="syscall_payload" SYSCALL_LIST="ppoll" + TRACE_PATH=$(mktemp --tmpdir -d "tmp.test_kernel_select_poll_epoll_${FUNCNAME[0]}_trace_path.XXXXXX") + TEST_VALIDATION_OUTPUT_PATH=$(mktemp --tmpdir -u "tmp.test_kernel_select_poll_epoll_${FUNCNAME[0]}_validation.XXXXXX") diag "ppoll with ulong_max fds, waits for input" - create_lttng_session_ok $SESSION_NAME $TRACE_PATH + create_lttng_session_ok $SESSION_NAME "$TRACE_PATH" lttng_enable_kernel_syscall_ok $SESSION_NAME $SYSCALL_LIST add_context_kernel_ok $SESSION_NAME channel0 pid start_lttng_tracing_ok - { out=$(yes | $CURDIR/select_poll_epoll -t 7); } 2>/dev/null + yes | "$CURDIR"/select_poll_epoll --validation-file "$TEST_VALIDATION_OUTPUT_PATH" -t 7 stop_lttng_tracing_ok - pid=$(echo $out | cut -d' ' -f1) - validate_trace "$SYSCALL_LIST" $TRACE_PATH - check_trace_content -t 7 -p $pid $TRACE_PATH 2>/dev/null + validate_trace "$SYSCALL_LIST" "$TRACE_PATH" + check_trace_content -t 7 --validation-file "$TEST_VALIDATION_OUTPUT_PATH" "$TRACE_PATH" 2>/dev/null destroy_lttng_session_ok $SESSION_NAME - rm -rf $TRACE_PATH + rm -rf "$TRACE_PATH" + rm -f "$TEST_VALIDATION_OUTPUT_PATH" } function test_epoll_pwait_invalid_ptr() { - TRACE_PATH=$(mktemp --tmpdir -d "tmp.${FUNCNAME[0]}_trace_path.XXXXXX") SESSION_NAME="syscall_payload" SYSCALL_LIST="epoll_pwait" + TRACE_PATH=$(mktemp --tmpdir -d "tmp.test_kernel_select_poll_epoll_${FUNCNAME[0]}_trace_path.XXXXXX") + TEST_VALIDATION_OUTPUT_PATH=$(mktemp --tmpdir -u "tmp.test_kernel_select_poll_epoll_${FUNCNAME[0]}_validation.XXXXXX") diag "epoll_pwait with invalid pointer, waits for input" - create_lttng_session_ok $SESSION_NAME $TRACE_PATH + create_lttng_session_ok $SESSION_NAME "$TRACE_PATH" lttng_enable_kernel_syscall_ok $SESSION_NAME $SYSCALL_LIST add_context_kernel_ok $SESSION_NAME channel0 pid start_lttng_tracing_ok - { out=$(yes | $CURDIR/select_poll_epoll -t 8); } 2>/dev/null + yes | "$CURDIR"/select_poll_epoll --validation-file "$TEST_VALIDATION_OUTPUT_PATH" -t 8 stop_lttng_tracing_ok - pid=$(echo $out | cut -d' ' -f1) - validate_trace "$SYSCALL_LIST" $TRACE_PATH - check_trace_content -t 8 -p $pid $TRACE_PATH 2>/dev/null + validate_trace "$SYSCALL_LIST" "$TRACE_PATH" + check_trace_content -t 8 --validation-file "$TEST_VALIDATION_OUTPUT_PATH" "$TRACE_PATH" 2>/dev/null destroy_lttng_session_ok $SESSION_NAME - rm -rf $TRACE_PATH + rm -rf "$TRACE_PATH" + rm -f "$TEST_VALIDATION_OUTPUT_PATH" } function test_epoll_pwait_int_max() { - TRACE_PATH=$(mktemp --tmpdir -d "tmp.${FUNCNAME[0]}_trace_path.XXXXXX") SESSION_NAME="syscall_payload" SYSCALL_LIST="epoll_pwait" + TRACE_PATH=$(mktemp --tmpdir -d "tmp.test_kernel_select_poll_epoll_${FUNCNAME[0]}_trace_path.XXXXXX") + TEST_VALIDATION_OUTPUT_PATH=$(mktemp --tmpdir -u "tmp.test_kernel_select_poll_epoll_${FUNCNAME[0]}_validation.XXXXXX") diag "epoll_pwait with maxevents set to INT_MAX, waits for input" - create_lttng_session_ok $SESSION_NAME $TRACE_PATH + create_lttng_session_ok $SESSION_NAME "$TRACE_PATH" lttng_enable_kernel_syscall_ok $SESSION_NAME $SYSCALL_LIST add_context_kernel_ok $SESSION_NAME channel0 pid start_lttng_tracing_ok - { out=$(yes | $CURDIR/select_poll_epoll -t 9); } 2>/dev/null + yes | "$CURDIR"/select_poll_epoll --validation-file "$TEST_VALIDATION_OUTPUT_PATH" -t 9 stop_lttng_tracing_ok - pid=$(echo $out | cut -d' ' -f1) - validate_trace "$SYSCALL_LIST" $TRACE_PATH - check_trace_content -t 9 -p $pid $TRACE_PATH 2>/dev/null + validate_trace "$SYSCALL_LIST" "$TRACE_PATH" + check_trace_content -t 9 --validation-file "$TEST_VALIDATION_OUTPUT_PATH" "$TRACE_PATH" 2>/dev/null destroy_lttng_session_ok $SESSION_NAME - rm -rf $TRACE_PATH + rm -rf "$TRACE_PATH" + rm -f "$TEST_VALIDATION_OUTPUT_PATH" } function test_ppoll_concurrent() { - TRACE_PATH=$(mktemp --tmpdir -d "tmp.${FUNCNAME[0]}_trace_path.XXXXXX") SESSION_NAME="syscall_payload" SYSCALL_LIST="ppoll" + TRACE_PATH=$(mktemp --tmpdir -d "tmp.test_kernel_select_poll_epoll_${FUNCNAME[0]}_trace_path.XXXXXX") + TEST_VALIDATION_OUTPUT_PATH=$(mktemp --tmpdir -u "tmp.test_kernel_select_poll_epoll_${FUNCNAME[0]}_validation.XXXXXX") diag "ppoll with concurrent updates of the structure from user-space, stress test (3000 iterations), waits for input + timeout 1ms" - create_lttng_session_ok $SESSION_NAME $TRACE_PATH + create_lttng_session_ok $SESSION_NAME "$TRACE_PATH" lttng_enable_kernel_syscall_ok $SESSION_NAME $SYSCALL_LIST add_context_kernel_ok $SESSION_NAME channel0 pid start_lttng_tracing_ok - { out=$(yes | $CURDIR/select_poll_epoll -t 10); } 2>/dev/null + yes | "$CURDIR"/select_poll_epoll --validation-file "$TEST_VALIDATION_OUTPUT_PATH" -t 10 stop_lttng_tracing_ok - pid=$(echo $out | cut -d' ' -f1) - validate_trace "$SYSCALL_LIST" $TRACE_PATH - check_trace_content -t 10 -p $pid $TRACE_PATH 2>/dev/null + validate_trace "$SYSCALL_LIST" "$TRACE_PATH" + check_trace_content -t 10 --validation-file "$TEST_VALIDATION_OUTPUT_PATH" "$TRACE_PATH" 2>/dev/null destroy_lttng_session_ok $SESSION_NAME - rm -rf $TRACE_PATH + rm -rf "$TRACE_PATH" + rm -f "$TEST_VALIDATION_OUTPUT_PATH" } function test_epoll_pwait_concurrent() { - TRACE_PATH=$(mktemp --tmpdir -d "tmp.${FUNCNAME[0]}_trace_path.XXXXXX") SESSION_NAME="syscall_payload" SYSCALL_LIST="epoll_ctl,epoll_pwait" + TRACE_PATH=$(mktemp --tmpdir -d "tmp.test_kernel_select_poll_epoll_${FUNCNAME[0]}_trace_path.XXXXXX") + TEST_VALIDATION_OUTPUT_PATH=$(mktemp --tmpdir -u "tmp.test_kernel_select_poll_epoll_${FUNCNAME[0]}_validation.XXXXXX") diag "epoll_pwait with concurrent munmap of the buffer from user-space, should randomly segfault, run multiple times, waits for input + timeout 1ms" - create_lttng_session_ok $SESSION_NAME $TRACE_PATH + create_lttng_session_ok $SESSION_NAME "$TRACE_PATH" lttng_enable_kernel_syscall_ok $SESSION_NAME $SYSCALL_LIST add_context_kernel_ok $SESSION_NAME channel0 pid @@ -343,18 +354,18 @@ function test_epoll_pwait_concurrent() start_lttng_tracing_ok diag "Expect segfaults" for i in $(seq 1 100); do - { out=$($CURDIR/select_poll_epoll -t 11); } 2>/dev/null + yes | "$CURDIR"/select_poll_epoll --validation-file "$TEST_VALIDATION_OUTPUT_PATH" -t 11 done - pid=$(echo $out | cut -d' ' -f1) stop_lttng_tracing_ok # epoll_wait is not always generated in the trace (stress test) - validate_trace "epoll_ctl" $TRACE_PATH - check_trace_content -t 11 -p $pid $TRACE_PATH 2>/dev/null + validate_trace "epoll_ctl" "$TRACE_PATH" + check_trace_content -t 11 --validation-file "$TEST_VALIDATION_OUTPUT_PATH" "$TRACE_PATH" 2>/dev/null destroy_lttng_session_ok $SESSION_NAME - rm -rf $TRACE_PATH + rm -rf "$TRACE_PATH" + rm -f "$TEST_VALIDATION_OUTPUT_PATH" } # MUST set TESTDIR before calling those functions diff --git a/tests/regression/kernel/validate_select_poll_epoll.py b/tests/regression/kernel/validate_select_poll_epoll.py index b0cb590db..97c7764c0 100755 --- a/tests/regression/kernel/validate_select_poll_epoll.py +++ b/tests/regression/kernel/validate_select_poll_epoll.py @@ -9,6 +9,7 @@ import argparse import pprint import sys import time +import json from collections import defaultdict @@ -110,16 +111,16 @@ class TraceParser: self.epoll_wait_exit(event) def handle_compat_syscall_entry_epoll_pwait(self, event): - self.epoll_wait_entry(event) + self.epoll_pwait_entry(event) def handle_compat_syscall_exit_epoll_pwait(self, event): - self.epoll_wait_exit(event) + self.epoll_pwait_exit(event) def handle_syscall_entry_epoll_pwait(self, event): - self.epoll_wait_entry(event) + self.epoll_pwait_entry(event) def handle_syscall_exit_epoll_pwait(self, event): - self.epoll_wait_exit(event) + self.epoll_pwait_exit(event) def epoll_wait_entry(self, event): pass @@ -127,6 +128,12 @@ class TraceParser: def epoll_wait_exit(self, event): pass + def epoll_pwait_entry(self, event): + self.epoll_wait_entry(event) + + def epoll_pwait_exit(self, event): + self.epoll_wait_exit(event) + ## poll + ppoll def handle_compat_syscall_entry_poll(self, event): self.poll_entry(event) @@ -222,8 +229,13 @@ class TraceParser: class Test1(TraceParser): - def __init__(self, trace, pid): - super().__init__(trace, pid) + def __init__(self, trace, validation_args): + super().__init__(trace, validation_args['pid']) + + # Values expected in the trace + self.epoll_wait_fd = validation_args['epoll_wait_fd'] + self.epoll_pwait_fd = validation_args['epoll_pwait_fd'] + self.expect["select_entry"]["select_in_fd0"] = 0 self.expect["select_entry"]["select_in_fd1023"] = 0 self.expect["select_exit"]["select_out_fd0"] = 0 @@ -234,6 +246,8 @@ class Test1(TraceParser): self.expect["epoll_ctl_exit"]["epoll_ctl_out_ok"] = 0 self.expect["epoll_wait_entry"]["epoll_wait_in_ok"] = 0 self.expect["epoll_wait_exit"]["epoll_wait_out_fd0"] = 0 + self.expect["epoll_pwait_entry"]["epoll_pwait_in_ok"] = 0 + self.expect["epoll_pwait_exit"]["epoll_pwait_out_fd0"] = 0 def select_entry(self, event): n = event["n"] @@ -319,7 +333,7 @@ class Test1(TraceParser): # check that we have FD 0 waiting for EPOLLIN|EPOLLPRI and that # data.fd = 0 - if epfd == 3 and 'EPOLL_CTL_ADD' in op_enum.labels and fd == 0 and \ + if (epfd == self.epoll_wait_fd or epfd == self.epoll_pwait_fd) and 'EPOLL_CTL_ADD' in op_enum.labels and fd == 0 and \ _event["data_union"]["fd"] == 0 and \ _event["events"]["EPOLLIN"] == 1 and \ _event["events"]["EPOLLPRI"] == 1: @@ -342,7 +356,7 @@ class Test1(TraceParser): maxevents = event["maxevents"] timeout = event["timeout"] - if epfd == 3 and maxevents == 1 and timeout == -1: + if epfd == self.epoll_wait_fd and maxevents == 1 and timeout == -1: self.expect["epoll_wait_entry"]["epoll_wait_in_ok"] = 1 # Save values of local variables to print in case of test failure @@ -363,10 +377,35 @@ class Test1(TraceParser): # Save values of local variables to print in case of test failure self.recorded_values["epoll_wait_exit"] = locals() + def epoll_pwait_entry(self, event): + epfd = event["epfd"] + maxevents = event["maxevents"] + timeout = event["timeout"] + + if epfd == self.epoll_pwait_fd and maxevents == 1 and timeout == -1: + self.expect["epoll_pwait_entry"]["epoll_pwait_in_ok"] = 1 + + # Save values of local variables to print in case of test failure + self.recorded_values["epoll_pwait_entry"] = locals() + + def epoll_pwait_exit(self, event): + ret = event["ret"] + fds_length = event["fds_length"] + overflow = event["overflow"] + + # check that FD 0 returned with EPOLLIN and the right data.fd + if ret == 1 and fds_length == 1: + fd_0 = event["fds"][0] + if overflow == 0 and fd_0["data_union"]["fd"] == 0 and \ + fd_0["events"]["EPOLLIN"] == 1: + self.expect["epoll_pwait_exit"]["epoll_pwait_out_fd0"] = 1 + + # Save values of local variables to print in case of test failure + self.recorded_values["epoll_pwait_exit"] = locals() class Test2(TraceParser): - def __init__(self, trace, pid): - super().__init__(trace, pid) + def __init__(self, trace, validation_args): + super().__init__(trace, validation_args['pid']) self.expect["select_entry"]["select_timeout_in_fd0"] = 0 self.expect["select_entry"]["select_timeout_in_fd1023"] = 0 self.expect["select_exit"]["select_timeout_out"] = 0 @@ -477,8 +516,8 @@ class Test2(TraceParser): class Test3(TraceParser): - def __init__(self, trace, pid): - super().__init__(trace, pid) + def __init__(self, trace, validation_args): + super().__init__(trace, validation_args['pid']) self.expect["select_entry"]["select_invalid_fd_in"] = 0 self.expect["select_exit"]["select_invalid_fd_out"] = 0 @@ -506,8 +545,8 @@ class Test3(TraceParser): class Test4(TraceParser): - def __init__(self, trace, pid): - super().__init__(trace, pid) + def __init__(self, trace, validation_args): + super().__init__(trace, validation_args['pid']) self.expect["poll_entry"]["big_poll_in"] = 0 self.expect["poll_exit"]["big_poll_out"] = 0 @@ -546,8 +585,8 @@ class Test4(TraceParser): self.recorded_values["poll_exit"] = locals() class Test5(TraceParser): - def __init__(self, trace, pid): - super().__init__(trace, pid) + def __init__(self, trace, validation_args): + super().__init__(trace, validation_args['pid']) self.expect["poll_entry"]["poll_overflow_in"] = 0 self.expect["poll_exit"]["poll_overflow_out"] = 0 @@ -580,8 +619,8 @@ class Test5(TraceParser): class Test6(TraceParser): - def __init__(self, trace, pid): - super().__init__(trace, pid) + def __init__(self, trace, validation_args): + super().__init__(trace, validation_args['pid']) self.expect["select_entry"]["pselect_invalid_in"] = 0 self.expect["select_exit"]["pselect_invalid_out"] = 0 @@ -613,8 +652,8 @@ class Test6(TraceParser): class Test7(TraceParser): - def __init__(self, trace, pid): - super().__init__(trace, pid) + def __init__(self, trace, validation_args): + super().__init__(trace, validation_args['pid']) self.expect["poll_entry"]["poll_max_in"] = 0 self.expect["poll_exit"]["poll_max_out"] = 0 @@ -644,8 +683,12 @@ class Test7(TraceParser): class Test8(TraceParser): - def __init__(self, trace, pid): - super().__init__(trace, pid) + def __init__(self, trace, validation_args): + super().__init__(trace, validation_args['pid']) + + # Values expected in the trace + self.epoll_fd = validation_args['epollfd'] + self.expect["epoll_wait_entry"]["epoll_wait_invalid_in"] = 0 self.expect["epoll_wait_exit"]["epoll_wait_invalid_out"] = 0 @@ -656,7 +699,7 @@ class Test8(TraceParser): # test that event in valid even though the target buffer pointer is # invalid and the program segfaults - if epfd == 3 and maxevents == 1 and timeout == -1: + if epfd == self.epoll_fd and maxevents == 1 and timeout == -1: self.expect["epoll_wait_entry"]["epoll_wait_invalid_in"] = 1 # Save values of local variables to print in case of test failure @@ -677,8 +720,12 @@ class Test8(TraceParser): class Test9(TraceParser): - def __init__(self, trace, pid): - super().__init__(trace, pid) + def __init__(self, trace, validation_args): + super().__init__(trace, validation_args['pid']) + + # Values expected in the trace + self.epoll_fd = validation_args['epollfd'] + self.expect["epoll_wait_entry"]["epoll_wait_max_in"] = 0 self.expect["epoll_wait_exit"]["epoll_wait_max_out"] = 0 @@ -688,7 +735,7 @@ class Test9(TraceParser): timeout = event["timeout"] # check the proper working of INT_MAX maxevent value - if epfd == 3 and maxevents == 2147483647 and timeout == -1: + if epfd == self.epoll_fd and maxevents == 2147483647 and timeout == -1: self.expect["epoll_wait_entry"]["epoll_wait_max_in"] = 1 # Save values of local variables to print in case of test failure @@ -711,39 +758,46 @@ if __name__ == "__main__": parser = argparse.ArgumentParser(description='Trace parser') parser.add_argument('path', metavar="", help='Trace path') parser.add_argument('-t', '--test', type=int, help='Test to validate') - parser.add_argument('-p', '--pid', type=int, help='PID of the app') + parser.add_argument('-o', '--validation-file', type=str, help='Validation file path') args = parser.parse_args() if not args.test: - print("Need to pass a test to validate (-t)") + print("Need to pass a test to validate (--test/-t)") sys.exit(1) - if not args.pid: - print("Need to pass the PID to check (-p)") + if not args.validation_file: + print("Need to pass the test validation file (--validation-file/-o)") sys.exit(1) traces = bt2.TraceCollectionMessageIterator(args.path) + with open(args.validation_file) as f: + try: + test_validation_args = json.load(f) + except Exception as e: + print('Failed to parse validation file: ' + str(e)) + sys.exit(1) + t = None if args.test == 1: - t = Test1(traces, args.pid) + t = Test1(traces, test_validation_args) elif args.test == 2: - t = Test2(traces, args.pid) + t = Test2(traces, test_validation_args) elif args.test == 3: - t = Test3(traces, args.pid) + t = Test3(traces, test_validation_args) elif args.test == 4: - t = Test4(traces, args.pid) + t = Test4(traces, test_validation_args) elif args.test == 5: - t = Test5(traces, args.pid) + t = Test5(traces, test_validation_args) elif args.test == 6: - t = Test6(traces, args.pid) + t = Test6(traces, test_validation_args) elif args.test == 7: - t = Test7(traces, args.pid) + t = Test7(traces, test_validation_args) elif args.test == 8: - t = Test8(traces, args.pid) + t = Test8(traces, test_validation_args) elif args.test == 9: - t = Test9(traces, args.pid) + t = Test9(traces, test_validation_args) elif args.test == 10: # stress test, nothing reliable to check ret = 0