19.5. pty ProgramThe goal in writing the pty program is to be able to type pty prog arg1 arg2 instead of prog arg1 arg2 When we use pty to execute another program, that program is executed in a session of its own, connected to a pseudo terminal. Let's look at the source code for the pty program. The first file (Figure 19.12) contains the main function. It calls the pty_fork function from the previous section. Figure 19.12. The main function for the pty program#include "apue.h" #include <termios.h> #ifndef TIOCGWINSZ #include <sys/ioctl.h> /* for struct winsize */ #endif #ifdef LINUX #define OPTSTR "+d:einv" #else #define OPTSTR "d:einv" #endif static void set_noecho(int); /* at the end of this file */ void do_driver(char *); /* in the file driver.c */ void loop(int, int); /* in the file loop.c */ int main(int argc, char *argv[]) { int fdm, c, ignoreeof, interactive, noecho, verbose; pid_t pid; char *driver; char slave_name[20]; struct termios orig_termios; struct winsize size; interactive = isatty(STDIN_FILENO); ignoreeof = 0; noecho = 0; verbose = 0; driver = NULL; opterr = 0; /* don't want getopt() writing to stderr */ while ((c = getopt(argc, argv, OPTSTR)) != EOF) { switch (c) { case 'd': /* driver for stdin/stdout */ driver = optarg; break; case 'e': /* noecho for slave pty's line discipline */ noecho = 1; break; case 'i': /* ignore EOF on standard input */ ignoreeof = 1; break; case 'n': /* not interactive */ interactive = 0; break; case 'v': /* verbose */ verbose = 1; break; case '?': err_quit("unrecognized option: -%c", optopt); } } if (optind >= argc) err_quit("usage: pty [ -d driver -einv ] program [ arg ... ]"); if (interactive) { /* fetch current termios and window size */ if (tcgetattr(STDIN_FILENO, &orig_termios) < 0) err_sys("tcgetattr error on stdin"); if (ioctl(STDIN_FILENO, TIOCGWINSZ, (char *) &size) < 0) err_sys("TIOCGWINSZ error"); pid = pty_fork(&fdm, slave_name, sizeof(slave_name), &orig_termios, &size); } else { pid = pty_fork(&fdm, slave_name, sizeof(slave_name), NULL, NULL); } if (pid < 0) { err_sys("fork error"); } else if (pid == 0) { /* child */ if (noecho) set_noecho(STDIN_FILENO); /* stdin is slave pty */ if (execvp(argv[optind], &argv[optind]) < 0) err_sys("can't execute: %s", argv[optind]); } if (verbose) { fprintf(stderr, "slave name = %s\n", slave_name); if (driver != NULL) fprintf(stderr, "driver = %s\n", driver); } if (interactive && driver == NULL) { if (tty_raw(STDIN_FILENO) < 0) /* user's tty to raw mode */ err_sys("tty_raw error"); if (atexit(tty_atexit) < 0) /* reset user's tty on exit */ err_sys("atexit error"); } if (driver) do_driver(driver); /* changes our stdin/stdout */ loop(fdm, ignoreeof); /* copies stdin -> ptym, ptym -> stdout */ exit(0); } static void set_noecho(int fd) /* turn off echo (for slave pty) */ { struct termios stermios; if (tcgetattr(fd, &stermios) < 0) err_sys("tcgetattr error"); stermios.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHONL); /* * Also turn off NL to CR/NL mapping on output. */ stermios.c_oflag &= ~(ONLCR); if (tcsetattr(fd, TCSANOW, &stermios) < 0) err_sys("tcsetattr error"); } In the next section, we'll look at the various command-line options when we examine different uses of the pty program. The getopt function helps us parse command-line arguments in a consistent manner. We'll discuss getopt in more detail in Chapter 21. Before calling pty_fork, we fetch the current values for the termios and winsize structures, passing these as arguments to pty_fork. This way, the PTY slave assumes the same initial state as the current terminal. After returning from pty_fork, the child optionally turns off echoing for the slave PTY and then calls execvp to execute the program specified on the command line. All remaining command-line arguments are passed as arguments to this program. The parent optionally sets the user's terminal to raw mode. In this case, the parent also sets an exit handler to reset the terminal state when exit is called. We describe the do_driver function in the next section. The parent then calls the function loop (Figure 19.13), which copies everything received from the standard input to the PTY master and everything from the PTY master to standard output. For variety, we have coded it in two processes this time, although a single process using select, poll, or multiple threads would also work. Figure 19.13. The loop function#include "apue.h" #define BUFFSIZE 512 static void sig_term(int); static volatile sig_atomic_t sigcaught; /* set by signal handler */ void loop(int ptym, int ignoreeof) { pid_t child; int nread; char buf[BUFFSIZE]; if ((child = fork()) < 0) { err_sys("fork error"); } else if (child == 0) { /* child copies stdin to ptym */ for ( ; ; ) { if ((nread = read(STDIN_FILENO, buf, BUFFSIZE)) < 0) err_sys("read error from stdin"); else if (nread == 0) break; /* EOF on stdin means we're done */ if (writen(ptym, buf, nread) != nread) err_sys("writen error to master pty"); } /* * We always terminate when we encounter an EOF on stdin, * but we notify the parent only if ignoreeof is 0. */ if (ignoreeof == 0) kill(getppid(), SIGTERM); /* notify parent */ exit(0); /* and terminate; child can't return */ } /* * Parent copies ptym to stdout. */ if (signal_intr(SIGTERM, sig_term) == SIG_ERR) err_sys("signal_intr error for SIGTERM"); for ( ; ; ) { if ((nread = read(ptym, buf, BUFFSIZE)) <= 0) break; /* signal caught, error, or EOF */ if (writen(STDOUT_FILENO, buf, nread) != nread) err_sys("writen error to stdout"); } /* * There are three ways to get here: sig_term() below caught the * SIGTERM from the child, we read an EOF on the pty master (which * means we have to signal the child to stop), or an error. */ if (sigcaught == 0) /* tell child if it didn't send us the signal */ kill(child, SIGTERM); /* * Parent returns to caller. */ } /* * The child sends us SIGTERM when it gets EOF on the pty slave or * when read() fails. We probably interrupted the read() of ptym. */ static void sig_term(int signo) { sigcaught = 1; /* just set flag and return */ } Note that, with two processes, when one terminates, it has to notify the other. We use the SIGTERM signal for this notification. |