Some programs require a terminal in order to function (such as an interactive shell, the passwd program, etc.) We may want to "pipe" data to and from such programs, but we cannot use a simple pipe that replacing stdin/out/err for such programs. To control the input and output of such programs we must use a pseudo-terminal which acts very much like a bi-directional pipe but with a virtual TTY for the program(s) on the other end.
A following steps are required to open a pseudo terminal:
Get/save our preferred TTY line discipline and window size.
Open the pty (pseudo-terminal) master device using posix_openpt(3).
posix_openpt
Call grantpt(3) to get a "slave" device.
grantpt
Call unlockpt(3) to unlock the slave device.
unlockpt
Use ptsname(3) to learn the slave device name (ex: "/dev/pts/2")
ptsname
/dev/pts/2
Fork a new process:
A) In the child:
a) Open the slave TTY using open() b) Make the opened slave the stdin/out/err of the new process. c) Apply the saved TTY disciplines/window size from step 0. d) Execve() the new program.
open()
stdin
out
err
B) In the parent:
a) Read and write on the master device descriptor to communicate with the child process, alasocketpair()`.
la
#define _XOPEN_SOURCE 600 #include <stdlib.h> #include <fcntl.h> int posix_openpt(int flags);
/dev/ptmx
flags: O_RDWR Open device for reading and writing O_NOCTTY Don't make the new pty the controlling terminal for this process.
int pty = posix_openpt(O_RDWR | O_NOCTTY);
#define _XOPEN_SOURCE 500 #include <stdlib.h> int grantpt(int fd);
0620
grantpt(pty);
#define _XOPEN_SOURCE 500 #include <stdlib.h> int unlockpt(int fd);
unlock(pty);
#define _XOPEN_SOURCE 500 #include <stdlib.h> char *ptsname(int fd);
Returns the name (such as "/dev/pts/2") of the slave pty device that corresponds to the master pty. Use the returned name to open the pty in the child and attach it to stdin/out/err.
Returns a string or NULL on error.
char *slave_name = ptsname(pty);
#include <termios.h> #include <unistd.h> int tcgetattr(int fd, struct termios *termios_p); int tcsetattr(int fd, int optional_actions, const struct termios *termios_p);
Get or set terminal line disciplines.
optional_actions can be one of:
optional_actions
TCSANOW Change things immediately. TCSADRAIN Change after everything has been written. TCSAFLUSH Change after everything is written and anything not read is flushed.
Returns 0 on success, -1 on error.
struct termios save; tcgetattr(STDIN_FILENO, &save); ... tcsetattr(slave_pty, TCSANOW, &save);
#include <sys/ioctl.h> int ioctl(int fd, unsigned long request, ...);
Makes some request w/ respect to the given file descriptor. There are many ioctls. Returns 0 on success, or -1 on error.
Additional parameters after the request may be required.
Requests: TIOCGWINSZ Get the window size (stored in a struct winsize pointer) TIOCSWINSZ Set the window size
struct winsize { unsigned short ws_row; unsigned short ws_col; unsigned short ws_xpixel; /* unused */ unsigned short ws_ypixel; /* unused */ };
SIGWINCH
struct winsize ws; ioctl(STDIN_FILENO, TIOCGWINSZ, &ws); ... ioctl(slave_pty, TIOCSWINSZ, &ws);
#define _XOPEN_SOURCE 600 #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <fcntl.h> #include <termios.h> #include <sys/ioctl.h> #include <errno.h> #define K 1024 // man pty int main(int argc, char *argv[]) { struct termios tio; struct winsize ws; pid_t pid; int pty, tty, r; char *pts, buf[K]; // Add a signal handler to handle things like SIGCHLD, maybe SIGWINCH // Get line attributes from our TTY: if (tcgetattr(STDIN_FILENO, &tio) < 0) { perror("tcgetattr"); exit(1); } // Get the window size from our TTY: if (ioctl(STDIN_FILENO, TIOCGWINSZ, &ws) < 0) { fprintf(stderr,"Getting winsize\n"); exit(1); } // Open master end of the PTY: if ((pty = posix_openpt(O_RDWR | O_NOCTTY)) < 0) { perror("openpt"); exit(1); } // The remaining can probably be done in the child process. // Allocate a slave terminal to us: if (grantpt(pty) < 0) { perror("grantpt"); exit(1); } // Unlock and set permissions on slave terminal side: if (unlockpt(pty) < 0) { perror("unlockpt"); exit(1); } // Find out what the device name of the slave side pseudo terminal is: if ((pts = ptsname(pty)) == NULL) { perror("ptsname"); exit(1); } pid = fork(); if (pid < 0) { perror("fork"); exit(1); } if (pid == 0) { // Open slave side PTY: if ((tty = open(pts, O_RDWR)) < 0) { perror("slave open"); exit(1); } dup2(tty, STDIN_FILENO); dup2(tty, STDOUT_FILENO); dup2(tty, STDERR_FILENO); close(tty); // Set line attributes (immediately): if (tcsetattr(STDIN_FILENO, TCSANOW, &tio) < 0) { perror("tcsetattr"); } // Set the window size: if (ioctl(STDIN_FILENO, TIOCSWINSZ, &ws) < 0) { fprintf(stderr,"winsize\n"); } execlp("arogue", "arogue", NULL); perror("execlp"); exit(1); } // In the parent process: // Add support for async I/O on keyboard and from the master pty: while((r = read(pty, buf, K)) > 0) { write(STDOUT_FILENO, buf, r); } exit(0); }
/** * Termios are documented in man 3 tcgetattr */ void noecho() { struct termios t; tcgetattr(STDIN_FILENO,&t); // Turn off: // ICANNON - canonical mode (line buffered/processed input) // ECHO - echoing characters as we type them // ISIG - Send signals when special keys are typed (Ctrl-C, Ctrl-Z, etc) // IEXTEN - Enable line editing t.c_lflag &= ~(ICANON|ECHO|ISIG|IEXTEN); // Set MIN minimum number of characters to block read on and TIME maximum // amount of time to wait for input. t.c_cc[VMIN] = 1; t.c_cc[VTIME] = 0; tcsetattr(STDIN_FILENO,TCSANOW,&t); } void echo() { struct termios t; tcgetattr(STDIN_FILENO, &t); t.c_lflag |= (ICANON|ECHO|ISIG|IEXTEN); tcsetattr(STDIN_FILENO, TCSANOW, &t); }