LinuxLinux学习之路

APUE读书笔记-19伪终端(7)

2020-10-03  本文已影响0人  QuietHeart

自定义改进的伪终端处理函数

尽管Single UNIX Specification尝试在这里提高移植特性,但是正如上表所示实现上还是有些问题。因此,我们提供了两个函数处理所有的细节:

返回:如果成功返回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。

上一篇下一篇

猜你喜欢

热点阅读