自定义改进的伪终端处理函数
尽管Single UNIX Specification尝试在这里提高移植特性,但是正如上表所示实现上还是有些问题。因此,我们提供了两个函数处理所有的细节:
-
ptym_open函数打开下一个可用的PTY master设备,
-
ptys_open打开相应的slave设备。
include "apue.h"
int ptym_open(char *pts_name, int pts_namesz);
返回:如果成功返回PTY master的文件描述符号,如果错误返回1。
int ptys_open(char *pts_name);
返回:如果成功返回PTY slave的文件描述符号,如果错误返回1。
一般,我们不会直接调用这两个函数;函数pty_fork会调用他们,也会fork子进程。
ptym_open函数确定下一个可用的PTY master并且打开这个设备。调用者必须分配一个数组来存储master或者slave的名称;如果调用成功,那么相应的slave会通过pts_name返回。这个名字然后被传递给ptys_open,它会打开slave设备。缓存的字节长度会被通过pts_namesz参数传递,这样ptym_open函数就不会拷贝一个比缓存长度还长的字符串了。
提供这两个函数来打开两个设备的原因,在我们展示pty_fork函数的时候会很明显的。一般,一个进程调用ptym_open来打开master并且获得slave的名称。进程然后进行fork,然后子进程调用setsid建立一个新的会话之后将调用ptys_open来打开slave。slave就是这样成为子进程的控制终端的。
(1)基于流的伪终端
下一个可用的PTY master设备通过一个STREAMS的克隆设备来进行访问。克隆设备就是一个特殊的设备,这个设备在被打开的时候会返回一个没有使用的设备。
基于STREAMS的PTY master克隆设备是/dev/ptmx。当我们打开它的时候,这个克隆打开函数会自动确认第一个没有使用的PTY master设备,然后打开那个没有使用的设备(后面我们将会看到,在基于BSD的系统中,我们需要自己寻找第一个没有使用的PTY master设备)。
基于流的伪终端打开函数
#include "apue.h"
#include <errno.h>
#include <fcntl.h>
#include <stropts.h>
int ptym_open(char *pts_name, int pts_namesz)
{
char *ptr;
int fdm;
/*
* Return the name of the master device so that on failure
* the caller can print an error message. Null terminate
* to handle case where strlen("/dev/ptmx") > pts_namesz.
*/
strncpy(pts_name, "/dev/ptmx", pts_namesz);
pts_name[pts_namesz - 1] = '\0';
if ((fdm = open(pts_name, O_RDWR)) < 0)
return(-1);
if (grantpt(fdm) < 0) { /* grant access to slave */
close(fdm);
return(-2);
}
if (unlockpt(fdm) < 0) { /* clear slave's lock flag */
close(fdm);
return(-3);
}
if ((ptr = ptsname(fdm)) == NULL) { /* get slave's name */
close(fdm);
return(-4);
}
/*
* Return name of slave. Null terminate to handle
* case where strlen(ptr) > pts_namesz.
*/
strncpy(pts_name, ptr, pts_namesz);
pts_name[pts_namesz - 1] = '\0';
return(fdm); /* return fd of master */
}
int ptys_open(char *pts_name)
{
int fds, setup;
/*
* The following open should allocate a controlling terminal.
*/
if ((fds = open(pts_name, O_RDWR)) < 0)
return(-5);
/*
* Check if stream is already set up by autopush facility.
*/
if ((setup = ioctl(fds, I_FIND, "ldterm")) < 0) {
close(fds);
return(-6);
}
if (setup == 0) {
if (ioctl(fds, I_PUSH, "ptem") < 0) {
close(fds);
return(-7);
}
if (ioctl(fds, I_PUSH, "ldterm") < 0) {
close(fds);
return(-8);
}
if (ioctl(fds, I_PUSH, "ttcompat") < 0) {
close(fds);
return(-9);
}
}
return(fds);
}
我们首先打开克隆设备/dev/ptmx来获取PTY master的文件描述符号。打开这个master 设备会自动锁住相应的slave 设备。
然后我们调用grantpt来改变slave设备的权限。在Solaris上面,会将slave设备的属主改变成real user ID,并且改变其组属主为tty组,以及将其许可权限修改成允许用户读、写,以及组写。将组属主修改成tty以及打开组写的权限的原因就是程序wall和write是被set-group-ID为tty组的。调用grantpt会执行/usr/lib/pt_chmod程序,这个程序是被set-user-ID为root的这样它能够修改slave的属主。
函数unlockpt被调用以清理slave设备的内部锁状态。我们需要在打开slave之前做这一步。我们必须调用ptsname来获取slave设备的名字,这个名字的形式一般为/dev/pts/NNN。
下一个函数是ptys_open。这个函数会实际打开slave设备。Solaris遵从以前的System V的动作:如果调用者是一个没有控制终端的session leader,那么这个open会将PTY slave分配成为一个控制终端。如果我们不想这么做,那么我们需要在open的时候指定O_NOCTTY标记。
打开slave设备之后,我们可能需要将三个STREAMS模块推送到slave流上面。伪终端模拟模块(ptem)和终端行规则模块(ldterm)一起,使得其行为如同一个终端。ttcompat模块提供了对原来的V7系统、4BSD系统以及Xenix ioctl调用的兼容,它是一个可选的模块,但是因为它在终端登陆以及网络登陆的时候会被自动推送,我们也会将它推送到slave流上面。
如果这三个模块已经存在,那么我们不需要推送它们。STREAMS系统提供了一个叫做autopush的工具,这个工具允许管理者配置一个模块的列表,这些模块会在特定的终端设备打开的时候被推送到一个流上面。我们使用ioctl的I_FIND命令来查看ldterm是否已经被推送到流上面,如果已经推送上去了,那么我们会假定流已经被autopush机制配置好了,就不用再次推送这个模块了。
调用ptym_open和ptys_open的结果就是给调用进程打开了两个文件描述符号:一个用于master,另外一个用于slave。
网友评论