/* 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 #include #include #include #include #include #include #include #include #include #define ITERATIONS 10 #include "common.h" static int sighups; /* number of SIGHUP signals received */ /* * Signal handler for SIGHUP and SIGUSR1. */ 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); if (close(slavefd) < 0) e(20); if (sigaction(SIGHUP, &oact, NULL) < 0) e(21); } /* * 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(0); case -1: e(6); default: break; } if (wait(NULL) <= 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(0); case -1: e(13); default: break; } if (wait(NULL) <= 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(0); 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 (wait(NULL) <= 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); } 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(); } quit(); }