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 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 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 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 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 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 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 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 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 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 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 320 ATF_TC_BODY(pty_no_queue, tc) 321 { 322 qsize = 0; 323 run(); 324 } 325 326 ATF_TC(pty_queue); 327 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 334 ATF_TC_BODY(pty_queue, tc) 335 { 336 qsize = 4096; 337 run(); 338 } 339 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