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