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:
+++ /dev/null
-/*
- * 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;
-}
--- /dev/null
+/*
+ * 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;
+}
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
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()
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
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
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
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
import pprint
import sys
import time
+import json
from collections import defaultdict
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
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)
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
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"]
# 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:
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
# 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
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
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
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
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
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
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
# 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
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
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
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