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-2023 raf <raf@raf.org>