root/tests/kernel/t_pty.c
/* $Id: t_pty.c,v 1.5 2020/06/24 07:02:57 rin Exp $ */

/*
 * Allocates a pty(4) device, and sends the specified number of packets of the
 * specified length though it, while a child reader process reads and reports
 * results.
 *
 * Written by Matthew Mondor
 */

#include <sys/cdefs.h>
__RCSID("$NetBSD: t_pty.c,v 1.5 2020/06/24 07:02:57 rin Exp $");

#include <errno.h>
#include <err.h>
#include <fcntl.h>
#include <poll.h>
#include <stdio.h>
#ifdef __linux__
#define _XOPEN_SOURCE
#define __USE_XOPEN
#endif
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <termios.h>
#include <unistd.h>

#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/wait.h>

#ifdef STANDALONE
#define atf_tc_fail_errno(fmt, ...)     err(EXIT_FAILURE, fmt, ## __VA_ARGS__)
#define atf_tc_fail(fmt, ...)           errx(EXIT_FAILURE, fmt, ## __VA_ARGS__)
static __dead void      usage(const char *);
static void             parse_args(int, char **);
#else
#include <atf-c.h>
#include "h_macros.h"
#endif

static int              pty_open(void);
static int              tty_open(const char *);
static void             fd_nonblock(int);
static pid_t            child_spawn(const char *);
static void             run(void);

static size_t           buffer_size = 4096;
static size_t           packets = 2;
static uint8_t          *dbuf;
static int              verbose;
static int              qsize;


static
void run(void)
{
        size_t i;
        int pty;
        int status;
        pid_t child;
        if ((dbuf = calloc(1, buffer_size)) == NULL)
                atf_tc_fail_errno("malloc(%zu)", buffer_size);

        if (verbose)
                (void)printf(
                    "parent: started; opening PTY and spawning child\n");
        pty = pty_open();
        child = child_spawn(ptsname(pty));
        if (verbose)
                (void)printf("parent: sleeping to make sure child is ready\n");
        (void)sleep(1);

        for (i = 0; i < buffer_size; i++)
                dbuf[i] = i & 0xff;

        if (verbose)
                (void)printf("parent: writing\n");

        for (i = 0; i < packets; i++) {
                ssize_t size;

                if (verbose)
                        (void)printf(
                            "parent: attempting to write %zu bytes to PTY\n",
                            buffer_size);
                if ((size = write(pty, dbuf, buffer_size)) == -1) {
                        atf_tc_fail_errno("parent: write()");
                        break;
                }
                if (verbose)
                        (void)printf("parent: wrote %zd bytes to PTY\n", size);
        }

        if (verbose)
                (void)printf("parent: waiting for child to exit\n");
        if (waitpid(child, &status, 0) == -1)
                atf_tc_fail_errno("waitpid");
        if (!WIFEXITED(status) || WEXITSTATUS(status) != 0)
                atf_tc_fail("child failed");

        if (verbose)
                (void)printf("parent: closing PTY\n");
        (void)close(pty);
        if (verbose)
                (void)printf("parent: exiting\n");
}

static void
condition(int fd)
{
        struct termios  tios;

        if (qsize) {
                int opt = qsize;
                if (ioctl(fd, TIOCSQSIZE, &opt) == -1)
                        atf_tc_fail_errno("Couldn't set tty(4) buffer size");
                if (ioctl(fd, TIOCGQSIZE, &opt) == -1)
                        atf_tc_fail_errno("Couldn't get tty(4) buffer size");
                if (opt != qsize)
                        atf_tc_fail("Wrong qsize %d != %d\n", qsize, opt);
        }
        if (tcgetattr(fd, &tios) == -1)
                atf_tc_fail_errno("tcgetattr()");
        cfmakeraw(&tios);
        cfsetspeed(&tios, B921600);
        if (tcsetattr(fd, TCSANOW, &tios) == -1)
                atf_tc_fail_errno("tcsetattr()");
}

static int
pty_open(void)
{
        int     fd;

        if ((fd = posix_openpt(O_RDWR)) == -1)
                atf_tc_fail_errno("Couldn't pty(4) device");
        condition(fd);
        if (grantpt(fd) == -1)
                atf_tc_fail_errno(
                    "Couldn't grant permissions on tty(4) device");


        condition(fd);

        if (unlockpt(fd) == -1)
                atf_tc_fail_errno("unlockpt()");

        return fd;
}

static int
tty_open(const char *ttydev)
{
        int             fd;

        if ((fd = open(ttydev, O_RDWR, 0)) == -1)
                atf_tc_fail_errno("Couldn't open tty(4) device");

#ifdef USE_PPP_DISCIPLINE
        {
                int     opt = PPPDISC;
                if (ioctl(fd, TIOCSETD, &opt) == -1)
                        atf_tc_fail_errno(
                            "Couldn't set tty(4) discipline to PPP");
        }
#endif

        condition(fd);

        return fd;
}

static void
fd_nonblock(int fd)
{
        int     opt;

        if ((opt = fcntl(fd, F_GETFL, NULL)) == -1)
                atf_tc_fail_errno("fcntl()");
        if (fcntl(fd, F_SETFL, opt | O_NONBLOCK) == -1)
                atf_tc_fail_errno("fcntl()");
}

static pid_t
child_spawn(const char *ttydev)
{
        pid_t           pid;
        int             tty;
        struct pollfd   pfd;
        size_t          total = 0;

        if ((pid = fork()) == -1)
                atf_tc_fail_errno("fork()");
        (void)setsid();
        if (pid != 0)
                return pid;

        if (verbose)
                (void)printf("child: started; open \"%s\"\n", ttydev);
        tty = tty_open(ttydev);
        fd_nonblock(tty);

        if (verbose)
                (void)printf("child: TTY open, starting read loop\n");
        pfd.fd = tty;
        pfd.events = POLLIN;
        pfd.revents = 0;
        for (;;) {
                int     ret;
                ssize_t size;

                if (verbose)
                        (void)printf("child: polling\n");
                if ((ret = poll(&pfd, 1, 2000)) == -1)
                        err(EXIT_FAILURE, "child: poll()");
                if (ret == 0)
                        break;
                if ((pfd.revents & POLLERR) != 0)
                        break;
                if ((pfd.revents & POLLIN) != 0) {
                        for (;;) {
                                if (verbose)
                                        (void)printf(
                                            "child: attempting to read %zu"
                                            " bytes\n", buffer_size);
                                if ((size = read(tty, dbuf, buffer_size))
                                    == -1) {
                                        if (errno == EAGAIN)
                                                break;
                                        err(EXIT_FAILURE, "child: read()");
                                }
                                if (verbose)
                                        (void)printf(
                                            "child: read %zd bytes from TTY\n",
                                            size);
                                if (size == 0)
                                        goto end;
                                total += size;
                        }
                }
        }
end:
        if (verbose)
                (void)printf("child: closing TTY %zu\n", total);
        (void)close(tty);
        if (verbose)
                (void)printf("child: exiting\n");
        if (total != buffer_size * packets)
                errx(EXIT_FAILURE,
                    "Lost data %zu != %zu\n", total, buffer_size * packets);

        exit(EXIT_SUCCESS);
}

#ifdef STANDALONE
static void
usage(const char *msg)
{

        if (msg != NULL)
                (void) fprintf(stderr, "\n%s\n\n", msg);

        (void)fprintf(stderr,
            "Usage: %s [-v] [-q <qsize>] [-s <packetsize>] [-n <packets>]\n",
                getprogname());

        exit(EXIT_FAILURE);
}

static void
parse_args(int argc, char **argv)
{
        int     ch;

        while ((ch = getopt(argc, argv, "n:q:s:v")) != -1) {
                switch (ch) {
                case 'n':
                        packets = (size_t)atoi(optarg);
                        break;
                case 'q':
                        qsize = atoi(optarg);
                        break;
                case 's':
                        buffer_size = (size_t)atoi(optarg);
                        break;
                case 'v':
                        verbose++;
                        break;
                default:
                        usage(NULL);
                        break;
                }
        }
        if (buffer_size < 0 || buffer_size > 65536)
                usage("-s must be between 0 and 65536");
        if (packets < 1 || packets > 100)
                usage("-p must be between 1 and 100");
}

int
main(int argc, char **argv)
{

        parse_args(argc, argv);
        run();
        exit(EXIT_SUCCESS);
}

#else
ATF_TC(pty_no_queue);

ATF_TC_HEAD(pty_no_queue, tc)
{
        atf_tc_set_md_var(tc, "descr", "Checks that writing to pty "
            "does not lose data with the default queue size of 1024");
}

ATF_TC_BODY(pty_no_queue, tc)
{
        qsize = 0;
        run();
}

ATF_TC(pty_queue);

ATF_TC_HEAD(pty_queue, tc)
{
        atf_tc_set_md_var(tc, "descr", "Checks that writing to pty "
            "does not lose data with the a queue size of 4096");
}

ATF_TC_BODY(pty_queue, tc)
{
        qsize = 4096;
        run();
}

ATF_TP_ADD_TCS(tp)
{
        ATF_TP_ADD_TC(tp, pty_no_queue);
        ATF_TP_ADD_TC(tp, pty_queue);

        return atf_no_error();
}
#endif