diff --git a/distrib/sets/lists/minix/mi b/distrib/sets/lists/minix/mi index b123b914f..7cc7ada04 100644 --- a/distrib/sets/lists/minix/mi +++ b/distrib/sets/lists/minix/mi @@ -5656,6 +5656,7 @@ ./usr/tests/minix-posix/test73 minix-sys ./usr/tests/minix-posix/test74 minix-sys ./usr/tests/minix-posix/test75 minix-sys +./usr/tests/minix-posix/test76 minix-sys ./usr/tests/minix-posix/test8 minix-sys ./usr/tests/minix-posix/test9 minix-sys ./usr/tests/minix-posix/testinterp minix-sys diff --git a/test/Makefile b/test/Makefile index 71e1501c6..4d584901d 100644 --- a/test/Makefile +++ b/test/Makefile @@ -31,6 +31,7 @@ COPTS.test68.c= -O0 # Some have special libraries LDADD.test59= -lmthread +LDADD.test76= -lutil # Some have an extra file OBJS.test57= test57loop.o @@ -53,7 +54,7 @@ MINIX_TESTS= \ 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 \ 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 \ 41 42 43 44 45 46 48 49 50 52 53 54 55 56 58 59 60 \ -61 64 65 66 67 68 69 70 71 72 73 74 75 +61 64 65 66 67 68 69 70 71 72 73 74 75 76 .if ${MACHINE_ARCH} == "i386" MINIX_TESTS+= \ diff --git a/test/run b/test/run index f922c3df4..8ddf28b72 100755 --- a/test/run +++ b/test/run @@ -21,14 +21,14 @@ badones= # list of tests that failed # Programs that require setuid setuids="test11 test33 test43 test44 test46 test56 test60 test61 test65 \ - test69" # test73" + test69 test76" # test73" # Scripts that require to be run as root rootscripts="testisofs" alltests="1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 \ 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 \ 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 \ - 61 62 63 64 65 66 67 68 69 70 71 72 75 \ + 61 62 63 64 65 66 67 68 69 70 71 72 75 76 \ sh1 sh2 interp mfs isofs" tests_no=`expr 0` diff --git a/test/test76.c b/test/test76.c new file mode 100644 index 000000000..062ccf4ce --- /dev/null +++ b/test/test76.c @@ -0,0 +1,446 @@ +/* Tests for interrupting VFS calls - by D.C. van Moolenbroek */ +/* This test needs to be run as root; otherwise, openpty() won't work. */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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(); +}