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 34 static __dead void usage(const char *); 35 static void parse_args(int, char **); 36 #else 37 #include <atf-c.h> 38 #include "h_macros.h" 39 #endif 40 41 static int pty_open(void); 42 static int tty_open(const char *); 43 static void fd_nonblock(int); 44 static pid_t child_spawn(const char *); 45 static void run(void); 46 47 static size_t buffer_size = 4096; 48 static size_t packets = 2; 49 static uint8_t *dbuf; 50 static int verbose; 51 static int qsize; 52 53 54 static 55 void 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 108 static void 109 condition(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 131 static int 132 pty_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 152 static int 153 tty_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 174 static void 175 fd_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 185 static pid_t 186 child_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 } 248 end: 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 262 static void 263 usage(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 276 static void 277 parse_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 306 int 307 main(int argc, char **argv) 308 { 309 310 parse_args(argc, argv); 311 run(); 312 exit(EXIT_SUCCESS); 313 } 314 315 #else 316 ATF_TC(pty_no_queue); 317 318 ATF_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 324 ATF_TC_BODY(pty_no_queue, tc) 325 { 326 qsize = 0; 327 run(); 328 } 329 330 ATF_TC(pty_queue); 331 332 ATF_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 338 ATF_TC_BODY(pty_queue, tc) 339 { 340 qsize = 4096; 341 run(); 342 } 343 344 ATF_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