1*f01e01ebSrin /* $Id: t_pty.c,v 1.5 2020/06/24 07:02:57 rin Exp $ */
26f52d919Schristos
36f52d919Schristos /*
46f52d919Schristos * Allocates a pty(4) device, and sends the specified number of packets of the
56f52d919Schristos * specified length though it, while a child reader process reads and reports
66f52d919Schristos * results.
76f52d919Schristos *
86f52d919Schristos * Written by Matthew Mondor
96f52d919Schristos */
106f52d919Schristos
116f52d919Schristos #include <sys/cdefs.h>
12*f01e01ebSrin __RCSID("$NetBSD: t_pty.c,v 1.5 2020/06/24 07:02:57 rin Exp $");
136f52d919Schristos
146f52d919Schristos #include <errno.h>
156f52d919Schristos #include <err.h>
166f52d919Schristos #include <fcntl.h>
176f52d919Schristos #include <poll.h>
186f52d919Schristos #include <stdio.h>
196f52d919Schristos #ifdef __linux__
206f52d919Schristos #define _XOPEN_SOURCE
216f52d919Schristos #define __USE_XOPEN
226f52d919Schristos #endif
236f52d919Schristos #include <stdint.h>
246f52d919Schristos #include <stdlib.h>
256f52d919Schristos #include <string.h>
266f52d919Schristos #include <termios.h>
276f52d919Schristos #include <unistd.h>
286f52d919Schristos
296f52d919Schristos #include <sys/ioctl.h>
306f52d919Schristos #include <sys/types.h>
316f52d919Schristos #include <sys/wait.h>
326f52d919Schristos
336f52d919Schristos #ifdef STANDALONE
345e62095eSrin #define atf_tc_fail_errno(fmt, ...) err(EXIT_FAILURE, fmt, ## __VA_ARGS__)
355e62095eSrin #define atf_tc_fail(fmt, ...) errx(EXIT_FAILURE, fmt, ## __VA_ARGS__)
366f52d919Schristos static __dead void usage(const char *);
376f52d919Schristos static void parse_args(int, char **);
386f52d919Schristos #else
396f52d919Schristos #include <atf-c.h>
40c54cb811Schristos #include "h_macros.h"
416f52d919Schristos #endif
426f52d919Schristos
436f52d919Schristos static int pty_open(void);
446f52d919Schristos static int tty_open(const char *);
456f52d919Schristos static void fd_nonblock(int);
466f52d919Schristos static pid_t child_spawn(const char *);
476f52d919Schristos static void run(void);
486f52d919Schristos
496f52d919Schristos static size_t buffer_size = 4096;
506f52d919Schristos static size_t packets = 2;
516f52d919Schristos static uint8_t *dbuf;
526f52d919Schristos static int verbose;
536f52d919Schristos static int qsize;
546f52d919Schristos
556f52d919Schristos
566f52d919Schristos static
run(void)576f52d919Schristos void run(void)
586f52d919Schristos {
596f52d919Schristos size_t i;
606f52d919Schristos int pty;
616f52d919Schristos int status;
626f52d919Schristos pid_t child;
636f52d919Schristos if ((dbuf = calloc(1, buffer_size)) == NULL)
645e62095eSrin atf_tc_fail_errno("malloc(%zu)", buffer_size);
656f52d919Schristos
666f52d919Schristos if (verbose)
676f52d919Schristos (void)printf(
686f52d919Schristos "parent: started; opening PTY and spawning child\n");
696f52d919Schristos pty = pty_open();
706f52d919Schristos child = child_spawn(ptsname(pty));
716f52d919Schristos if (verbose)
726f52d919Schristos (void)printf("parent: sleeping to make sure child is ready\n");
736f52d919Schristos (void)sleep(1);
746f52d919Schristos
756f52d919Schristos for (i = 0; i < buffer_size; i++)
766f52d919Schristos dbuf[i] = i & 0xff;
776f52d919Schristos
786f52d919Schristos if (verbose)
796f52d919Schristos (void)printf("parent: writing\n");
806f52d919Schristos
816f52d919Schristos for (i = 0; i < packets; i++) {
826f52d919Schristos ssize_t size;
836f52d919Schristos
846f52d919Schristos if (verbose)
856f52d919Schristos (void)printf(
866f52d919Schristos "parent: attempting to write %zu bytes to PTY\n",
876f52d919Schristos buffer_size);
886f52d919Schristos if ((size = write(pty, dbuf, buffer_size)) == -1) {
895e62095eSrin atf_tc_fail_errno("parent: write()");
906f52d919Schristos break;
916f52d919Schristos }
926f52d919Schristos if (verbose)
936f52d919Schristos (void)printf("parent: wrote %zd bytes to PTY\n", size);
946f52d919Schristos }
956f52d919Schristos
966f52d919Schristos if (verbose)
976f52d919Schristos (void)printf("parent: waiting for child to exit\n");
986f52d919Schristos if (waitpid(child, &status, 0) == -1)
995e62095eSrin atf_tc_fail_errno("waitpid");
1006f52d919Schristos if (!WIFEXITED(status) || WEXITSTATUS(status) != 0)
1015e62095eSrin atf_tc_fail("child failed");
1026f52d919Schristos
1036f52d919Schristos if (verbose)
1046f52d919Schristos (void)printf("parent: closing PTY\n");
1056f52d919Schristos (void)close(pty);
1066f52d919Schristos if (verbose)
1076f52d919Schristos (void)printf("parent: exiting\n");
1086f52d919Schristos }
1096f52d919Schristos
1106f52d919Schristos static void
condition(int fd)1116f52d919Schristos condition(int fd)
1126f52d919Schristos {
1136f52d919Schristos struct termios tios;
1146f52d919Schristos
1156f52d919Schristos if (qsize) {
1166f52d919Schristos int opt = qsize;
1176f52d919Schristos if (ioctl(fd, TIOCSQSIZE, &opt) == -1)
1185e62095eSrin atf_tc_fail_errno("Couldn't set tty(4) buffer size");
1196f52d919Schristos if (ioctl(fd, TIOCGQSIZE, &opt) == -1)
1205e62095eSrin atf_tc_fail_errno("Couldn't get tty(4) buffer size");
1216f52d919Schristos if (opt != qsize)
122*f01e01ebSrin atf_tc_fail("Wrong qsize %d != %d\n", qsize, opt);
1236f52d919Schristos }
1246f52d919Schristos if (tcgetattr(fd, &tios) == -1)
1255e62095eSrin atf_tc_fail_errno("tcgetattr()");
1266f52d919Schristos cfmakeraw(&tios);
1276f52d919Schristos cfsetspeed(&tios, B921600);
1286f52d919Schristos if (tcsetattr(fd, TCSANOW, &tios) == -1)
1295e62095eSrin atf_tc_fail_errno("tcsetattr()");
1306f52d919Schristos }
1316f52d919Schristos
1326f52d919Schristos static int
pty_open(void)1336f52d919Schristos pty_open(void)
1346f52d919Schristos {
1356f52d919Schristos int fd;
1366f52d919Schristos
1376f52d919Schristos if ((fd = posix_openpt(O_RDWR)) == -1)
1385e62095eSrin atf_tc_fail_errno("Couldn't pty(4) device");
1396f52d919Schristos condition(fd);
1406f52d919Schristos if (grantpt(fd) == -1)
1415e62095eSrin atf_tc_fail_errno(
1426f52d919Schristos "Couldn't grant permissions on tty(4) device");
1436f52d919Schristos
1446f52d919Schristos
1456f52d919Schristos condition(fd);
1466f52d919Schristos
1476f52d919Schristos if (unlockpt(fd) == -1)
1485e62095eSrin atf_tc_fail_errno("unlockpt()");
1496f52d919Schristos
1506f52d919Schristos return fd;
1516f52d919Schristos }
1526f52d919Schristos
1536f52d919Schristos static int
tty_open(const char * ttydev)1546f52d919Schristos tty_open(const char *ttydev)
1556f52d919Schristos {
1566f52d919Schristos int fd;
1576f52d919Schristos
1586f52d919Schristos if ((fd = open(ttydev, O_RDWR, 0)) == -1)
1595e62095eSrin atf_tc_fail_errno("Couldn't open tty(4) device");
1606f52d919Schristos
1616f52d919Schristos #ifdef USE_PPP_DISCIPLINE
1626f52d919Schristos {
1636f52d919Schristos int opt = PPPDISC;
1646f52d919Schristos if (ioctl(fd, TIOCSETD, &opt) == -1)
1655e62095eSrin atf_tc_fail_errno(
1666f52d919Schristos "Couldn't set tty(4) discipline to PPP");
1676f52d919Schristos }
1686f52d919Schristos #endif
1696f52d919Schristos
1706f52d919Schristos condition(fd);
1716f52d919Schristos
1726f52d919Schristos return fd;
1736f52d919Schristos }
1746f52d919Schristos
1756f52d919Schristos static void
fd_nonblock(int fd)1766f52d919Schristos fd_nonblock(int fd)
1776f52d919Schristos {
1786f52d919Schristos int opt;
1796f52d919Schristos
1806f52d919Schristos if ((opt = fcntl(fd, F_GETFL, NULL)) == -1)
1815e62095eSrin atf_tc_fail_errno("fcntl()");
1826f52d919Schristos if (fcntl(fd, F_SETFL, opt | O_NONBLOCK) == -1)
1835e62095eSrin atf_tc_fail_errno("fcntl()");
1846f52d919Schristos }
1856f52d919Schristos
1866f52d919Schristos static pid_t
child_spawn(const char * ttydev)1876f52d919Schristos child_spawn(const char *ttydev)
1886f52d919Schristos {
1896f52d919Schristos pid_t pid;
1906f52d919Schristos int tty;
1916f52d919Schristos struct pollfd pfd;
1926f52d919Schristos size_t total = 0;
1936f52d919Schristos
1946f52d919Schristos if ((pid = fork()) == -1)
1955e62095eSrin atf_tc_fail_errno("fork()");
1966f52d919Schristos (void)setsid();
1976f52d919Schristos if (pid != 0)
1986f52d919Schristos return pid;
1996f52d919Schristos
2006f52d919Schristos if (verbose)
2016f52d919Schristos (void)printf("child: started; open \"%s\"\n", ttydev);
2026f52d919Schristos tty = tty_open(ttydev);
2036f52d919Schristos fd_nonblock(tty);
2046f52d919Schristos
2056f52d919Schristos if (verbose)
2066f52d919Schristos (void)printf("child: TTY open, starting read loop\n");
2076f52d919Schristos pfd.fd = tty;
2086f52d919Schristos pfd.events = POLLIN;
2096f52d919Schristos pfd.revents = 0;
2106f52d919Schristos for (;;) {
2116f52d919Schristos int ret;
2126f52d919Schristos ssize_t size;
2136f52d919Schristos
2146f52d919Schristos if (verbose)
2156f52d919Schristos (void)printf("child: polling\n");
2166f52d919Schristos if ((ret = poll(&pfd, 1, 2000)) == -1)
2176f52d919Schristos err(EXIT_FAILURE, "child: poll()");
2186f52d919Schristos if (ret == 0)
2196f52d919Schristos break;
2206f52d919Schristos if ((pfd.revents & POLLERR) != 0)
2216f52d919Schristos break;
2226f52d919Schristos if ((pfd.revents & POLLIN) != 0) {
2236f52d919Schristos for (;;) {
2246f52d919Schristos if (verbose)
2256f52d919Schristos (void)printf(
2266f52d919Schristos "child: attempting to read %zu"
2276f52d919Schristos " bytes\n", buffer_size);
2286f52d919Schristos if ((size = read(tty, dbuf, buffer_size))
2296f52d919Schristos == -1) {
2306f52d919Schristos if (errno == EAGAIN)
2316f52d919Schristos break;
2326f52d919Schristos err(EXIT_FAILURE, "child: read()");
2336f52d919Schristos }
2346f52d919Schristos if (verbose)
2356f52d919Schristos (void)printf(
2366f52d919Schristos "child: read %zd bytes from TTY\n",
2376f52d919Schristos size);
2386f52d919Schristos if (size == 0)
2396f52d919Schristos goto end;
2406f52d919Schristos total += size;
2416f52d919Schristos }
2426f52d919Schristos }
2436f52d919Schristos }
2446f52d919Schristos end:
2456f52d919Schristos if (verbose)
2466f52d919Schristos (void)printf("child: closing TTY %zu\n", total);
2476f52d919Schristos (void)close(tty);
2486f52d919Schristos if (verbose)
2496f52d919Schristos (void)printf("child: exiting\n");
2506f52d919Schristos if (total != buffer_size * packets)
2516f52d919Schristos errx(EXIT_FAILURE,
2526f52d919Schristos "Lost data %zu != %zu\n", total, buffer_size * packets);
2536f52d919Schristos
2546f52d919Schristos exit(EXIT_SUCCESS);
2556f52d919Schristos }
2566f52d919Schristos
2576f52d919Schristos #ifdef STANDALONE
2586f52d919Schristos static void
usage(const char * msg)2596f52d919Schristos usage(const char *msg)
2606f52d919Schristos {
2616f52d919Schristos
2626f52d919Schristos if (msg != NULL)
2636f52d919Schristos (void) fprintf(stderr, "\n%s\n\n", msg);
2646f52d919Schristos
2656f52d919Schristos (void)fprintf(stderr,
2666f52d919Schristos "Usage: %s [-v] [-q <qsize>] [-s <packetsize>] [-n <packets>]\n",
2676f52d919Schristos getprogname());
2686f52d919Schristos
2696f52d919Schristos exit(EXIT_FAILURE);
2706f52d919Schristos }
2716f52d919Schristos
2726f52d919Schristos static void
parse_args(int argc,char ** argv)2736f52d919Schristos parse_args(int argc, char **argv)
2746f52d919Schristos {
2756f52d919Schristos int ch;
2766f52d919Schristos
2776f52d919Schristos while ((ch = getopt(argc, argv, "n:q:s:v")) != -1) {
2786f52d919Schristos switch (ch) {
2796f52d919Schristos case 'n':
2806f52d919Schristos packets = (size_t)atoi(optarg);
2816f52d919Schristos break;
2826f52d919Schristos case 'q':
2836f52d919Schristos qsize = atoi(optarg);
2846f52d919Schristos break;
2856f52d919Schristos case 's':
2866f52d919Schristos buffer_size = (size_t)atoi(optarg);
2876f52d919Schristos break;
2886f52d919Schristos case 'v':
2896f52d919Schristos verbose++;
2906f52d919Schristos break;
2916f52d919Schristos default:
2926f52d919Schristos usage(NULL);
2936f52d919Schristos break;
2946f52d919Schristos }
2956f52d919Schristos }
2966f52d919Schristos if (buffer_size < 0 || buffer_size > 65536)
2976f52d919Schristos usage("-s must be between 0 and 65536");
2986f52d919Schristos if (packets < 1 || packets > 100)
2996f52d919Schristos usage("-p must be between 1 and 100");
3006f52d919Schristos }
3016f52d919Schristos
3026f52d919Schristos int
main(int argc,char ** argv)3036f52d919Schristos main(int argc, char **argv)
3046f52d919Schristos {
3056f52d919Schristos
3066f52d919Schristos parse_args(argc, argv);
3076f52d919Schristos run();
3086f52d919Schristos exit(EXIT_SUCCESS);
3096f52d919Schristos }
3106f52d919Schristos
3116f52d919Schristos #else
3126f52d919Schristos ATF_TC(pty_no_queue);
3136f52d919Schristos
ATF_TC_HEAD(pty_no_queue,tc)3146f52d919Schristos ATF_TC_HEAD(pty_no_queue, tc)
3156f52d919Schristos {
3166f52d919Schristos atf_tc_set_md_var(tc, "descr", "Checks that writing to pty "
3176f52d919Schristos "does not lose data with the default queue size of 1024");
3186f52d919Schristos }
3196f52d919Schristos
ATF_TC_BODY(pty_no_queue,tc)3206f52d919Schristos ATF_TC_BODY(pty_no_queue, tc)
3216f52d919Schristos {
3226f52d919Schristos qsize = 0;
3236f52d919Schristos run();
3246f52d919Schristos }
3256f52d919Schristos
3266f52d919Schristos ATF_TC(pty_queue);
3276f52d919Schristos
ATF_TC_HEAD(pty_queue,tc)3286f52d919Schristos ATF_TC_HEAD(pty_queue, tc)
3296f52d919Schristos {
3306f52d919Schristos atf_tc_set_md_var(tc, "descr", "Checks that writing to pty "
3316f52d919Schristos "does not lose data with the a queue size of 4096");
3326f52d919Schristos }
3336f52d919Schristos
ATF_TC_BODY(pty_queue,tc)3346f52d919Schristos ATF_TC_BODY(pty_queue, tc)
3356f52d919Schristos {
3366f52d919Schristos qsize = 4096;
3376f52d919Schristos run();
3386f52d919Schristos }
3396f52d919Schristos
ATF_TP_ADD_TCS(tp)3406f52d919Schristos ATF_TP_ADD_TCS(tp)
3416f52d919Schristos {
3426f52d919Schristos ATF_TP_ADD_TC(tp, pty_no_queue);
3436f52d919Schristos ATF_TP_ADD_TC(tp, pty_queue);
3446f52d919Schristos
3456f52d919Schristos return atf_no_error();
3466f52d919Schristos }
3476f52d919Schristos #endif
348