xref: /netbsd-src/tests/kernel/t_pty.c (revision f01e01eb6a56a2bd3224e5c5dcadc7c74b8d2cbf)
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__)
36 static __dead void	usage(const char *);
37 static void		parse_args(int, char **);
38 #else
39 #include <atf-c.h>
40 #include "h_macros.h"
41 #endif
42 
43 static int		pty_open(void);
44 static int		tty_open(const char *);
45 static void		fd_nonblock(int);
46 static pid_t		child_spawn(const char *);
47 static void		run(void);
48 
49 static size_t		buffer_size = 4096;
50 static size_t		packets = 2;
51 static uint8_t		*dbuf;
52 static int		verbose;
53 static int		qsize;
54 
55 
56 static
run(void)57 void 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 
110 static void
condition(int fd)111 condition(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 
132 static int
pty_open(void)133 pty_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 
153 static int
tty_open(const char * ttydev)154 tty_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 
175 static void
fd_nonblock(int fd)176 fd_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 
186 static pid_t
child_spawn(const char * ttydev)187 child_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 	}
244 end:
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
258 static void
usage(const char * msg)259 usage(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 
272 static void
parse_args(int argc,char ** argv)273 parse_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 
302 int
main(int argc,char ** argv)303 main(int argc, char **argv)
304 {
305 
306 	parse_args(argc, argv);
307 	run();
308 	exit(EXIT_SUCCESS);
309 }
310 
311 #else
312 ATF_TC(pty_no_queue);
313 
ATF_TC_HEAD(pty_no_queue,tc)314 ATF_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 
ATF_TC_BODY(pty_no_queue,tc)320 ATF_TC_BODY(pty_no_queue, tc)
321 {
322 	qsize = 0;
323 	run();
324 }
325 
326 ATF_TC(pty_queue);
327 
ATF_TC_HEAD(pty_queue,tc)328 ATF_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 
ATF_TC_BODY(pty_queue,tc)334 ATF_TC_BODY(pty_queue, tc)
335 {
336 	qsize = 4096;
337 	run();
338 }
339 
ATF_TP_ADD_TCS(tp)340 ATF_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