一 介绍
who命令会显示系统中活动用户的情况。
二 关于命令who
Unix系统中,几乎所有的命令都是人为编写的程序。在Unix系统中增加新命令方法:将程序的可执行文件放到以下任意一个目录:
/bin
/usr/bin
/usr/local/bin
三 问题1:who命令做些什么
who命令的输出格式如下:
用户名 终端名 登录时间 用户登录地址
可通过man命令查看联机帮助:
四 问题2:who命令是如何工作的
已登录用户的信息放在文件/var/adm/utmp
中,who通过读改文件获得信息。
使用带有选项-k的man命令可以根据关键字搜索联机帮助,例如:man -k utmp
可以得出:who要读取utmp这个文件,进一步,utmp文件里保存的是结构数组,数组元素是utmp类型的结构,可以在utmp.h
中找到定义,该文件在/usr/include
目录中。
在CentOS6.5中,结构体定义在/usr/include/bits/utmp.h
头文件中:
- ut_user数组:保存登录名
- ut_line数组:保存设备名
- ut_hose数组:保存用户用于登录的远程计算机名
由此可见,who的工作流程可如下图表示:
五 如何编写who
1.如何从文件中读取数据结构
(1)打开一个文件:open
open()系统调用在进程和文件之间建立一条连接,这个连接被称为文件描述符,基本用法如下:
系统调用 | open |
---|---|
目标 | 打开一个文件 |
头文件 | <fcntl.h> |
函数原型 | int fd=open(char* name,int how) |
name | 文件名 |
how | O_RDONLY,O_WRONLY,O_RDWR |
返回值 | -1:遇到错误,int 成功返回 |
Unix并不禁止一个文件同时被多个进程访问。
如果文件被顺利打开,内核会返回一个正整数值,叫做文件描述符。如果同时打开好几个文件,它们所对应的文件描述符是不同的,如果将一个文件打开多次,对应的文件描述符也不相同。必须通过文件描述符对文件进行操作。
(2)从文件读取数据:read
用法如下:
系统调用 | read |
---|---|
目标 | 将数据读到缓冲区 |
头文件 | <unistd.h> |
函数原型 | ssize_t numread=read(int fd,void* buf,size_t qty) |
fd | 文件描述符 |
buf | 用于存放数据的目的缓冲区 |
qty | 要读取的字节数 |
返回值 | -1:遇到错误,numread 成功读取 |
(3)关闭文件:close
用法如下:
系统调用 | close |
---|---|
目标 | 关闭一个文件 |
头文件 | <unistd.h> |
函数原型 | int result=close(int fd) |
fd | 文件描述符 |
返回值 | -1:遇到错误,0: 成功关闭 |
(4)编写who1.c
/* who1.c - a first version of the who program
* open,read UTMP file,and show results
*/
#include<fcntl.h>
#include<utmp.h>
#include<stdio.h>
#include<unistd.h>
#define SHOWHOST
void show_info(struct utmp* utbufp);
int main()
{
struct utmp current_record;
int utmpfd;
int reclen=sizeof(current_record);
if((utmpfd=open(UTMP_FILE,O_RDONLY))==-1)
{
perror(UTMP_FILE); /*UTMP_FILE is in utmp.h*/
exit(1);
}
while(read(utmpfd,¤t_record,reclen)==reclen)
show_info(¤t_record);
close(utmpfd);
return 0;
}
void show_info(struct utmp* utbufp)
{
printf("%-8.8s",utbufp->ut_name);
printf(" ");
printf("%-8.8s",utbufp->ut_line);
printf(" ");
printf("%10ld",utbufp->ut_time);
printf(" ");
#ifndef SHOWHOST
printf("(%s)",utbufp->ut_host);
#endif
printf("\n");
}
运行结果如下:
系统who命令结果如下:
可见,相比系统who命令,有两处需要完善:
- 清除空白记录
- 正确显示登录时间
(5)编写who2.c
1)清除空白记录
实际上utmp包含所有终端的信息,甚至那些尚未被用到的终端信息。
在/usr/include/bits/utmp.h
文件中有如下记录:
可以看出,utmp结构中有一个成员ut_type,当其值为7(USER_PROCESS)时,表示这是一个已经登录的用户名。根据这一点,进行如下修改:
void show_info(struct utmp* utbufp)
{
if(utbufp->ut_type!=USER_PROCESS)
return;
printf("%-8.8s",utbufp->ut_name);
..........................
}
2)以可读的方式显示登录时间
Unix存储时间的方式:time_t数据类型。时间是用一个整数表示,它的值是从1970.01.01 00:00:00开始所经过的秒数。
将time_t显示出来::ctime
在命令行中输入:
man -k time | grep transform
很多记录提到了ctime,ctime将表示时间的整数值转换成日常所用的时间形式
可见,ctime()函数要输入一个指向time_t的指针,返回如下格式的字符串:
Wed Jun 30 21:49:08 1993.\n
对上述字符串进行处理,即可得到需要的日期。
由此得到who的第二个版本:
/* who2.c - a first version of the who program
* open,read UTMP file,and show results
*/
#include<fcntl.h>
#include<utmp.h>
#include<stdio.h>
#include<unistd.h>
#include<time.h>
/*#define SHOWHOST*/
void show_info(struct utmp* utbufp);
void showtime(long timeval);
int main()
{
struct utmp utbuf;
int utmpfd;
if((utmpfd=open(UTMP_FILE,O_RDONLY))==-1)
{
perror(UTMP_FILE); /*UTMP_FILE is in utmp.h*/
exit(1);
}
while(read(utmpfd,&utbuf,sizeof(utbuf))==sizeof(utbuf))
show_info(&utbuf);
close(utmpfd);
return 0;
}
void show_info(struct utmp* utbufp)
{
if(utbufp->ut_type!=USER_PROCESS)
return;
printf("%-8.8s",utbufp->ut_name);
printf(" ");
printf("%-8.8s",utbufp->ut_line);
printf(" ");
showtime(utbufp->ut_time);
#ifdef SHOWHOST
printf("(%s)",utbufp->ut_host);
#endif
printf("\n");
}
void showtime(long timeval)
{
char *cp;
cp=ctime(&timeval);
printf("%12.12s",cp+4);
}
编译后执行结果如下:
可见二者基本相似。