1 1.5 rin /* $Id: t_pty.c,v 1.5 2020/06/24 07:02:57 rin Exp $ */ 2 1.1 christos 3 1.1 christos /* 4 1.1 christos * Allocates a pty(4) device, and sends the specified number of packets of the 5 1.1 christos * specified length though it, while a child reader process reads and reports 6 1.1 christos * results. 7 1.1 christos * 8 1.1 christos * Written by Matthew Mondor 9 1.1 christos */ 10 1.1 christos 11 1.1 christos #include <sys/cdefs.h> 12 1.5 rin __RCSID("$NetBSD: t_pty.c,v 1.5 2020/06/24 07:02:57 rin Exp $"); 13 1.1 christos 14 1.1 christos #include <errno.h> 15 1.1 christos #include <err.h> 16 1.1 christos #include <fcntl.h> 17 1.1 christos #include <poll.h> 18 1.1 christos #include <stdio.h> 19 1.1 christos #ifdef __linux__ 20 1.1 christos #define _XOPEN_SOURCE 21 1.1 christos #define __USE_XOPEN 22 1.1 christos #endif 23 1.1 christos #include <stdint.h> 24 1.1 christos #include <stdlib.h> 25 1.1 christos #include <string.h> 26 1.1 christos #include <termios.h> 27 1.1 christos #include <unistd.h> 28 1.1 christos 29 1.1 christos #include <sys/ioctl.h> 30 1.1 christos #include <sys/types.h> 31 1.1 christos #include <sys/wait.h> 32 1.1 christos 33 1.1 christos #ifdef STANDALONE 34 1.3 rin #define atf_tc_fail_errno(fmt, ...) err(EXIT_FAILURE, fmt, ## __VA_ARGS__) 35 1.3 rin #define atf_tc_fail(fmt, ...) errx(EXIT_FAILURE, fmt, ## __VA_ARGS__) 36 1.1 christos static __dead void usage(const char *); 37 1.1 christos static void parse_args(int, char **); 38 1.1 christos #else 39 1.1 christos #include <atf-c.h> 40 1.2 christos #include "h_macros.h" 41 1.1 christos #endif 42 1.1 christos 43 1.1 christos static int pty_open(void); 44 1.1 christos static int tty_open(const char *); 45 1.1 christos static void fd_nonblock(int); 46 1.1 christos static pid_t child_spawn(const char *); 47 1.1 christos static void run(void); 48 1.1 christos 49 1.1 christos static size_t buffer_size = 4096; 50 1.1 christos static size_t packets = 2; 51 1.1 christos static uint8_t *dbuf; 52 1.1 christos static int verbose; 53 1.1 christos static int qsize; 54 1.1 christos 55 1.1 christos 56 1.1 christos static 57 1.1 christos void run(void) 58 1.1 christos { 59 1.1 christos size_t i; 60 1.1 christos int pty; 61 1.1 christos int status; 62 1.1 christos pid_t child; 63 1.1 christos if ((dbuf = calloc(1, buffer_size)) == NULL) 64 1.3 rin atf_tc_fail_errno("malloc(%zu)", buffer_size); 65 1.1 christos 66 1.1 christos if (verbose) 67 1.1 christos (void)printf( 68 1.1 christos "parent: started; opening PTY and spawning child\n"); 69 1.1 christos pty = pty_open(); 70 1.1 christos child = child_spawn(ptsname(pty)); 71 1.1 christos if (verbose) 72 1.1 christos (void)printf("parent: sleeping to make sure child is ready\n"); 73 1.1 christos (void)sleep(1); 74 1.1 christos 75 1.1 christos for (i = 0; i < buffer_size; i++) 76 1.1 christos dbuf[i] = i & 0xff; 77 1.1 christos 78 1.1 christos if (verbose) 79 1.1 christos (void)printf("parent: writing\n"); 80 1.1 christos 81 1.1 christos for (i = 0; i < packets; i++) { 82 1.1 christos ssize_t size; 83 1.1 christos 84 1.1 christos if (verbose) 85 1.1 christos (void)printf( 86 1.1 christos "parent: attempting to write %zu bytes to PTY\n", 87 1.1 christos buffer_size); 88 1.1 christos if ((size = write(pty, dbuf, buffer_size)) == -1) { 89 1.3 rin atf_tc_fail_errno("parent: write()"); 90 1.1 christos break; 91 1.1 christos } 92 1.1 christos if (verbose) 93 1.1 christos (void)printf("parent: wrote %zd bytes to PTY\n", size); 94 1.1 christos } 95 1.1 christos 96 1.1 christos if (verbose) 97 1.1 christos (void)printf("parent: waiting for child to exit\n"); 98 1.1 christos if (waitpid(child, &status, 0) == -1) 99 1.3 rin atf_tc_fail_errno("waitpid"); 100 1.1 christos if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) 101 1.3 rin atf_tc_fail("child failed"); 102 1.1 christos 103 1.1 christos if (verbose) 104 1.1 christos (void)printf("parent: closing PTY\n"); 105 1.1 christos (void)close(pty); 106 1.1 christos if (verbose) 107 1.1 christos (void)printf("parent: exiting\n"); 108 1.1 christos } 109 1.1 christos 110 1.1 christos static void 111 1.1 christos condition(int fd) 112 1.1 christos { 113 1.1 christos struct termios tios; 114 1.1 christos 115 1.1 christos if (qsize) { 116 1.1 christos int opt = qsize; 117 1.1 christos if (ioctl(fd, TIOCSQSIZE, &opt) == -1) 118 1.3 rin atf_tc_fail_errno("Couldn't set tty(4) buffer size"); 119 1.1 christos if (ioctl(fd, TIOCGQSIZE, &opt) == -1) 120 1.3 rin atf_tc_fail_errno("Couldn't get tty(4) buffer size"); 121 1.1 christos if (opt != qsize) 122 1.5 rin atf_tc_fail("Wrong qsize %d != %d\n", qsize, opt); 123 1.1 christos } 124 1.1 christos if (tcgetattr(fd, &tios) == -1) 125 1.3 rin atf_tc_fail_errno("tcgetattr()"); 126 1.1 christos cfmakeraw(&tios); 127 1.1 christos cfsetspeed(&tios, B921600); 128 1.1 christos if (tcsetattr(fd, TCSANOW, &tios) == -1) 129 1.3 rin atf_tc_fail_errno("tcsetattr()"); 130 1.1 christos } 131 1.1 christos 132 1.1 christos static int 133 1.1 christos pty_open(void) 134 1.1 christos { 135 1.1 christos int fd; 136 1.1 christos 137 1.1 christos if ((fd = posix_openpt(O_RDWR)) == -1) 138 1.3 rin atf_tc_fail_errno("Couldn't pty(4) device"); 139 1.1 christos condition(fd); 140 1.1 christos if (grantpt(fd) == -1) 141 1.3 rin atf_tc_fail_errno( 142 1.1 christos "Couldn't grant permissions on tty(4) device"); 143 1.1 christos 144 1.1 christos 145 1.1 christos condition(fd); 146 1.1 christos 147 1.1 christos if (unlockpt(fd) == -1) 148 1.3 rin atf_tc_fail_errno("unlockpt()"); 149 1.1 christos 150 1.1 christos return fd; 151 1.1 christos } 152 1.1 christos 153 1.1 christos static int 154 1.1 christos tty_open(const char *ttydev) 155 1.1 christos { 156 1.1 christos int fd; 157 1.1 christos 158 1.1 christos if ((fd = open(ttydev, O_RDWR, 0)) == -1) 159 1.3 rin atf_tc_fail_errno("Couldn't open tty(4) device"); 160 1.1 christos 161 1.1 christos #ifdef USE_PPP_DISCIPLINE 162 1.1 christos { 163 1.1 christos int opt = PPPDISC; 164 1.1 christos if (ioctl(fd, TIOCSETD, &opt) == -1) 165 1.3 rin atf_tc_fail_errno( 166 1.1 christos "Couldn't set tty(4) discipline to PPP"); 167 1.1 christos } 168 1.1 christos #endif 169 1.1 christos 170 1.1 christos condition(fd); 171 1.1 christos 172 1.1 christos return fd; 173 1.1 christos } 174 1.1 christos 175 1.1 christos static void 176 1.1 christos fd_nonblock(int fd) 177 1.1 christos { 178 1.1 christos int opt; 179 1.1 christos 180 1.1 christos if ((opt = fcntl(fd, F_GETFL, NULL)) == -1) 181 1.3 rin atf_tc_fail_errno("fcntl()"); 182 1.1 christos if (fcntl(fd, F_SETFL, opt | O_NONBLOCK) == -1) 183 1.3 rin atf_tc_fail_errno("fcntl()"); 184 1.1 christos } 185 1.1 christos 186 1.1 christos static pid_t 187 1.1 christos child_spawn(const char *ttydev) 188 1.1 christos { 189 1.1 christos pid_t pid; 190 1.1 christos int tty; 191 1.1 christos struct pollfd pfd; 192 1.1 christos size_t total = 0; 193 1.1 christos 194 1.1 christos if ((pid = fork()) == -1) 195 1.3 rin atf_tc_fail_errno("fork()"); 196 1.1 christos (void)setsid(); 197 1.1 christos if (pid != 0) 198 1.1 christos return pid; 199 1.1 christos 200 1.1 christos if (verbose) 201 1.1 christos (void)printf("child: started; open \"%s\"\n", ttydev); 202 1.1 christos tty = tty_open(ttydev); 203 1.1 christos fd_nonblock(tty); 204 1.1 christos 205 1.1 christos if (verbose) 206 1.1 christos (void)printf("child: TTY open, starting read loop\n"); 207 1.1 christos pfd.fd = tty; 208 1.1 christos pfd.events = POLLIN; 209 1.1 christos pfd.revents = 0; 210 1.1 christos for (;;) { 211 1.1 christos int ret; 212 1.1 christos ssize_t size; 213 1.1 christos 214 1.1 christos if (verbose) 215 1.1 christos (void)printf("child: polling\n"); 216 1.1 christos if ((ret = poll(&pfd, 1, 2000)) == -1) 217 1.1 christos err(EXIT_FAILURE, "child: poll()"); 218 1.1 christos if (ret == 0) 219 1.1 christos break; 220 1.1 christos if ((pfd.revents & POLLERR) != 0) 221 1.1 christos break; 222 1.1 christos if ((pfd.revents & POLLIN) != 0) { 223 1.1 christos for (;;) { 224 1.1 christos if (verbose) 225 1.1 christos (void)printf( 226 1.1 christos "child: attempting to read %zu" 227 1.1 christos " bytes\n", buffer_size); 228 1.1 christos if ((size = read(tty, dbuf, buffer_size)) 229 1.1 christos == -1) { 230 1.1 christos if (errno == EAGAIN) 231 1.1 christos break; 232 1.1 christos err(EXIT_FAILURE, "child: read()"); 233 1.1 christos } 234 1.1 christos if (verbose) 235 1.1 christos (void)printf( 236 1.1 christos "child: read %zd bytes from TTY\n", 237 1.1 christos size); 238 1.1 christos if (size == 0) 239 1.1 christos goto end; 240 1.1 christos total += size; 241 1.1 christos } 242 1.1 christos } 243 1.1 christos } 244 1.1 christos end: 245 1.1 christos if (verbose) 246 1.1 christos (void)printf("child: closing TTY %zu\n", total); 247 1.1 christos (void)close(tty); 248 1.1 christos if (verbose) 249 1.1 christos (void)printf("child: exiting\n"); 250 1.1 christos if (total != buffer_size * packets) 251 1.1 christos errx(EXIT_FAILURE, 252 1.1 christos "Lost data %zu != %zu\n", total, buffer_size * packets); 253 1.1 christos 254 1.1 christos exit(EXIT_SUCCESS); 255 1.1 christos } 256 1.1 christos 257 1.1 christos #ifdef STANDALONE 258 1.1 christos static void 259 1.1 christos usage(const char *msg) 260 1.1 christos { 261 1.1 christos 262 1.1 christos if (msg != NULL) 263 1.1 christos (void) fprintf(stderr, "\n%s\n\n", msg); 264 1.1 christos 265 1.1 christos (void)fprintf(stderr, 266 1.1 christos "Usage: %s [-v] [-q <qsize>] [-s <packetsize>] [-n <packets>]\n", 267 1.1 christos getprogname()); 268 1.1 christos 269 1.1 christos exit(EXIT_FAILURE); 270 1.1 christos } 271 1.1 christos 272 1.1 christos static void 273 1.1 christos parse_args(int argc, char **argv) 274 1.1 christos { 275 1.1 christos int ch; 276 1.1 christos 277 1.1 christos while ((ch = getopt(argc, argv, "n:q:s:v")) != -1) { 278 1.1 christos switch (ch) { 279 1.1 christos case 'n': 280 1.1 christos packets = (size_t)atoi(optarg); 281 1.1 christos break; 282 1.1 christos case 'q': 283 1.1 christos qsize = atoi(optarg); 284 1.1 christos break; 285 1.1 christos case 's': 286 1.1 christos buffer_size = (size_t)atoi(optarg); 287 1.1 christos break; 288 1.1 christos case 'v': 289 1.1 christos verbose++; 290 1.1 christos break; 291 1.1 christos default: 292 1.1 christos usage(NULL); 293 1.1 christos break; 294 1.1 christos } 295 1.1 christos } 296 1.1 christos if (buffer_size < 0 || buffer_size > 65536) 297 1.1 christos usage("-s must be between 0 and 65536"); 298 1.1 christos if (packets < 1 || packets > 100) 299 1.1 christos usage("-p must be between 1 and 100"); 300 1.1 christos } 301 1.1 christos 302 1.1 christos int 303 1.1 christos main(int argc, char **argv) 304 1.1 christos { 305 1.1 christos 306 1.1 christos parse_args(argc, argv); 307 1.1 christos run(); 308 1.1 christos exit(EXIT_SUCCESS); 309 1.1 christos } 310 1.1 christos 311 1.1 christos #else 312 1.1 christos ATF_TC(pty_no_queue); 313 1.1 christos 314 1.1 christos ATF_TC_HEAD(pty_no_queue, tc) 315 1.1 christos { 316 1.1 christos atf_tc_set_md_var(tc, "descr", "Checks that writing to pty " 317 1.1 christos "does not lose data with the default queue size of 1024"); 318 1.1 christos } 319 1.1 christos 320 1.1 christos ATF_TC_BODY(pty_no_queue, tc) 321 1.1 christos { 322 1.1 christos qsize = 0; 323 1.1 christos run(); 324 1.1 christos } 325 1.1 christos 326 1.1 christos ATF_TC(pty_queue); 327 1.1 christos 328 1.1 christos ATF_TC_HEAD(pty_queue, tc) 329 1.1 christos { 330 1.1 christos atf_tc_set_md_var(tc, "descr", "Checks that writing to pty " 331 1.1 christos "does not lose data with the a queue size of 4096"); 332 1.1 christos } 333 1.1 christos 334 1.1 christos ATF_TC_BODY(pty_queue, tc) 335 1.1 christos { 336 1.1 christos qsize = 4096; 337 1.1 christos run(); 338 1.1 christos } 339 1.1 christos 340 1.1 christos ATF_TP_ADD_TCS(tp) 341 1.1 christos { 342 1.1 christos ATF_TP_ADD_TC(tp, pty_no_queue); 343 1.1 christos ATF_TP_ADD_TC(tp, pty_queue); 344 1.1 christos 345 1.1 christos return atf_no_error(); 346 1.1 christos } 347 1.1 christos #endif 348