终端标识

尽管控制终端的名字在多数 UNIX 系统上都是 /dev/tty,不过为了可移植性,POSIX.1 还是提供了一个可用来确定控制终端名字的运行时函数 ctermid。

#include <stdio.h>
char *ctermid(char *ptr);
/* 返回值:若成功,返回指向终端名的指针;否则,返回指向空字符串的指针 */

如果参数 ptr 非空,则被认为是一个指向长度至少为 L_ctermid 字节(定义在 <stdio.h> 中)的数组的指针,终端名也会被存储在该数组中。若 ptr 是一个空指针,则由函数为数组(通常作为静态变量)分配空间。
ctermid 函数的实现大致如下。

#include <stdio.h>
#include <string.h>

static char ctermName[L_ctermid];

char *myCtermid(char *ptr){
if(ptr == NULL)
ptr = ctermName;
return strcpy(ptr, "/dev/tty");
}

isatty 和 ttyname 也是常用的终端函数。如果文件描述符引用一个终端设备,则 isatty 返回真。ttyname 返回的则是该文件描述符打开的终端设备的路径名。

#include <unistd.h>
int isatty(int fd); /* 返回值:若为终端设备,返回 1;否则,返回 0 */
char *ttyname(int fd); /* 返回值:指向终端路径名的指针;若出错,返回 NULL */

下面代码是 isatty 的实现。我们只使用了一个终端专用函数,并查看其返回值来确认。

#include <stdio.h>
#include <termios.h>

int myIsatty(int fd){
struct termios ts;
return (tcgetattr(fd, &ts) != -1); // true if no error (is a tty)
}

int main(void){
printf("fd 0: %s\n", myIsatty(0)? "tty": "not a tty");
printf("fd 1: %s\n", myIsatty(1)? "tty": "not a tty");
printf("fd 2: %s\n", myIsatty(2)? "tty": "not a tty");
return 0;
}

测试结果如下:

$ ./myIsatty.out
fd 0: tty
fd 1: tty
fd 2: tty
$ ./myIsatty.out </etc/passwd 2>/dev/null
fd 0: not a tty
fd 1: tty
fd 2: not a tty

ttyname 函数的实现就比较长,因为它要搜索所有设备表项来寻找匹配项。

#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
#include <string.h>
#include <dirent.h>
#include <sys/stat.h>
#include <unistd.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){
int 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;

struct devdir *ddp = malloc(sizeof(struct devdir));
if(ddp == NULL)
return;
if((ddp->d_name=strdup(dirname)) == NULL){
free(ddp);
return;
}

ddp->d_next = NULL;
if(head == NULL){
head = ddp;
tail = ddp;
}else{
tail->d_next = ddp;
tail = ddp;
}
}

static void cleanup(void){
struct devdir *nddp;
while(head != NULL){
nddp = head->d_next;
free(head->d_name);
free(head);
head = nddp;
}
tail = NULL;
}

static char *searchdir(char *dirname, struct stat *fdstat){
strcpy(pathname, dirname);
strcat(pathname, "/");
int len = strlen(pathname);
DIR *dirp = opendir(dirname);
struct dirent *itemp;
while((itemp=readdir(dirp)) != NULL){
strncpy(pathname+len, itemp->d_name, _POSIX_PATH_MAX-len);
/* skip aliases */
if(strcmp(pathname, "/dev/stdin")==0 || strcmp(pathname, "/dev/stdout")==0 || strcmp(pathname, "/dev/stderr")==0)
continue;
struct stat itemstat;
if(stat(pathname, &itemstat) < 0)
continue;
if(S_ISDIR(itemstat.st_mode)){
add(pathname);
continue;
}
if((itemstat.st_ino==fdstat->st_ino) && (itemstat.st_dev==fdstat->st_dev)){
closedir(dirp);
return pathname;
}
}
closedir(dirp);
return NULL;
}

char *myTtyname(int fd){
if(isatty(fd) == 0)
return NULL;
struct stat fdstat;
if(fstat(fd, &fdstat)<0 || S_ISCHR(fdstat.st_mode)==0)
return NULL;
char *name = searchdir("/dev", &fdstat);
if(name == NULL){
struct devdir *ddp;
for(ddp=head; ddp!=NULL; ddp=ddp->d_next)
if((name=searchdir(ddp->d_name, &fdstat)) != NULL)
break;
}
cleanup();
return name;
}

void test(int fd){
char *name = NULL;
if(isatty(fd)){
if((name=myTtyname(fd)) == NULL)
name = "undefined";
}else{
name = "not a tty";
}
printf("fd %d: %s\n", fd, name);
}

int main(void){
test(STDIN_FILENO);
test(1);
test(2);
exit(0);
}

这里通过读取 /dev 目录来寻找具有相同设备号和 i 节点编号的表项。另外,终端名可能在 /dev 的子目录中,所以也需要搜索。这里还跳过了少数几个可能会产生不正确结果或奇怪结果的目录:/dev/.、/dev/.. 和 /dev/fd。同时也跳过了一些别名:/dev/stdin、/dev/stdout 和 /dev/stderr,因为它们是 /dev/fd 目录中文件的符号链接。
运行结果如下:

# ./myTtyname.out </dev/console 2>/dev/null
fd 0: /dev/console
fd 1: /dev/pts/1
fd 2: not a tty
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值