libslack(pseudo) - pseudo terminal module
#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);
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.
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-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.
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;
}
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)
1995 Tatu Ylonen <ylo@cs.hut.fi> 2001-2023 raf <raf@raf.org>