5、pty程序
写pty程序是因为这样就可以用"pty prog arg1 arg2"方式的键入取代"prog arg1 arg2"方式的键入。
当我们使用pty来执行另外一个程序的时候,那个程序会在它自己的session中被执行,并且连接到一个伪终端上面。下面,我们来看一下pty程序的源代码。
pty程序的main函数
首先是包含main函数的文件,这个文件会调用前面章节中定义的pty_fork函数。
#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");
}
在下一节,我们将要看到使用pty程序的各种不同的命令行选项。getopt函数帮助我们将命令参数以一个比较一致的方式包装起来,本书21章对getopt函数进行了更详细的解说。
注:鉴于以后可能不会再对像20,21章这样只是开发一个程序的过程描述、这样的章节进行详细的翻译,后面给出一个经过实践的使用和解说getopt的例子, 参见: 对于getopt函数的解说以及例子 (参见第5.3.1节)。
言归正转(早上上班的时候在公交车上学到的,用英语应该是"Let's get down to business."_),
- 在调用pty_fork之前,我们获得当前termios和winsize结构的值,并且将它们作为pty_fork的参数进行传递。采用这个方法,PTY slave假设具有和当前终端同样的初始状态。
- 从pty_fork返回之后,子进程可以关闭slave PTY的显示,然后调用execvp来执行命令行中指定的程序。所有剩下的命令行的参数都会被作为参数传递给这个程序。
- 父进程可以设置用户终端为raw模式。在这个情况下,父进程也设置exit处理函数以便调用exit的时候重置终端状态。我们后面会对do_driver函数进行描述。
- 父进程然后调用loop函数,这个函数会将从标准输入上面接收到的所有数据拷贝到PTY master上面,并且将拷贝PTY master上面的所有内容到标准输出。为了表示多样性,我们这里在两个进程中对它进行拷贝。当然,在一个进程中使用select,pool或者多线程的方式也可以做到这个效果。
网友评论