487 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			487 lines
		
	
	
		
			11 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 <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.
 | |
|  */
 | |
| 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();
 | |
| }
 | 
