分类: LINUX
2012-03-01 21:20:46
++++++APUE读书笔记-18终端输入输出-09终端标识++++++
9、终端标识
================================================
根据以前,大多数版本的UNIX 系统上面的控制终端的名称都是/dev/tty。POSIX.1提供了一个运行时的函数,我们可以通过调用这个函数来确定控制终端的名字。
#include
char *ctermid(char *ptr);
返回:如果成功,返回指向控制终端名字的指针;如果错误返回空的字符串。
如果ptr非空,那么会假定它指向一个至少有L_ctermid字节的数组,而进程的控制终端的名字就存放在那个数组中。常数L_ctermid定义在
对于这两种情况,数组的起始地址都会被做为函数的返回值返回。由于大多数的UNIX系统使用/dev/tty作为控制终端的名字,所以这个函数只是为了便于在其它操作系统可移植的一种手段。
本书描述的所有四个平台,都会在我们调用ctermid的时候返回"dev/tty"的。
ctermid函数的例子
下面的代码展示了POSIX.1的ctermid函数的实现
需要注意的是我们不会保护调用者的缓存越界,因为我们无法确定它的大小。
POSIX.1中的ctermid函数的实现
#include
#include
static char ctermid_name[L_ctermid];
char * ctermid(char *str)
{
if (str == NULL)
str = ctermid_name;
return(strcpy(str, "/dev/tty")); /* strcpy() returns str */
}
对于UNIX系统有两个值得知道的函数:一个是isatty,这个函数在如果一个文件描述符号引用的是终端设备的时候返回true;一个是ttyname,这个函数返回打开的文件描述符号的终端设备路径名称。
#include
int isatty(int filedes);
返回:如果是终端设备返回1,如果不是就返回0。
char *ttyname(int filedes);
返回:指向终端的路径名称,如果错误返回NULL。
isatty函数的例子
函数isatty的时间非常简单,如下面代码所示。我们只是简单地执行了一个终端特定的函数(这个函数如果成功执行的时候并不会改变任何东西),然后通过查看返回值来确定。
函数isatty的POSIX.1实现
#include
int isatty(int fd)
{
struct termios ts;
return(tcgetattr(fd, &ts) != -1); /* true if no error (is a tty) */
}
我们通过如下的代码来测试我们上面写的isatty函数,
测试isatty函数
#include "apue.h"
int main(void)
{
printf("fd 0: %s\n", isatty(0) ? "tty" : "not a tty");
printf("fd 1: %s\n", isatty(1) ? "tty" : "not a tty");
printf("fd 2: %s\n", isatty(2) ? "tty" : "not a tty");
exit(0);
}
当我们运行上面代码对应的程序的时候,我们会得到如下的输出:
$ ./a.out
fd 0: tty
fd 1: tty
fd 2: tty
$ ./a.out /dev/null
fd 0: not a tty
fd 1: tty
fd 2: not a tty
函数ttyname的例子
如下面代码所示的,其实ttyname函数很长,因为我们需要搜索所有的设备节点,来查找一个匹配的。
POSIX.1的ttyname函数的实现
#include
#include
#include
#include
#include
#include
#include
struct devdir {
struct devdir *d_next;
char *d_name;
};
static struct devdir *head;
static struct devdir *tail;
static char pathname[_POSIX_PATH_MAX + 1];
static void add(char *dirname)
{
struct devdir *ddp;
int len;
len = strlen(dirname);
/*
* Skip ., .., and /dev/fd.
*/
if ((dirname[len-1] == '.') && (dirname[len-2] == '/' ||
(dirname[len-2] == '.' && dirname[len-3] == '/')))
return;
if (strcmp(dirname, "/dev/fd") == 0)
return;
ddp = malloc(sizeof(struct devdir));
if (ddp == NULL)
return;
ddp->d_name = strdup(dirname);
if (ddp->d_name == NULL) {
free(ddp);
return;
}
ddp->d_next = NULL;
if (tail == NULL) {
head = ddp;
tail = ddp;
} else {
tail->d_next = ddp;
tail = ddp;
}
}
static void cleanup(void)
{
struct devdir *ddp, *nddp;
ddp = head;
while (ddp != NULL) {
nddp = ddp->d_next;
free(ddp->d_name);
free(ddp);
ddp = nddp;
}
head = NULL;
tail = NULL;
}
static char * searchdir(char *dirname, struct stat *fdstatp)
{
struct stat devstat;
DIR *dp;
int devlen;
struct dirent *dirp;
strcpy(pathname, dirname);
if ((dp = opendir(dirname)) == NULL)
return(NULL);
strcat(pathname, "/");
devlen = strlen(pathname);
while ((dirp = readdir(dp)) != NULL) {
strncpy(pathname + devlen, dirp->d_name,
_POSIX_PATH_MAX - devlen);
/*
* Skip aliases.
*/
if (strcmp(pathname, "/dev/stdin") == 0 ||
strcmp(pathname, "/dev/stdout") == 0 ||
strcmp(pathname, "/dev/stderr") == 0)
continue;
if (stat(pathname, &devstat) < 0)
continue;
if (S_ISDIR(devstat.st_mode)) {
add(pathname);
continue;
}
if (devstat.st_ino == fdstatp->st_ino &&
devstat.st_dev == fdstatp->st_dev) { /* found a match */
closedir(dp);
return(pathname);
}
}
closedir(dp);
return(NULL);
}
char * ttyname(int fd)
{
struct stat fdstat;
struct devdir *ddp;
char *rval;
if (isatty(fd) == 0)
return(NULL);
if (fstat(fd, &fdstat) < 0)
return(NULL);
if (S_ISCHR(fdstat.st_mode) == 0)
return(NULL);
rval = searchdir("/dev", &fdstat);
if (rval == NULL) {
for (ddp = head; ddp != NULL; ddp = ddp->d_next)
if ((rval = searchdir(ddp->d_name, &fdstat)) != NULL)
break;
}
cleanup();
return(rval);
}
主要的技术是读取/dev目录,查找一个具有同样设备号码以及i-node号码的文件。前面说过,每个文件系统都有一个唯一的设备号码(stat结构的st_dev成员),每个文件系统的目录项都有一个唯一的i-node号(stat结构的st_ino成员)。我们假定在这个函数中,我们遇到一个匹配的设备号码以及i-node号码的时候,我们定位到了想要的目录项。我们也会检查两者的st_rdev成员是否匹配(也就是终端设备的主、次设备号码)以及目录项是不是一个字符设备文件。但是,因为我们已经确定了文件描述符号参数是一个终端设备和字符设备文件,并且设备号码和i-node号码在UNIX系统中是唯一的,所以,没有必要进行额外的检查。
我们的终端可能在子目录/dev下面,所以我们搜索其中的所有文件。我们会跳过一些目录,因为它门会产生不正确地输出,这些目录是:/dev/.,/dev/..,/dev/fd。我们也会跳过/dev/stdin,/dev/stdout,和/dev/stderr,因为它们是指向/dev/fd中的一个目录项的链接。
我们可以通过如下的代码来检查上面实现的函数:
测试ttyname函数的代码
#include "apue.h"
int main(void)
{
char *name;
if (isatty(0)) {
name = ttyname(0);
if (name == NULL)
name = "undefined";
} else {
name = "not a tty";
}
printf("fd 0: %s\n", name);
if (isatty(1)) {
name = ttyname(1);
if (name == NULL)
name = "undefined";
} else {
name = "not a tty";
}
printf("fd 1: %s\n", name);
if (isatty(2)) {
name = ttyname(2);
if (name == NULL)
name = "undefined";
} else {
name = "not a tty";
}
printf("fd 2: %s\n", name);
exit(0);
}
运行上面的程序,我们得到如下的输出:
$ ./a.out < /dev/console 2> /dev/null
fd 0: /dev/console
fd 1: /dev/ttyp3
fd 2: not a tty
参考: