minix/minix/tests/test77.c
David van Moolenbroek 27d0ecdb62 VFS: unbreak select on /dev/tty
The remapping from /dev/tty to the real controlling terminal in the
device code was confusing the select code.  The latter is now aware
of this case and should handle it properly, at the cost of one extra
field in the filp structure.

There is a nasty, hopefully sufficiently rare case of /dev/tty being
kept open while controlling terminals are changing, that we are still
not handling.  Doing so would require more than just a few changes,
but the code should at least detect and cleanly fail on this case.

Test77 now has a basic test set for selecting on /dev/tty.

Change-Id: Iaedea449cdb728d0e66a9de8faacdfd9638dfe92
2014-08-28 16:30:48 +00:00

659 lines
15 KiB
C

/* Tests for opening/closing pseudo terminals - by D.C. van Moolenbroek */
/* This test needs to be run as root; otherwise, openpty() won't work. */
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <termios.h>
#include <sys/wait.h>
#include <sys/syslimits.h>
#include <paths.h>
#include <fcntl.h>
#include <util.h>
#define ITERATIONS 10
#include "common.h"
static int sighups; /* number of SIGHUP signals received */
/*
* Signal handler for SIGHUP and SIGUSR1.
*/
static void
signal_handler(int sig)
{
if (sig == SIGHUP)
sighups++;
}
/*
* Set the slave side of the pseudo terminal to raw mode. This simplifies
* testing communication.
*/
static void
make_raw(int slavefd)
{
struct termios tios;
if (tcgetattr(slavefd, &tios) < 0) e(100);
cfmakeraw(&tios);
if (tcsetattr(slavefd, TCSANOW, &tios) < 0) e(101);
}
/*
* See if the given pseudo terminal can successfully perform basic
* communication between master and slave.
*/
static void
test_comm(int masterfd, int slavefd)
{
char c;
make_raw(slavefd);
c = 'A';
if (write(masterfd, &c, sizeof(c)) != sizeof(c)) e(200);
if (read(slavefd, &c, sizeof(c)) != sizeof(c)) e(201);
if (c != 'A') e(202);
c = 'B';
if (write(slavefd, &c, sizeof(c)) != sizeof(c)) e(203);
if (read(masterfd, &c, sizeof(c)) != sizeof(c)) e(204);
if (c != 'B') e(205);
c = 'C';
if (write(masterfd, &c, sizeof(c)) != sizeof(c)) e(206);
if (read(slavefd, &c, sizeof(c)) != sizeof(c)) e(207);
if (c != 'C') e(208);
c = 'D';
if (write(slavefd, &c, sizeof(c)) != sizeof(c)) e(209);
if (read(masterfd, &c, sizeof(c)) != sizeof(c)) e(210);
if (c != 'D') e(211);
}
/*
* Get device node names for the master and slave end of a free pseudo
* terminal. We don't want to replicate the entire openpty(3) logic here, so
* start by letting openpty(3) do the work for us. We make the assumption that
* nobody snatches the pair while we are running.
*/
static void
get_names(char pname[PATH_MAX], char tname[PATH_MAX])
{
int len, masterfd, slavefd;
if (openpty(&masterfd, &slavefd, tname, NULL, NULL) < 0) e(300);
/*
* openpty(3) gives us only the slave name, but we also need the master
* name.
*/
strlcpy(pname, tname, PATH_MAX);
len = strlen(_PATH_DEV);
if (strncmp(pname, _PATH_DEV, len)) e(301);
/* If this fails, this test needs to be updated. */
if (pname[len] != 't') e(302);
pname[len] = 'p';
test_comm(masterfd, slavefd);
if (close(masterfd) < 0) e(303);
if (close(slavefd) < 0) e(304);
}
/*
* Test various orders of opening and closing the master and slave sides of a
* pseudo terminal, as well as opening/closing one side without ever opening
* the other.
*/
static void
test77a(void)
{
struct sigaction act, oact;
char pname[PATH_MAX], tname[PATH_MAX];
int masterfd, slavefd;
subtest = 1;
/* We do not want to get SIGHUP signals in this test. */
memset(&act, 0, sizeof(act));
act.sa_handler = SIG_IGN;
if (sigaction(SIGHUP, &act, &oact) < 0) e(1);
/* Get master and slave device names for a free pseudo terminal. */
get_names(pname, tname);
/* Try opening and then closing the master. */
if ((masterfd = open(pname, O_RDWR | O_NOCTTY)) < 0) e(2);
if (close(masterfd) < 0) e(3);
/* Now see if we can reopen the master as well as the slave. */
if ((masterfd = open(pname, O_RDWR | O_NOCTTY)) < 0) e(4);
if ((slavefd = open(tname, O_RDWR | O_NOCTTY)) < 0) e(5);
test_comm(masterfd, slavefd);
/* In the meantime, test different closing orders. This is order A. */
if (close(slavefd) < 0) e(6);
if (close(masterfd) < 0) e(7);
/* Now try opening the pair again. */
if ((masterfd = open(pname, O_RDWR | O_NOCTTY)) < 0) e(8);
if ((slavefd = open(tname, O_RDWR | O_NOCTTY)) < 0) e(9);
test_comm(masterfd, slavefd);
if (close(slavefd) < 0) e(10);
/*
* Try reopening the slave after closing it. It is not very important
* that this works, but the TTY driver should currently support it.
*/
if ((slavefd = open(tname, O_RDWR | O_NOCTTY)) < 0) e(11);
test_comm(masterfd, slavefd);
/* This is closing order B. This may or may not cause a SIGHUP. */
if (close(masterfd) < 0) e(12);
if (close(slavefd) < 0) e(13);
/* Try the normal open procedure. */
if ((masterfd = open(pname, O_RDWR | O_NOCTTY)) < 0) e(14);
if ((slavefd = open(tname, O_RDWR | O_NOCTTY)) < 0) e(15);
test_comm(masterfd, slavefd);
if (close(slavefd) < 0) e(16);
if (close(masterfd) < 0) e(17);
/* Try reopening and closing the slave, without opening the master. */
if ((slavefd = open(tname, O_RDWR | O_NOCTTY)) < 0) e(18);
if (close(slavefd) < 0) e(19);
/* Again, try the normal open procedure. */
if ((masterfd = open(pname, O_RDWR | O_NOCTTY)) < 0) e(20);
if ((slavefd = open(tname, O_RDWR | O_NOCTTY)) < 0) e(21);
test_comm(masterfd, slavefd);
if (close(slavefd) < 0) e(22);
if (close(masterfd) < 0) e(23);
/* Finally, try opening the slave first. */
if ((slavefd = open(tname, O_RDWR | O_NOCTTY)) < 0) e(24);
if ((masterfd = open(pname, O_RDWR | O_NOCTTY)) < 0) e(25);
test_comm(masterfd, slavefd);
if (close(slavefd) < 0) e(26);
if (close(masterfd) < 0) e(27);
if (sigaction(SIGHUP, &oact, NULL) < 0) e(28);
}
/*
* Test opening a single side multiple times.
*/
static void
test77b(void)
{
char pname[PATH_MAX], tname[PATH_MAX];
int masterfd, slavefd, extrafd;
subtest = 2;
/* Get master and slave device names for a free pseudo terminal. */
get_names(pname, tname);
/* It must not be possible to open the master multiple times. */
if ((masterfd = open(pname, O_RDWR | O_NOCTTY)) < 0) e(1);
if ((slavefd = open(tname, O_RDWR | O_NOCTTY)) < 0) e(2);
test_comm(masterfd, slavefd);
if ((extrafd = open(pname, O_RDWR | O_NOCTTY)) >= 0) e(3);
if (errno != EIO) e(4);
test_comm(masterfd, slavefd);
if (close(slavefd) < 0) e(5);
if (close(masterfd) < 0) e(6);
/* The slave can be opened multiple times, though. */
if ((masterfd = open(pname, O_RDWR | O_NOCTTY)) < 0) e(7);
if ((slavefd = open(tname, O_RDWR | O_NOCTTY)) < 0) e(8);
test_comm(masterfd, slavefd);
if ((extrafd = open(tname, O_RDWR | O_NOCTTY)) < 0) e(9);
test_comm(masterfd, extrafd);
test_comm(masterfd, slavefd);
if (close(slavefd) < 0) e(10);
if (close(extrafd) < 0) e(11);
if (close(masterfd) < 0) e(12);
}
/*
* Test communication on half-open pseudo terminals.
*/
static void
test77c(void)
{
struct sigaction act, oact;
char pname[PATH_MAX], tname[PATH_MAX];
int masterfd, slavefd;
char c;
subtest = 3;
/* We do not want to get SIGHUP signals in this test. */
memset(&act, 0, sizeof(act));
act.sa_handler = SIG_IGN;
if (sigaction(SIGHUP, &act, &oact) < 0) e(1);
/* Get master and slave device names for a free pseudo terminal. */
get_names(pname, tname);
if ((masterfd = open(pname, O_RDWR | O_NOCTTY)) < 0) e(2);
/* Writes to the master should be buffered until there is a slave. */
c = 'E';
if (write(masterfd, &c, sizeof(c)) != sizeof(c)) e(3);
if ((slavefd = open(tname, O_RDWR | O_NOCTTY)) < 0) e(4);
make_raw(slavefd);
if (read(slavefd, &c, sizeof(c)) != sizeof(c)) e(5);
if (c != 'E') e(6);
/* Discard the echo on the master. */
if (tcflush(slavefd, TCOFLUSH) != 0) e(7);
test_comm(masterfd, slavefd);
if (close(slavefd) < 0) e(8);
/* Writes to the master after the slave has been closed should fail. */
if (write(masterfd, &c, sizeof(c)) >= 0) e(9);
if (errno != EIO) e(10);
if (close(masterfd) < 0) e(11);
/* Writes to the slave should be buffered until there is a master. */
if ((slavefd = open(tname, O_RDWR | O_NOCTTY)) < 0) e(12);
make_raw(slavefd);
c = 'F';
if (write(slavefd, &c, sizeof(c)) != sizeof(c)) e(13);
if ((masterfd = open(pname, O_RDWR | O_NOCTTY)) < 0) e(14);
if (read(masterfd, &c, sizeof(c)) != sizeof(c)) e(15);
if (c != 'F') e(16);
test_comm(masterfd, slavefd);
if (close(masterfd) < 0) e(17);
if (write(slavefd, &c, sizeof(c)) >= 0) e(18);
if (errno != EIO) e(19);
/* Reads from the slave should return EOF if the master is gone. */
if (read(slavefd, &c, sizeof(c)) != 0) e(20);
if (close(slavefd) < 0) e(21);
if (sigaction(SIGHUP, &oact, NULL) < 0) e(22);
}
/*
* Wait for a child process to terminate. Return 0 if the child exited without
* errors, -1 otherwise.
*/
static int
waitchild(void)
{
int status;
if (wait(&status) <= 0) return -1;
if (!WIFEXITED(status)) return -1;
if (WEXITSTATUS(status) != 0) return -1;
return 0;
}
/*
* Test opening the slave side with and without the O_NOCTTY flag.
*/
static void
test77d(void)
{
char pname[PATH_MAX], tname[PATH_MAX];
int masterfd, slavefd;
subtest = 4;
/* Get master and slave device names for a free pseudo terminal. */
get_names(pname, tname);
/* Make ourselves process group leader if we aren't already. */
(void) setsid();
if ((masterfd = open(pname, O_RDWR | O_NOCTTY)) < 0) e(1);
/*
* Opening the slave with O_NOCTTY should not change its controlling
* terminal.
*/
switch (fork()) {
case 0:
if (setsid() < 0) e(2);
if ((slavefd = open(tname, O_RDWR | O_NOCTTY)) < 0) e(3);
if (open("/dev/tty", O_RDWR) >= 0) e(4);
if (errno != ENXIO) e(5);
exit(errct);
case -1:
e(6);
default:
break;
}
if (waitchild() < 0) e(7);
if (close(masterfd) < 0) e(8);
if ((masterfd = open(pname, O_RDWR | O_NOCTTY)) < 0) e(9);
/*
* Opening the slave without O_NOCTTY should change its controlling
* terminal, though.
*/
switch (fork()) {
case 0:
if (setsid() < 0) e(10);
if ((slavefd = open(tname, O_RDWR)) < 0) e(11);
if (open("/dev/tty", O_RDWR) < 0) e(12);
exit(errct);
case -1:
e(13);
default:
break;
}
if (waitchild() < 0) e(14);
if (close(masterfd) < 0) e(15);
}
/*
* Test receiving of SIGHUP on master hang-up. All of the tests so far have
* ignored SIGHUP, and probably would not have received one anyway, since the
* process was not its own session leader. Time to test this aspect.
*/
static void
test77e(void)
{
struct sigaction act, hup_oact, usr_oact;
sigset_t set, oset;
char pname[PATH_MAX], tname[PATH_MAX];
int masterfd, slavefd;
subtest = 5;
/* Get master and slave device names for a free pseudo terminal. */
get_names(pname, tname);
memset(&act, 0, sizeof(act));
act.sa_handler = signal_handler;
if (sigaction(SIGHUP, &act, &hup_oact) < 0) e(1);
memset(&act, 0, sizeof(act));
act.sa_handler = signal_handler;
if (sigaction(SIGUSR1, &act, &usr_oact) < 0) e(2);
sigemptyset(&set);
sigaddset(&set, SIGHUP);
sigaddset(&set, SIGUSR1);
if (sigprocmask(SIG_BLOCK, &set, &oset) < 0) e(3);
sighups = 0;
/* Make ourselves process group leader if we aren't already. */
(void) setsid();
if ((masterfd = open(pname, O_RDWR | O_NOCTTY)) < 0) e(4);
switch (fork()) {
case 0:
if (close(masterfd) < 0) e(5);
/* Become session leader. */
if (setsid() < 0) e(6);
if ((slavefd = open(tname, O_RDWR)) < 0) e(7);
/* Tell the parent we are ready. */
kill(getppid(), SIGUSR1);
/* We should now get a SIGHUP. */
set = oset;
if (sigsuspend(&set) >= 0) e(8);
if (sighups != 1) e(9);
exit(errct);
case -1:
e(10);
default:
break;
}
/* Wait for SIGUSR1 from the child. */
set = oset;
if (sigsuspend(&set) >= 0) e(11);
/* Closing the master should now raise a SIGHUP signal in the child. */
if (close(masterfd) < 0) e(12);
if (waitchild() < 0) e(13);
if (sigprocmask(SIG_SETMASK, &oset, NULL) < 0) e(14);
if (sigaction(SIGHUP, &hup_oact, NULL) < 0) e(15);
if (sigaction(SIGUSR1, &usr_oact, NULL) < 0) e(16);
}
/*
* Test basic select functionality on /dev/tty. While this test should not be
* part of this test set, we already have all the infrastructure we need here.
*/
static void
test77f(void)
{
struct sigaction act, oact;
char c, pname[PATH_MAX], tname[PATH_MAX];
struct timeval tv;
fd_set fd_set;
int fd, maxfd, masterfd, slavefd;
subtest = 6;
/* We do not want to get SIGHUP signals in this test. */
memset(&act, 0, sizeof(act));
act.sa_handler = SIG_IGN;
if (sigaction(SIGHUP, &act, &oact) < 0) e(1);
/* Get master and slave device names for a free pseudo terminal. */
get_names(pname, tname);
if ((masterfd = open(pname, O_RDWR | O_NOCTTY)) < 0) e(2);
switch (fork()) {
case 0:
if (setsid() < 0) e(3);
close(masterfd);
if ((slavefd = open(tname, O_RDWR)) < 0) e(4);
if ((fd = open("/dev/tty", O_RDWR)) < 0) e(5);
make_raw(fd);
/* Without slave input, /dev/tty is not ready for reading. */
FD_ZERO(&fd_set);
FD_SET(fd, &fd_set);
tv.tv_sec = 0;
tv.tv_usec = 0;
if (select(fd + 1, &fd_set, NULL, NULL, &tv) != 0) e(6);
if (FD_ISSET(fd, &fd_set)) e(7);
FD_SET(fd, &fd_set);
tv.tv_sec = 0;
tv.tv_usec = 10000;
if (select(fd + 1, &fd_set, NULL, NULL, &tv) != 0) e(8);
if (FD_ISSET(fd, &fd_set)) e(9);
/* It will be ready for writing, though. */
FD_SET(fd, &fd_set);
if (select(fd + 1, NULL, &fd_set, NULL, NULL) != 1) e(10);
if (!FD_ISSET(fd, &fd_set)) e(11);
/* Test mixing file descriptors to the same terminal. */
FD_ZERO(&fd_set);
FD_SET(fd, &fd_set);
FD_SET(slavefd, &fd_set);
tv.tv_sec = 0;
tv.tv_usec = 10000;
maxfd = fd > slavefd ? fd : slavefd;
if (select(maxfd + 1, &fd_set, NULL, NULL, &tv) != 0) e(12);
if (FD_ISSET(fd, &fd_set)) e(13);
if (FD_ISSET(slavefd, &fd_set)) e(14);
/* The delayed echo on the master must wake up our select. */
c = 'A';
if (write(slavefd, &c, sizeof(c)) != sizeof(c)) e(15);
FD_ZERO(&fd_set);
FD_SET(fd, &fd_set);
if (select(fd + 1, &fd_set, NULL, NULL, NULL) != 1) e(16);
if (!FD_ISSET(fd, &fd_set)) e(17);
/* Select must now still flag readiness for reading. */
tv.tv_sec = 0;
tv.tv_usec = 0;
if (select(fd + 1, &fd_set, NULL, NULL, &tv) != 1) e(18);
if (!FD_ISSET(fd, &fd_set)) e(19);
/* That is, until we read the byte. */
if (read(slavefd, &c, sizeof(c)) != sizeof(c)) e(20);
if (c != 'B') e(21);
if (select(fd + 1, &fd_set, NULL, NULL, &tv) != 0) e(22);
if (FD_ISSET(fd, &fd_set)) e(23);
/* Ask the parent to close the master. */
c = 'C';
if (write(slavefd, &c, sizeof(c)) != sizeof(c)) e(24);
FD_SET(fd, &fd_set);
/* The closure must cause an EOF condition on the slave. */
if (select(fd + 1, &fd_set, NULL, NULL, NULL) != 1) e(25);
if (!FD_ISSET(fd, &fd_set)) e(26);
if (select(fd + 1, &fd_set, NULL, NULL, NULL) != 1) e(27);
if (!FD_ISSET(fd, &fd_set)) e(28);
if (read(slavefd, &c, sizeof(c)) != 0) e(29);
exit(errct);
case -1:
e(30);
default:
/* Wait for the child to write something to the slave. */
FD_ZERO(&fd_set);
FD_SET(masterfd, &fd_set);
if (select(masterfd + 1, &fd_set, NULL, NULL, NULL) != 1)
e(31);
if (!FD_ISSET(masterfd, &fd_set)) e(32);
if (read(masterfd, &c, sizeof(c)) != sizeof(c)) e(33);
if (c != 'A') e(34);
/* Write a reply once the child is blocked in its select. */
tv.tv_sec = 1;
tv.tv_usec = 0;
if (select(masterfd + 1, &fd_set, NULL, NULL, &tv) != 0)
e(35);
c = 'B';
if (write(masterfd, &c, sizeof(c)) != sizeof(c)) e(36);
/* Wait for the child to request closing the master. */
if (read(masterfd, &c, sizeof(c)) != sizeof(c)) e(37);
if (c != 'C') e(38);
/* Close the master once the child is blocked in its select. */
sleep(1);
close(masterfd);
break;
}
if (waitchild() < 0) e(39);
if (sigaction(SIGHUP, &oact, NULL) < 0) e(28);
}
int
main(int argc, char **argv)
{
int i, m;
start(77);
if (argc == 2)
m = atoi(argv[1]);
else
m = 0xFF;
for (i = 0; i < ITERATIONS; i++) {
if (m & 0x01) test77a();
if (m & 0x02) test77b();
if (m & 0x04) test77c();
if (m & 0x08) test77d();
if (m & 0x10) test77e();
if (m & 0x20) test77f();
}
quit();
}