t_pty.c revision 1.5
1/* $Id: t_pty.c,v 1.5 2020/06/24 07:02:57 rin Exp $ */
2
3/*
4 * Allocates a pty(4) device, and sends the specified number of packets of the
5 * specified length though it, while a child reader process reads and reports
6 * results.
7 *
8 * Written by Matthew Mondor
9 */
10
11#include <sys/cdefs.h>
12__RCSID("$NetBSD: t_pty.c,v 1.5 2020/06/24 07:02:57 rin Exp $");
13
14#include <errno.h>
15#include <err.h>
16#include <fcntl.h>
17#include <poll.h>
18#include <stdio.h>
19#ifdef __linux__
20#define _XOPEN_SOURCE
21#define __USE_XOPEN
22#endif
23#include <stdint.h>
24#include <stdlib.h>
25#include <string.h>
26#include <termios.h>
27#include <unistd.h>
28
29#include <sys/ioctl.h>
30#include <sys/types.h>
31#include <sys/wait.h>
32
33#ifdef STANDALONE
34#define	atf_tc_fail_errno(fmt, ...)	err(EXIT_FAILURE, fmt, ## __VA_ARGS__)
35#define	atf_tc_fail(fmt, ...)		errx(EXIT_FAILURE, fmt, ## __VA_ARGS__)
36static __dead void	usage(const char *);
37static void		parse_args(int, char **);
38#else
39#include <atf-c.h>
40#include "h_macros.h"
41#endif
42
43static int		pty_open(void);
44static int		tty_open(const char *);
45static void		fd_nonblock(int);
46static pid_t		child_spawn(const char *);
47static void		run(void);
48
49static size_t		buffer_size = 4096;
50static size_t		packets = 2;
51static uint8_t		*dbuf;
52static int		verbose;
53static int		qsize;
54
55
56static
57void run(void)
58{
59	size_t i;
60	int pty;
61	int status;
62	pid_t child;
63	if ((dbuf = calloc(1, buffer_size)) == NULL)
64		atf_tc_fail_errno("malloc(%zu)", buffer_size);
65
66	if (verbose)
67		(void)printf(
68		    "parent: started; opening PTY and spawning child\n");
69	pty = pty_open();
70	child = child_spawn(ptsname(pty));
71	if (verbose)
72		(void)printf("parent: sleeping to make sure child is ready\n");
73	(void)sleep(1);
74
75	for (i = 0; i < buffer_size; i++)
76		dbuf[i] = i & 0xff;
77
78	if (verbose)
79		(void)printf("parent: writing\n");
80
81	for (i = 0; i < packets; i++) {
82		ssize_t	size;
83
84		if (verbose)
85			(void)printf(
86			    "parent: attempting to write %zu bytes to PTY\n",
87			    buffer_size);
88		if ((size = write(pty, dbuf, buffer_size)) == -1) {
89			atf_tc_fail_errno("parent: write()");
90			break;
91		}
92		if (verbose)
93			(void)printf("parent: wrote %zd bytes to PTY\n", size);
94	}
95
96	if (verbose)
97		(void)printf("parent: waiting for child to exit\n");
98	if (waitpid(child, &status, 0) == -1)
99		atf_tc_fail_errno("waitpid");
100	if (!WIFEXITED(status) || WEXITSTATUS(status) != 0)
101		atf_tc_fail("child failed");
102
103	if (verbose)
104		(void)printf("parent: closing PTY\n");
105	(void)close(pty);
106	if (verbose)
107		(void)printf("parent: exiting\n");
108}
109
110static void
111condition(int fd)
112{
113	struct termios	tios;
114
115	if (qsize) {
116		int opt = qsize;
117		if (ioctl(fd, TIOCSQSIZE, &opt) == -1)
118			atf_tc_fail_errno("Couldn't set tty(4) buffer size");
119		if (ioctl(fd, TIOCGQSIZE, &opt) == -1)
120			atf_tc_fail_errno("Couldn't get tty(4) buffer size");
121		if (opt != qsize)
122			atf_tc_fail("Wrong qsize %d != %d\n", qsize, opt);
123	}
124	if (tcgetattr(fd, &tios) == -1)
125		atf_tc_fail_errno("tcgetattr()");
126	cfmakeraw(&tios);
127	cfsetspeed(&tios, B921600);
128	if (tcsetattr(fd, TCSANOW, &tios) == -1)
129		atf_tc_fail_errno("tcsetattr()");
130}
131
132static int
133pty_open(void)
134{
135	int	fd;
136
137	if ((fd = posix_openpt(O_RDWR)) == -1)
138		atf_tc_fail_errno("Couldn't pty(4) device");
139	condition(fd);
140	if (grantpt(fd) == -1)
141		atf_tc_fail_errno(
142		    "Couldn't grant permissions on tty(4) device");
143
144
145	condition(fd);
146
147	if (unlockpt(fd) == -1)
148		atf_tc_fail_errno("unlockpt()");
149
150	return fd;
151}
152
153static int
154tty_open(const char *ttydev)
155{
156	int		fd;
157
158	if ((fd = open(ttydev, O_RDWR, 0)) == -1)
159		atf_tc_fail_errno("Couldn't open tty(4) device");
160
161#ifdef USE_PPP_DISCIPLINE
162	{
163		int	opt = PPPDISC;
164		if (ioctl(fd, TIOCSETD, &opt) == -1)
165			atf_tc_fail_errno(
166			    "Couldn't set tty(4) discipline to PPP");
167	}
168#endif
169
170	condition(fd);
171
172	return fd;
173}
174
175static void
176fd_nonblock(int fd)
177{
178	int	opt;
179
180	if ((opt = fcntl(fd, F_GETFL, NULL)) == -1)
181		atf_tc_fail_errno("fcntl()");
182	if (fcntl(fd, F_SETFL, opt | O_NONBLOCK) == -1)
183		atf_tc_fail_errno("fcntl()");
184}
185
186static pid_t
187child_spawn(const char *ttydev)
188{
189	pid_t		pid;
190	int		tty;
191	struct pollfd	pfd;
192	size_t		total = 0;
193
194	if ((pid = fork()) == -1)
195		atf_tc_fail_errno("fork()");
196	(void)setsid();
197	if (pid != 0)
198		return pid;
199
200	if (verbose)
201		(void)printf("child: started; open \"%s\"\n", ttydev);
202	tty = tty_open(ttydev);
203	fd_nonblock(tty);
204
205	if (verbose)
206		(void)printf("child: TTY open, starting read loop\n");
207	pfd.fd = tty;
208	pfd.events = POLLIN;
209	pfd.revents = 0;
210	for (;;) {
211		int	ret;
212		ssize_t	size;
213
214		if (verbose)
215			(void)printf("child: polling\n");
216		if ((ret = poll(&pfd, 1, 2000)) == -1)
217			err(EXIT_FAILURE, "child: poll()");
218		if (ret == 0)
219			break;
220		if ((pfd.revents & POLLERR) != 0)
221			break;
222		if ((pfd.revents & POLLIN) != 0) {
223			for (;;) {
224				if (verbose)
225					(void)printf(
226					    "child: attempting to read %zu"
227					    " bytes\n", buffer_size);
228				if ((size = read(tty, dbuf, buffer_size))
229				    == -1) {
230					if (errno == EAGAIN)
231						break;
232					err(EXIT_FAILURE, "child: read()");
233				}
234				if (verbose)
235					(void)printf(
236					    "child: read %zd bytes from TTY\n",
237					    size);
238				if (size == 0)
239					goto end;
240				total += size;
241			}
242		}
243	}
244end:
245	if (verbose)
246		(void)printf("child: closing TTY %zu\n", total);
247	(void)close(tty);
248	if (verbose)
249		(void)printf("child: exiting\n");
250	if (total != buffer_size * packets)
251		errx(EXIT_FAILURE,
252		    "Lost data %zu != %zu\n", total, buffer_size * packets);
253
254	exit(EXIT_SUCCESS);
255}
256
257#ifdef STANDALONE
258static void
259usage(const char *msg)
260{
261
262	if (msg != NULL)
263		(void) fprintf(stderr, "\n%s\n\n", msg);
264
265	(void)fprintf(stderr,
266	    "Usage: %s [-v] [-q <qsize>] [-s <packetsize>] [-n <packets>]\n",
267		getprogname());
268
269	exit(EXIT_FAILURE);
270}
271
272static void
273parse_args(int argc, char **argv)
274{
275	int	ch;
276
277	while ((ch = getopt(argc, argv, "n:q:s:v")) != -1) {
278		switch (ch) {
279		case 'n':
280			packets = (size_t)atoi(optarg);
281			break;
282		case 'q':
283			qsize = atoi(optarg);
284			break;
285		case 's':
286			buffer_size = (size_t)atoi(optarg);
287			break;
288		case 'v':
289			verbose++;
290			break;
291		default:
292			usage(NULL);
293			break;
294		}
295	}
296	if (buffer_size < 0 || buffer_size > 65536)
297		usage("-s must be between 0 and 65536");
298	if (packets < 1 || packets > 100)
299		usage("-p must be between 1 and 100");
300}
301
302int
303main(int argc, char **argv)
304{
305
306	parse_args(argc, argv);
307	run();
308	exit(EXIT_SUCCESS);
309}
310
311#else
312ATF_TC(pty_no_queue);
313
314ATF_TC_HEAD(pty_no_queue, tc)
315{
316        atf_tc_set_md_var(tc, "descr", "Checks that writing to pty "
317	    "does not lose data with the default queue size of 1024");
318}
319
320ATF_TC_BODY(pty_no_queue, tc)
321{
322	qsize = 0;
323	run();
324}
325
326ATF_TC(pty_queue);
327
328ATF_TC_HEAD(pty_queue, tc)
329{
330        atf_tc_set_md_var(tc, "descr", "Checks that writing to pty "
331	    "does not lose data with the a queue size of 4096");
332}
333
334ATF_TC_BODY(pty_queue, tc)
335{
336	qsize = 4096;
337	run();
338}
339
340ATF_TP_ADD_TCS(tp)
341{
342        ATF_TP_ADD_TC(tp, pty_no_queue);
343        ATF_TP_ADD_TC(tp, pty_queue);
344
345        return atf_no_error();
346}
347#endif
348