# NAME

libslack(pseudo) - pseudo terminal module

# SYNOPSIS

        #include <slack/std.h>
        #include <slack/pseudo.h>

        int pty_open(int *pty_user_fd, int *pty_process_fd, char *pty_device_name, size_t pty_device_name_size, const struct termios *pty_device_termios, const struct winsize *pty_device_winsize);
        int pty_release(const char *pty_device_name);
        int pty_set_owner(const char *pty_device_name, uid_t uid);
        int pty_make_controlling_tty(int *pty_process_fd, const char *pty_device_name);
        int pty_change_window_size(int pty_user_fd, int row, int col, int xpixel, int ypixel);
        pid_t pty_fork(int *pty_user_fd, char *pty_device_name, size_t pty_device_name_size, const struct termios *pty_device_termios, const struct winsize *pty_device_winsize);

# DESCRIPTION

This module provides functions for opening pseudo terminals, changing their ownership, making them the controlling terminal, changing their window size and forking a new process whose standard input, output, and error are attached to a pseudo terminal which is made the controlling terminal.

`int pty_open(int *pty_user_fd, int *pty_process_fd, char *pty_device_name, size_t pty_device_name_size, const struct termios *pty_device_termios, const struct winsize *pty_device_winsize)`

  A safe version of openpty(3). Allocates and opens a pseudo terminal. The new file descriptor for the user (or controlling process) side of the pseudo terminal is stored in `*pty_user_fd`. The new file descriptor for the process side of the pseudo terminal is stored in `*pty_process_fd`. The device name of the process side of the pseudo terminal is stored in the buffer pointed to by `pty_device_name` which must be able to hold at least `64` characters. `pty_device_name_size` is the size of the buffer pointed to by `pty_device_name`. No more than `pty_device_name_size` bytes will be written into the buffer pointed to by `pty_device_name`, including the terminating `nul` byte. If `pty_device_termios` is not null, it is passed to tcsetattr(3) with the command `TCSANOW` to set the terminal attributes of the device on the process side of the pseudo terminal. If `pty_device_winsize` is not null, it is passed to ioctl(2) with the command `TIOCSWINSZ` to set the window size of the device on the process side of the pseudo terminal. On success, returns `0`. On error, returns `-1` with `errno` set appropriately.

`int pty_release(const char *pty_device_name)`

  Releases the pseudo terminal device whose name is in `pty_device_name`. Its ownership is returned to root, and its permissions are set to `rw-rw-rw-`. Note that only root can execute this function successfully on most systems. On success, returns `0`. On error, returns `-1` with `errno` set appropriately.

`int pty_set_owner(const char *pty_device_name, uid_t uid)`

  Changes the ownership of the pseudo terminal device referred to by `pty_device_name` to the user id, `uid`. Group ownership of the device will be changed to the `tty` group if it exists. Otherwise, it will be changed to the given user's primary group. The device's permissions will be set to `rw--w----`. Note that only root can execute this function successfully on most systems. Also note that the ownership of the device is automatically set to the real uid of the process by pty_open(3) and pty_fork(3). The permissions are also set automatically by these functions. So pty_set_owner(3) is only needed when the device needs to be owned by some user other than the real user. On success, returns `0`. On error, returns `-1` with `errno` set appropriately.

`int pty_make_controlling_tty(int *pty_process_fd, const char *pty_device_name)`

  Makes the pseudo terminal's process file descriptor the controlling terminal. `*pty_process_fd` contains the file descriptor for the process side of a pseudo terminal. The file descriptor of the resulting controlling terminal will be stored in `*pty_process_fd`. `pty_device_name` is the device name of the process side of the pseudo terminal. On success, returns `0`. On error, returns `-1` with `errno` set appropriately.

`int pty_change_window_size(int pty_user_fd, int row, int col, int xpixel, int ypixel)`

  Changes the window size associated with the pseudo terminal referred to by `pty_user_fd`. The `row`, `col`, `xpixel`, and `ypixel` parameters specify the new window size. On success, returns `0`. On error, returns `-1` with `errno` set appropriately.

`pid_t pty_fork(int *pty_user_fd, char *pty_device_name, size_t pty_device_name_size, const struct termios *pty_device_termios, const struct winsize *pty_device_winsize)`

  A safe version of forkpty(3). Creates a pseudo terminal, and then calls fork(2). In the parent process, the process side of the pseudo terminal is closed. In the child process, the user side of the pseudo terminal is closed, and the process side is made the controlling terminal. It is duplicated onto standard input, output, and error, and is then closed. The user (or controlling process) side of the pseudo terminal is stored in `*pty_user_fd` for the parent process. The device name of the process side of the pseudo terminal is stored in the buffer pointed to by `pty_device_name` which must be able to hold at least `64` bytes. `pty_device_name_size` is the size of the buffer pointed to by `pty_device_name`. No more than `pty_device_name_size` bytes will be written to `pty_device_name`, including the terminating `nul` byte. If `pty_device_termios` is not null, it is passed to tcsetattr(3) with the command `TCSANOW` to set the terminal attributes of the process side of the pseudo terminal device. If `pty_device_winsize` is not null, it is passed to ioctl(2) with the command `TIOCSWINSZ` to set the window size of the process side of the pseudo terminal device. On success, returns `0` to the child process and returns the process id of the child process to the parent process. On error, returns `-1` with `errno` set appropriately.

# ERRORS

Additional errors may be generated and returned from the underlying system calls. See their manual pages.

`EINVAL`

  Invalid arguments were passed to one of the functions.

`ENOTTY`

  openpty(3) or open("/dev/ptc") returned a pty process file descriptor for which ttyname(3) failed to return the pty device name. open("/dev/ptmx") returned a pty user file descriptor for which ptsname(3) failed to return the pty device name.

`ENOENT`

  The old BSD-style pty device search failed to locate an available pseudo terminal.

`ENOSPC`

  The device name of the process side of the pseudo terminal was too large to fit in the `pty_device_name` buffer passed to pty_open(3) or pty_fork(3).

`ENXIO`

  pty_make_controlling_tty(3) failed to disconnect from the controlling terminal.

# MT-Level

MT-Safe if and only if ttyname_r(3) or ptsname_r(3) are available when needed. On systems that have openpty(3) or `"/dev/ptc"`, ttyname_r(3) is required, otherwise the unsafe ttyname(3) will be used. On systems that have `"/dev/ptmx"`, ptsname_r(3) is required, otherwise the unsafe ptsname(3) will be used. On systems that have _getpty(2), pty_open(3) is unsafe because _getpty(2) is unsafe. In short, it's MT-Safe under Linux, Unsafe under Solaris and OpenBSD.

# EXAMPLE

A very simple pty program:

        #include <slack/std.h>
        #include <slack/pseudo.h>
        #include <slack/sig.h>

        #include <sys/select.h>
        #include <sys/wait.h>

        struct termios stdin_termios;
        struct winsize stdin_winsize;
        int havewin = 0;
        char pty_device_name[64];
        int pty_user_fd;
        pid_t pid;

        int tty_raw(int fd)
        {
            struct termios attr[1];

            if (tcgetattr(fd, attr) == -1)
                return -1;

            attr->c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG);
            attr->c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
            attr->c_cflag &= ~(CSIZE | PARENB);
            attr->c_cflag |= (CS8);
            attr->c_oflag &= ~(OPOST);
            attr->c_cc[VMIN] = 1;
            attr->c_cc[VTIME] = 0;

            return tcsetattr(fd, TCSANOW, attr);
        }

        void restore_stdin(void)
        {
            if (tcsetattr(STDIN_FILENO, TCSANOW, &stdin_termios) == -1)
                errorsys("failed to restore stdin terminal attributes");
        }

        void winch(int signo)
        {
            struct winsize winsize;

            if (ioctl(STDIN_FILENO, TIOCGWINSZ, &winsize) != -1)
                ioctl(pty_user_fd, TIOCSWINSZ, &winsize);
        }

        int main(int ac, char **av)
        {
            if (ac == 1)
            {
                fprintf(stderr, "usage: pty command [arg...]\n");
                return EXIT_FAILURE;
            }

            if (isatty(STDIN_FILENO))
                havewin = ioctl(STDIN_FILENO, TIOCGWINSZ, &stdin_winsize) != -1;

            switch (pid = pty_fork(&pty_user_fd, pty_device_name, sizeof pty_device_name, NULL, havewin ? &stdin_winsize : NULL))
            {
                case -1:
                    fprintf(stderr, "pty: pty_fork() failed (%s)\n", strerror(errno));
                    pty_release(pty_device_name);
                    return EXIT_FAILURE;

                case 0:
                    execvp(av[1], av + 1);
                    return EXIT_FAILURE;

                default:
                {
                    int in_fd = STDIN_FILENO;
                    int status;

                    if (isatty(STDIN_FILENO))
                    {
                        if (tcgetattr(STDIN_FILENO, &stdin_termios) != -1)
                            atexit((void (*)(void))restore_stdin);

                        tty_raw(STDIN_FILENO);

                        signal_set_handler(SIGWINCH, 0, winch);
                    }

                    while (pty_user_fd != -1)
                    {
                        fd_set readfds[1];
                        int maxfd;
                        char buf[BUFSIZ];
                        ssize_t bytes;
                        int n;

                        FD_ZERO(readfds);

                        if (in_fd != -1)
                            FD_SET(in_fd, readfds);

                        if (pty_user_fd != -1)
                            FD_SET(pty_user_fd, readfds);

                        maxfd = (pty_user_fd > in_fd) ? pty_user_fd : in_fd;

                        signal_handle_all();

                        if ((n = select(maxfd + 1, readfds, NULL, NULL, NULL)) == -1 && errno != EINTR)
                            break;

                        if (n == -1 && errno == EINTR)
                            continue;

                        if (in_fd != -1 && FD_ISSET(in_fd, readfds))
                        {
                            if ((bytes = read(in_fd, buf, BUFSIZ)) > 0)
                            {
                                if (pty_user_fd != -1 && write(pty_user_fd, buf, bytes) == -1)
                                    break;
                            }
                            else if (n == -1 && errno == EINTR)
                            {
                                continue;
                            }
                            else
                            {
                                in_fd = -1;
                                continue;
                            }
                        }

                        if (pty_user_fd != -1 && FD_ISSET(pty_user_fd, readfds))
                        {
                            if ((bytes = read(pty_user_fd, buf, BUFSIZ)) > 0)
                            {
                                if (write(STDOUT_FILENO, buf, bytes) == -1)
                                    break;
                            }
                            else if (n == -1 && errno == EINTR)
                            {
                                continue;
                            }
                            else
                            {
                                close(pty_user_fd);
                                pty_user_fd = -1;
                                continue;
                            }
                        }
                    }

                    if (waitpid(pid, &status, 0) == -1)
                    {
                        fprintf(stderr, "pty: waitpid(%d) failed (%s)\n", (int)pid, strerror(errno));
                        pty_release(pty_device_name);
                        return EXIT_FAILURE;
                    }
                }
            }

            pty_release(pty_device_name);

            if (pty_user_fd != -1)
                close(pty_user_fd);

            return EXIT_SUCCESS;
        }

# SEE ALSO

libslack(3), openpty(3), forkpty(3) open(2), close(2), grantpt(3), unlockpt(3), ioctl(2), ttyname(3), ttyname_r(3), ptsname(3), ptsname_r(3), setpgrp(2), vhangup(2), setsid(2), _getpty(2), chown(2), chmod(2), tcsetattr(3), setpgrp(2), fork(2), dup2(2)

# AUTHOR

1995 Tatu Ylonen <ylo@cs.hut.fi> 2001-2021 raf <raf@raf.org>