ttyname给定指定文件描述符上打开的终端设备的路经名,在APUE(中文第三版P564-565)中有一个非递归的实现,花了两个多小时的时间搞懂,现在把关键的代码写上注释。在最后会有一个本人改写的递归实现。
源代码:
#include <sys/stat.h>
#include <dirent.h>
#include <limits.h>
#include <string.h>
#include <termios.h>
#include <unistd.h>
#include <stdlib.h>
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);
/*
* 跳过 ., .., 和 /dev/fd 这些目录
*/
if ((dirname[len-1] == ’.’) && (dirname[len-2] == ’/’||
(dirname[len-2] == ’.’ && dirname[len-3] == ’/’)))
return;
if (strcmp(dirname, "/dev/fd") == 0)
return;
if ((ddp = malloc(sizeof(struct devdir))) == NULL)
return;
if ((ddp->d_name = strdup(dirname)) == 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);
}
add这个函数的作用是建立单向链表,链表中有两个指针,一个指向下一个节点,一个指向目录名所在的内存区域,这块内存是由strdup调用malloc建立的,每当遇到一个目录,searchdir就会调用add将这个目录名保存在节点中d_name指针所指的内存中。
ttyname这一部分本人感到比较难以理解,开始不清楚怎样做到遍历/dev的。其实第一次调用searchdir找出了/dev下的所有目录,然后进行while循环,就是在这个循环中,链表长度会发生变化,而不是第一次searchdir后的长度,感觉这里正是整个程序最关键的技巧。比如,第一次的链表为:/dev/dir1–/dev/dir2–/dev/dir3–NULL,然后从dir1开始查找,如果遇到目录,就会变成/dev/dir1–/dev/dir/2–/dev/dir3–/dev/dir1/dir11/–/dev/dir1/dir12–/dev/dir1/dir13–NULL,依次类推,就是逐个节点查找,遇到目录就在在尾部插入节点,所以调用searchdir的次数就不是确定的,与整个/dev下的目录数有关,开始的时候我就以为调用的次数就是第一次调用searchdir建立链表的节点树,可是这样做不到遍历整个文件树啊,就卡住了。
下面是自己写的递归查找的版本:
#include <unistd.h>
#include <stdio.h>
#include <sys/stat.h>
#include <limits.h>
#include <dirent.h>
#include <string.h>
#include <stdlib.h>
static char pathname[_POSIX_PATH_MAX + 1];
char *
searchdir( char *dirname, struct stat *fdstatp)
{
int len;
DIR *dp;
struct dirent *dirp;
struct stat statbuf;
strcpy(pathname,dirname);
strcat(pathname,"/");
len = strlen(pathname);
if((dp = opendir(dirname)) == NULL)
return NULL;
while((dirp = readdir(dp)) != NULL){
strncpy(pathname +len,dirp->d_name,_POSIX_PATH_MAX - len);
if ( strcmp(pathname + len,".") == 0 || strcmp(pathname+len+1,".") == 0)
continue;
if (lstat(pathname,&statbuf) < 0)
return NULL;
if (S_ISDIR(statbuf.st_mode)){
if (searchdir(pathname,fdstatp) != NULL)
return pathname;
} else if ( S_ISCHR(statbuf.st_mode)){
if (statbuf.st_dev == fdstatp->st_dev &&
statbuf.st_ino == fdstatp->st_ino ){
closedir(dp);
return pathname;
}
}
}
closedir(dp);
return NULL;
}
char *
my_ttyname( int fd)
{
struct stat statbuf;
char *termpathname;
if (isatty(fd) == 0)
return NULL;
if (fstat(fd,&statbuf) < 0)
return NULL;
termpathname = searchdir("/dev",&statbuf);
return termpathname;
}
int
main(void)
{
char *name;
if (isatty(0)){
name = my_ttyname(0);
if (name == NULL)
name = "undefined";
}else
name = "not a tty";
printf("fd=0: %s\n",name);
if (isatty(1)){
name = my_ttyname(1);
if (name == NULL)
name = "undefined";
}else
name = "not a tty";
printf("fd=1: %s\n",name);
if (isatty(2)){
name = my_ttyname(2);
if (name == NULL)
name = "undefined";
}else
name = "not a tty";
printf("fd=2: %s\n",name);
return 0;
}