NAME

libslack(pseudo) - pseudo terminal module


SYNOPSIS

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

    int pty_open(int *masterfd, int *slavefd, char *slavename, size_t slavenamesize, const struct termios *slave_termios, const struct winsize *slave_winsize);
    int pty_release(const char *slavename);
    int pty_set_owner(const char *slavename, uid_t uid);
    int pty_make_controlling_tty(int *slavefd, const char *slavename);
    int pty_change_window_size(int masterfd, int row, int col, int xpixel, int ypixel);
    pid_t pty_fork(int *masterfd, char *slavename, size_t slavenamesize, const struct termios *slave_termios, const struct winsize *slave_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 and attached to a pseudo terminal which is made the controlling terminal.

int pty_open(int *masterfd, int *slavefd, char *slavename, size_t slavenamesize, const struct termios *slave_termios, const struct winsize *slave_winsize)

A safe version of openpty(3). Allocates and opens a pseudo terminal. The new descriptor for the master side of the pseudo terminal is stored in *masterfd. The new descriptor for the slave side of the pseudo terminal is stored in *slavefd. The device name of the slave side of the pseudo terminal is stored in the buffer pointed to by slavename which must be able to hold at least 64 characters. slavenamesize is the size of the buffer pointed to by slavename. No more than slavenamesize bytes will be written into the buffer pointed to by slavename, including the terminating nul byte. If slave_termios is not null, it is passed to tcsetattr(3) with the command TCSANOW to set the terminal attributes of the slave device. If slave_winsize is not null, it is passed to ioctl(2) with the command TIOCSWINSZ to set the window size of the slave device. On success, returns 0. On error, returns -1 with errno set appropriately.

int pty_release(const char *slavename)

Releases the slave tty device whose name is in slavename. Its ownership is returned to root, and its permissions 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 *slavename, uid_t uid)

Changes the ownership of the slave pty device referred to by slavename to the user id, uid. Group ownership of the slave pty device will be changed to the tty group if it exists. Otherwise, it will be changed to the given user's primary group. The slave pty device's permissions are 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 *slavefd, const char *slavename)

Makes the slave pty the controlling terminal. *slavefd contains the descriptor for the slave side of a pseudo terminal. The descriptor of the resulting controlling terminal will be stored in *slavefd. slavename is the device name of the slave side of the pseudo terminal. On success, returns 0. On error, returns -1 with errno set appropriately.

int pty_change_window_size(int masterfd, int row, int col, int xpixel, int ypixel)

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

pid_t pty_fork(int *masterfd, char *slavename, size_t slavenamesize, const struct termios *slave_termios, const struct winsize *slave_winsize)

A safe version of forkpty(3). Creates a pseudo terminal and then calls fork(2). In the parent process, the slave side of the pseudo terminal is closed. In the child process, the master side of the pseudo terminal is closed and the slave side is made the controlling terminal. It is duplicated onto standard input, output and error and then closed. The master side of the pseudo terminal is stored in *masterfd for the parent process. The device name of the slave side of the pseudo terminal is stored in the buffer pointed to by slavename which must be able to hold at least 64 bytes. slavenamesize is the size of the buffer pointed to by slavename. No more than slavenamesize bytes will be written to slavename, including the terminating nul byte. If slave_termios is not null, it is passed to tcsetattr(3) with the command TCSANOW to set the terminal attributes of the slave device. If slave_winsize is not null, it is passed to ioctl(2) with the command TIOCSWINSZ to set the window size of the slave 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 slave descriptor for which ttyname(3) failed to return the slave device name. open("/dev/ptmx") returned a master descriptor for which ptsname(3) failed to return the slave device name.

ENOENT

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

ENOSPC

The device name of the slave side of the pseudo terminal was too large to fit in the slavename 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 slavename[64];
    int masterfd;
    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(masterfd, 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(&masterfd, slavename, sizeof slavename, NULL, havewin ? &stdin_winsize : NULL))
        {
            case -1:
                fprintf(stderr, "pty: pty_fork() failed (%s)\n", strerror(errno));
                pty_release(slavename);
                return EXIT_FAILURE;

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

            default:
            {
                int infd = 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 (masterfd != -1)
                {
                    fd_set readfds[1];
                    int maxfd;
                    char buf[BUFSIZ];
                    ssize_t bytes;
                    int n;

                    FD_ZERO(readfds);

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

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

                    maxfd = (masterfd > infd) ? masterfd : infd;

                    signal_handle_all();

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

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

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

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

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

        pty_release(slavename);

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

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