Tests: fix: select_poll_epoll: test assumes epoll fd value
authorJérémie Galarneau <jeremie.galarneau@efficios.com>
Thu, 10 Feb 2022 16:59:31 +0000 (11:59 -0500)
committerJérémie Galarneau <jeremie.galarneau@efficios.com>
Mon, 28 Feb 2022 21:46:10 +0000 (16:46 -0500)
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 <jeremie.galarneau@efficios.com>
Change-Id: Ie373c63f6e6b9267ae2d785c9f0a532a5de37905

tests/regression/kernel/Makefile.am
tests/regression/kernel/select_poll_epoll.c [deleted file]
tests/regression/kernel/select_poll_epoll.cpp [new file with mode: 0644]
tests/regression/kernel/test_select_poll_epoll
tests/regression/kernel/validate_select_poll_epoll.py

index 040b76423a207e45494cd75107d4280b392aab3a..dfbed73f4eb73925ad54fca2100441120554edeb 100644 (file)
@@ -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 (file)
index d0d7e4a..0000000
+++ /dev/null
@@ -1,986 +0,0 @@
-/*
- * Copyright (C) 2016 Julien Desfossez <jdesfossez@efficios.com>
- *
- * SPDX-License-Identifier: GPL-2.0-only
- *
- */
-
-#include <stdio.h>
-#include <poll.h>
-#include <signal.h>
-#include <unistd.h>
-#include <sys/syscall.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <fcntl.h>
-#include <stdlib.h>
-#include <string.h>
-#include <stddef.h>
-#include <sys/select.h>
-#include <sys/epoll.h>
-#include <popt.h>
-#include <sys/time.h>
-#include <sys/resource.h>
-#include <limits.h>
-#include <pthread.h>
-#include <sys/mman.h>
-#include <common/compat/time.h>
-
-#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 (file)
index 0000000..abedc0a
--- /dev/null
@@ -0,0 +1,1021 @@
+/*
+ * Copyright (C) 2016 Julien Desfossez <jdesfossez@efficios.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-only
+ *
+ */
+
+#include <stdio.h>
+#include <poll.h>
+#include <signal.h>
+#include <unistd.h>
+#include <sys/syscall.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stddef.h>
+#include <sys/select.h>
+#include <sys/epoll.h>
+#include <popt.h>
+#include <sys/time.h>
+#include <sys/resource.h>
+#include <limits.h>
+#include <pthread.h>
+#include <sys/mman.h>
+#include <common/compat/time.h>
+#include <common/error.h>
+
+#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;
+}
index 16b0da525c4fae8cf394aa57729dd84bb24641d6..6d5509b25990ea7960e39803cc07b2c54e6b7290 100755 (executable)
@@ -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
index b0cb590dbac2502190cb83f4296b7cac241dc019..97c7764c0a9fbb4975539a20d159e490271e7912 100755 (executable)
@@ -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="<path/to/trace>", 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
This page took 0.054842 seconds and 4 git commands to generate.