1/* $Id: t_pty.c,v 1.1 2011/09/24 15:53:01 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.1 2011/09/24 15:53:01 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