打开伪终端设备

在[url=http://aisxyz.iteye.com/admin/blogs/2423960]伪终端概述[/url]一节中已对 PTY进行了初步的介绍。尽管 PTY 表现得就像物理终端设备一样,不过在打开 PTY 设备文件时,应用程序并不需要设置 O_TTY_INIT 标识(见[url=http://aisxyz.iteye.com/admin/blogs/2370533]不带缓冲的文件I/O之open[/url])。各种平台打开 PTY 设备的方法有所不同,posix_openpt 函数提供了一种可移植的方法来打开下一个可用的伪终端主设备。

#include <stdlib.h>
#include <fcntl.h>
int posix_openpt(int oflag);
/* 返回值:若成功,返回下一个可用的 PTY 主设备的文件描述符;否则,返回 -1 */

参数 oflag 是一个位屏蔽字,指定如何打开主设备。它类似于 open 函数的 oflag 参数,但只支持指定 O_RDWR 来打开主设备进行读、写,或指定 O_NOCTTY 来防止主设备成为调用者的控制终端,其他打开标志都会导致未定义的行为。
在伪终端从设备可用之前,它的权限必须设置,以便应用程序可以访问它。grantpt 函数可以把从设备节点的用户 ID 设置为调用者的实际用户 ID,设置其组 ID 为一非指定值,通常是可以访问该终端设备的组。权限被设置为 0620,即对个体所有者是读/写,对组所有者是写。实现通常将 PTY 从设备的组所有者设置为 tty 组。把那些要对系统中所有活动端具有写权限的程序(如 wall(1)和 write(1))的设置组 ID 设置为 tty 组,因为在 PTY 从设备上 tty 组的写权限是被允许的。

#include <stdlib.h>
int grantpt(int fd);
int unlockpt(int fd);
/* 两个函数的返回值:若成功,返回 0;否则,返回 -1 */
char *ptsname(int fd);
/* 返回值:若成功,返回指向 PTY 从设备名的指针;否则,返回 NULL */

这几个函数中的 fd 参数都是与伪终端主设备关联的文件描述符。
为了更改从设备节点的权限,grantpt 可能需要 fork 并 exec 一个设置用户 ID 程序(如 Solaris 中是 /usr/lib/pt_chmod),因此调用者可能捕捉到行为未定义的 SIGCHLD 信号。
unlockpt 函数用于准予对 PTY 从设备的访问,从而允许应用程序打开该设备。阻止其他进程打开从设备后,建立该设备的应用程序有机会在使用主、从设备之前正确地初始化这些设备。
若给定了伪终端主设备的文件描述符,则可以用 ptsname 函数找到伪终端从设备的路径名(该名字可能存储在静态存储中,因此后续的调用可能会覆盖它)。
为了方便处理相关细节,下面自定义了两个函数:ptym_open 和 ptys_open。ptym_open 打开下一个可用的 PTY 主设备。调用者必须分配一个数组来存放主设备或从设备的名字,并且如果调用成功,相应的从设备名会通过 pts_name 返回。然后将这个名字传给用来打开该从设备的 ptys_open 函数。缓冲区的字节长度由 pts_namesz 传送,使得 ptym_open 不会复制比该缓冲区长的字符串。不过一般不直接调用这两个函数,而是由另一个自定义的函数 pty_fork 来调用,并且还会 fork 出一个子进程。一个进程调用 ptym_open 来打开一个主设备并得到从设备名,然后 fork 子进程。子进程在调用 setsid 建立新的会话后调用 ptys_open 打开从设备。这就是从设备如何成为子进程控制终端的过程。

#include <stdlib.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <termios.h>
#include <sys/ioctl.h>

int ptym_open(char *pts_name, int pts_namesz){
int fdm = posix_openpt(O_RDWR);
if(fdm < 0)
return -1;
if(grantpt(fdm) < 0) // grant access to slave
goto errout;
if(unlockpt(fdm) < 0) // clear slave's lock flag
goto errout;
char *ptr = ptsname(fdm); // get slave's name
if(ptr == NULL)
goto errout;
strncpy(pts_name, ptr, pts_namesz); // save name of slave
pts_name[pts_namesz - 1] = '\0';
return fdm;

errout:
int err = errno;
close(fdm);
errno = err;
return -1;
}

int ptys_open(char *pts_name){
return open(pts_name, O_RDWR);
}

pid_t pty_fork(int *ptyfdm, char *slave_name, int slave_namesz,
const struct termios *slave_termios,
const struct winsize *slave_winsize){
char pts_name[20];
int fdm = ptym_open(pts_name, sizeof(pts_name));
if(fdm < 0){
printf("can't open master pty: %s, error %d\n", pts_name, fdm);
exit(1);
}
if(slave_name != NULL){
strncpy(slave_name, pts_name, slave_namesz);
slave_name[slave_namesz-1] = '\0';
}
pid_t pid = fork();
if(pid < 0)
return -1;
if(pid > 0){ // parent
*ptyfdm = fdm; // save fd of master
return pid; // parent return pid of child
}
close(fdm); // all done with master in child
if(setsid() < 0){
printf("setsid error\n");
exit(1);
}
/* Linux/Solaris acquires controlling terminal on open(). */
int fds = ptys_open(pts_name);
if(fds < 0){
printf("can't open slave pty\n");
exit(1);
}
#if defined(BSD)
/* TIOCSCTTY is the BSD way to acquire a controlling terminal. */
if(ioctl(fds, TIOCSCTTY, (char *)0) < 0){
printf("TIOCSCTTY error\n");
exit(1);
}
#endif
if(slave_termios != NULL)
tcsetattr(fds, TCSANOW, slave_termios);
if(slave_winsize != NULL)
ioctl(fds, TIOCSWINSZ, slave_winsize);
/* Slave becomes stdin/stdout/stderr of child. */
dup2(fds, STDIN_FILENO);
dup2(fds, STDOUT_FILENO);
dup2(fds, STDERR_FILENO);
if(fds!=STDIN_FILENO && fds!=STDOUT_FILENO && fds!=STDERR_FILENO){
printf("dup2 error to stdin/stdout/stderr\n");
close(fds);
}
return 0; // child returns 0 just like fork()
}

在 pty_fork 函数中,PTY 主设备的文件描述符通过参数 ptrfdm 指针返回。如果参数 slave_name 不为空,则从设备名被存储在该指针指向的由调用者分配的存储区中。如果 slave_termios 不为空,则系统使用该指针引用的结构初始化从设备的终端行规程,否则系统会把从设备的 termios 结构设置成实现定义的初始状态。类似地,如果 slave_winsize 指针不为空,则按该指针引用的结构初始化从设备的窗口大小,否则 winsize 结构(见[url=http://aisxyz.iteye.com/admin/blogs/2423871]终端窗口大小和 termcap[/url])一般初始化为 0。我们还把从设备的文件描述符复制给了子进程的标准输入、标准输出和标准错误。这意味着以后不管子进程 exec 何种程序,它都具有同 PTY 从设备联系起来的这 3 个描述符。如果该函数成功执行,它会在子进程中返回 0,在父进程中返回子进程的进程 ID,否则返回 -1。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值