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