--- /dev/null
+/* Tests for interrupting VFS calls - by D.C. van Moolenbroek */
+/* This test needs to be run as root; otherwise, openpty() won't work. */
+#include <stdio.h>
+#include <sys/time.h>
+#include <sys/wait.h>
+#include <sys/socket.h>
+#include <sys/utsname.h>
+#include <netinet/in.h>
+#include <signal.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <util.h>
+
+#define ITERATIONS 1
+
+#include "common.h"
+
+/*
+ * This signal handler does nothing. It just needs to be triggered, so that
+ * PM will tell VFS to unpause this process.
+ */
+static void dummy_handler(int sig)
+{
+ /* Nothing. */
+}
+
+/*
+ * Interrupt a select(2) call.
+ */
+static void
+test76a(void)
+{
+ struct sigaction act, oact;
+ struct itimerval it;
+ struct sockaddr_in sin;
+ struct timeval tv;
+ fd_set set;
+ int tfd[2], pfd[2], sfd, maxfd;
+
+ subtest = 1;
+
+ act.sa_handler = dummy_handler;
+ sigfillset(&act.sa_mask);
+ act.sa_flags = 0;
+ if (sigaction(SIGALRM, &act, &oact) < 0) e(1);
+
+ memset(&it, 0, sizeof(it));
+ it.it_value.tv_sec = 0;
+ it.it_value.tv_usec = 10000;
+ if (setitimer(ITIMER_REAL, &it, NULL) < 0) e(2);
+
+ /* First try without any file descriptors. */
+ tv.tv_sec = 1;
+ tv.tv_usec = 0;
+ if (select(0, NULL, NULL, NULL, &tv) >= 0) e(3);
+ if (errno != EINTR) e(4);
+
+ /* Then try with different types of file descriptors, all blocking. */
+ if (openpty(&tfd[0], &tfd[1], NULL, NULL, NULL) < 0) e(5);
+
+ FD_ZERO(&set);
+ FD_SET(tfd[0], &set); /* reading from the PTY master should block */
+ maxfd = tfd[0];
+
+ if (pipe(pfd) < 0) e(6);
+ FD_SET(pfd[0], &set); /* reading from an empty pipe should block */
+ if (maxfd < pfd[0]) maxfd = pfd[0];
+
+ if ((sfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) e(7);
+
+ memset(&sin, 0, sizeof(sin));
+ sin.sin_family = AF_INET;
+ /* Binding to an arbitrary port is fine. */
+ if (bind(sfd, (struct sockaddr *)&sin, sizeof(sin)) < 0) e(8);
+
+ if (listen(sfd, 1) < 0) e(9);
+
+ FD_SET(sfd, &set); /* reading from a listening socket should block */
+ if (maxfd < sfd) maxfd = sfd;
+
+ memset(&it, 0, sizeof(it));
+ it.it_value.tv_sec = 0;
+ it.it_value.tv_usec = 100000;
+ if (setitimer(ITIMER_REAL, &it, NULL) < 0) e(10);
+
+ tv.tv_sec = 1;
+ tv.tv_usec = 0;
+ if (select(maxfd + 1, &set, NULL, NULL, &tv) >= 0) e(11);
+ if (errno != EINTR) e(12);
+
+ if (close(tfd[0]) < 0) e(13);
+ if (close(tfd[1]) < 0) e(14);
+ if (close(pfd[0]) < 0) e(15);
+ if (close(pfd[1]) < 0) e(16);
+ if (close(sfd) < 0) e(17);
+
+ if (sigaction(SIGUSR1, &oact, NULL) < 0) e(18);
+}
+
+/*
+ * Interrupt reads and writes to a pipe. POSIX states that if the operation
+ * was partially successful, the number of bytes written so far should be
+ * returned; otherwise, the we should get the normal EINTR.
+ */
+static void
+test76b(void)
+{
+ struct sigaction act, oact;
+ struct itimerval it;
+ char *buf;
+ int pfd[2];
+
+ subtest = 2;
+
+ if ((buf = malloc(PIPE_BUF * 2)) == NULL) e(1);
+
+ if (pipe(pfd) < 0) e(2);
+
+ act.sa_handler = dummy_handler;
+ sigfillset(&act.sa_mask);
+ act.sa_flags = 0;
+ if (sigaction(SIGALRM, &act, &oact) < 0) e(3);
+
+ memset(&it, 0, sizeof(it));
+ it.it_value.tv_sec = 0;
+ it.it_value.tv_usec = 10000;
+ if (setitimer(ITIMER_REAL, &it, NULL) < 0) e(4);
+
+ /*
+ * This write is too large for the pipe, so it will block until the
+ * signal arrives. When being interrupted, it should return the pipe
+ * size, as that is the part that has been filled successfully so far.
+ */
+ if (write(pfd[1], buf, PIPE_BUF * 2) != PIPE_BUF) e(5);
+
+ /*
+ * Since the write partially succeeded, we should be able to read all
+ * we wrote so far, without blocking.
+ */
+ if (read(pfd[0], buf, PIPE_BUF) != PIPE_BUF) e(6);
+
+ /* We should now be able to fill the pipe up to its full size again. */
+ if (write(pfd[1], buf, PIPE_BUF) != PIPE_BUF) e(7);
+
+ memset(&it, 0, sizeof(it));
+ it.it_value.tv_sec = 0;
+ it.it_value.tv_usec = 10000;
+ if (setitimer(ITIMER_REAL, &it, NULL) < 0) e(8);
+
+ /* Now interrupt a write attempt on a full pipe. */
+ if (write(pfd[1], buf, 1) >= 0) e(9);
+ if (errno != EINTR) e(10);
+
+ /* Empty the pipe again. */
+ if (read(pfd[0], buf, PIPE_BUF) != PIPE_BUF) e(11);
+
+ memset(&it, 0, sizeof(it));
+ it.it_value.tv_sec = 0;
+ it.it_value.tv_usec = 10000;
+ if (setitimer(ITIMER_REAL, &it, NULL) < 0) e(12);
+
+ /* Now interrupt a read on an empty pipe. */
+ if (read(pfd[0], buf, PIPE_BUF) >= 0) e(13);
+ if (errno != EINTR) e(14);
+
+ if (close(pfd[0]) < 0) e(15);
+ if (close(pfd[1]) < 0) e(16);
+
+ if (sigaction(SIGUSR1, &oact, NULL) < 0) e(17);
+
+ free(buf);
+}
+
+/*
+ * Interrupt an ioctl(2) call. We use an alarm to interrupt an accept(3) call
+ * on a TCP socket - the accept procedure is (currently) implemented using
+ * ioctl(2) calls.
+ */
+static void
+test76c(void)
+{
+ struct sigaction act, oact;
+ struct itimerval it;
+ struct sockaddr_in sin;
+ socklen_t len;
+ int sfd;
+
+ subtest = 3;
+
+ if ((sfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) e(1);
+
+ memset(&sin, 0, sizeof(sin));
+ sin.sin_family = AF_INET;
+ /* Binding to an arbitrary port is fine. */
+ if (bind(sfd, (struct sockaddr *)&sin, sizeof(sin)) < 0) e(2);
+
+ if (listen(sfd, 1) < 0) e(3);
+
+ act.sa_handler = dummy_handler;
+ sigfillset(&act.sa_mask);
+ act.sa_flags = 0;
+ if (sigaction(SIGALRM, &act, &oact) < 0) e(4);
+
+ memset(&it, 0, sizeof(it));
+ it.it_value.tv_sec = 0;
+ it.it_value.tv_usec = 10000;
+ if (setitimer(ITIMER_REAL, &it, NULL) < 0) e(5);
+
+ /* This will block until the timer fires. */
+ len = sizeof(sin);
+ if (accept(sfd, (struct sockaddr *)&sin, &len) >= 0) e(6);
+ if (errno != EINTR) e(7);
+
+ if (close(sfd) < 0) e(8);
+
+ if (sigaction(SIGUSR1, &oact, NULL) < 0) e(9);
+}
+
+/*
+ * Try to trigger semi-concurrent processing of normal system calls and
+ * postponed PM requests for a single process within VFS.
+ */
+static void
+test76d(void)
+{
+ struct utsname name;
+ struct sigaction act, oact;
+ struct itimerval it;
+ int r, fd, pfd[2], count, status;
+ time_t stime, etime, runtime = 30 /*seconds*/;
+ char buf[3], *pbuf;
+
+ subtest = 4;
+
+ /* This test would kill wimpy platforms such as ARM. */
+ if (uname(&name) < 0) e(1);
+ if (!strcmp(name.machine, "arm")) return;
+
+ act.sa_handler = dummy_handler;
+ sigfillset(&act.sa_mask);
+ act.sa_flags = 0;
+ if (sigaction(SIGALRM, &act, &oact) < 0) e(2);
+
+ if (pipe(pfd) < 0) e(3);
+
+ /* Pre-fill the pipe. */
+ if ((pbuf = malloc(PIPE_BUF - 1)) == NULL) e(4);
+
+ if (write(pfd[1], pbuf, PIPE_BUF - 1) != PIPE_BUF - 1) e(5);
+
+ free(pbuf);
+
+ switch (fork()) {
+ case 0:
+ if (close(pfd[1]) < 0) e(6);
+
+ /* Read from the pipe, but more slowly than the writer. */
+ while ((r = read(pfd[0], buf, 2)) != 0)
+ if (r < 0) e(7);
+
+ exit(0);
+ case -1:
+ e(8);
+ default:
+ break;
+ }
+
+ switch (fork()) {
+ case 0:
+ if (close(pfd[0]) < 0) e(9);
+
+ time(&stime);
+
+ /* Start an alarm mayhem. */
+ it.it_value.tv_sec = 0;
+ it.it_value.tv_usec = 1;
+ it.it_interval.tv_sec = 0;
+ it.it_interval.tv_usec = 1;
+ if (setitimer(ITIMER_REAL, &it, NULL) < 0) e(10);
+
+ /*
+ * Then start writing to the pipe, in such a way that the
+ * write operation will be suspended in every so many cases.
+ */
+ do {
+ if (write(pfd[1], buf, 3) < 0 && errno != EINTR)
+ e(11);
+
+ time(&etime);
+ } while ((int)(etime - stime) < runtime);
+
+ exit(0);
+ case -1:
+ e(12);
+ default:
+ break;
+ }
+
+ if (close(pfd[0]) < 0) e(13);
+ if (close(pfd[1]) < 0) e(14);
+
+ /*
+ * First give the two processes a while to run regularly. Then start
+ * creating additional noise to keep the VFS worker threads busy.
+ */
+ runtime /= 2;
+
+ sleep(runtime);
+
+ /*
+ * As of writing, VFS has less than 20 worker threads. Create more
+ * processes than that.
+ */
+ for (count = 2; count < 20; count++) {
+ switch (fork()) {
+ case 0:
+ time(&stime);
+
+ do {
+ /*
+ * Opening a character device blocks the
+ * calling thread, hopefully causing work to be
+ * queued. Sadly, in practice, the high
+ * priorities of system processes prevent this
+ * case from occurring frequently. It works
+ * better with a driver that has a priority
+ * below that of of user processes.
+ */
+ if ((fd = open("/dev/null", O_WRONLY)) < 0)
+ e(15);
+
+ close(fd);
+
+ time(&etime);
+ } while ((int)(etime - stime) < runtime);
+
+ exit(0);
+ case -1:
+ e(16);
+ default:
+ break;
+ }
+ }
+
+ /* Wait for all children to shut down. */
+ while (count-- > 0) {
+ if (wait(&status) <= 0) e(17);
+ if (!WIFEXITED(status)) e(18);
+ if (WEXITSTATUS(status) != 0) e(19);
+ }
+
+ if (sigaction(SIGUSR1, &oact, NULL) < 0) e(20);
+}
+
+/*
+ * Try to get a nonblocking select(2) call to be interrupted by a signal.
+ * In the future, VFS should prevent this from happening at all; for now, we
+ * just want to make sure it does not result in disaster when it does happen.
+ */
+static void
+test76e(void)
+{
+ struct utsname name;
+ struct sigaction act, oact;
+ struct itimerval it;
+ struct timeval tv;
+ fd_set set;
+ int tfd[2], left;
+
+ subtest = 5;
+
+ /* This test would kill wimpy platforms such as ARM. */
+ if (uname(&name) < 0) e(1);
+ if (!strcmp(name.machine, "arm")) return;
+
+ if (openpty(&tfd[0], &tfd[1], NULL, NULL, NULL) < 0) e(2);
+
+ act.sa_handler = dummy_handler;
+ sigfillset(&act.sa_mask);
+ act.sa_flags = 0;
+ if (sigaction(SIGALRM, &act, &oact) < 0) e(3);
+
+ /*
+ * Start an alarm mayhem. We have to try to get a signal in between
+ * VFS sending a select request to TTY, and TTY replying to VFS with
+ * initial results.
+ */
+ it.it_value.tv_sec = 0;
+ it.it_value.tv_usec = 1;
+ it.it_interval.tv_sec = 0;
+ it.it_interval.tv_usec = 1;
+ if (setitimer(ITIMER_REAL, &it, NULL) < 0) e(4);
+
+ /*
+ * Now issue nonblocking selects until we get interrupted, or until
+ * we have gone through a hardcoded maximum of attempts.
+ */
+ left = 100000;
+ do {
+ if (--left == 0) break;
+
+ FD_ZERO(&set);
+ FD_SET(tfd[0], &set); /* reading from master should block */
+
+ tv.tv_sec = 0;
+ tv.tv_usec = 0;
+ } while (select(2, &set, NULL, NULL, &tv) >= 0);
+
+ if (left > 0 && errno != EINTR) e(5);
+
+ it.it_value.tv_sec = 0;
+ it.it_value.tv_usec = 0;
+ if (setitimer(ITIMER_REAL, &it, NULL) < 0) e(6);
+
+ /* The call failed, so the set must be unmodified. */
+ if (left > 0 && !FD_SET(tfd[0], &set)) e(7);
+
+ if (close(tfd[0]) < 0) e(8);
+ if (close(tfd[1]) < 0) e(9);
+
+ if (sigaction(SIGUSR1, &oact, NULL) < 0) e(10);
+}
+
+int
+main(int argc, char **argv)
+{
+ int i, m;
+
+ start(76);
+
+ if (argc == 2)
+ m = atoi(argv[1]);
+ else
+ m = 0xFF;
+
+ for (i = 0; i < ITERATIONS; i++) {
+ if (m & 0x01) test76a();
+ if (m & 0x02) test76b();
+ if (m & 0x04) test76c();
+ if (m & 0x08) test76d();
+ if (m & 0x10) test76e();
+ }
+
+ quit();
+}