今天偷看了一本书,书名叫做《unix/linux编程实践教程》。第三章讲的是文件读写内容,于是跟着他写了一个who命令。先记录于此,以备以后复习用。
在linux终端输入who命令,显示如下:
第一列是用户名
第二列是终端名
第三列是登陆时间
第四列是用户登陆地址
我用的是linux虚拟机,系统版本为ubuntu 10.10,IP地址为:10.201.18.2,显示当前共有两个用户登入到该虚拟机,其中ppxpp是在本地登陆,zhouhao是在远程登陆,其ip地址为10.201.18.5但是who命令是如何实现这一功能的呢?
这与linux系统下的一个叫做utmp的文件有关。
utmp文件记录了每个用户的登陆信息,由一系列utmp记录组成,而utmp记录的结构定义位于utmp.h中:
struct utmp { short ut_type; /* Type of record */ pid_t ut_pid; /* PID of login process */ char ut_line[UT_LINESIZE]; /* Device name of tty - "/dev/" */ char ut_id[4]; /* Terminal name suffix, or inittab(5) ID */ char ut_user[UT_NAMESIZE]; /* Username */ char ut_host[UT_HOSTSIZE]; /* Hostname for remote login, or kernel version for run-level messages */ struct exit_status ut_exit; /* Exit status of a process marked as DEAD_PROCESS; not used by Linux init(8) */ /* The ut_session and ut_tv fields must be the same size when compiled 32- and 64-bit. This allows data files and shared memory to be shared between 32- and 64-bit applications. */ #if __WORDSIZE == 64 && defined __WORDSIZE_COMPAT32 int32_t ut_session; /* Session ID (getsid(2)), used for windowing */ struct { int32_t tv_sec; /* Seconds */ int32_t tv_usec; /* Microseconds */ } ut_tv; /* Time entry was made */ #else long ut_session; /* Session ID */ struct timeval ut_tv; /* Time entry was made */ #endif int32_t ut_addr_v6[4]; /* Internet address of remote host; IPv4 address uses just ut_addr_v6[0] */ char __unused[20]; /* Reserved for future use */ };
编写程序从utmp文件中读取出该结构体内容,并打印:
//打印struct utmp的内容void showUtmp(struct utmp* pUtmp){ printf("%-13.13s", pUtmp->ut_user); printf("%-8.8s", pUtmp->ut_line); printf("%-14.10ld", pUtmp->ut_tv.tv_sec); printf("(%s)", pUtmp->ut_host); printf("\n");}
int main(int ac, char **avs){ int utmpfd;//read utmp file struct utmp curtUsr; //一个记录的长度 int len = sizeof(struct utmp); //在我的系统中,utmp文件位于/var/run/utmp char *fPath = "/var/run/utmp"; if(-1 == (utmpfd = open(fPath, O_RDONLY))) { perror("open utmp file failed!\n"); exit(-1); } while( read(utmpfd, &curtUsr, len) ==len) { showUtmp(&curtUsr); } close(utmpfd); return 0;}
使用gcc编译:gcc -o m_who who.c,运行:
第一列为用户名,第二列为终端号,第三列为登陆时间,第四列为用户登陆地址
咋一看,打印的都是神马玩意!!
第一:显然比标准的who命令多显示了LOGIN,reboot和runlevel。他们到底是何方神圣暂且不论(其实我现在也还不知道,慢慢学习吧),反正与标准的who命令就是不同,说明我们的程序还有某个地方有疏漏。
第二:时间显示也与who命令不一样
好吧,继续改进吧,先解决第一个问题。
使用man utmp命令查看utmp相关信息,发现有下述描述:
很显然,我们需要使用ut_type字段判断当前的记录属于什么类型,我们在showUtmp函数中增加打印ut_type的语句:
printf("%-5d", pUtmp->ut_type);
结果显示如下:
可知,为了正确显示当前登录的用户列表,我们只需要打印出ut_type == USER_PROCESS的记录就可以了,修改main函数如下:
int main(int ac, char **avs){ int utmpfd;//read utmp file struct utmp curtUsr; //一个记录的长度 int len = sizeof(struct utmp); //在我的系统中,utmp文件位于/var/run/utmp char *fPath = "/var/run/utmp"; if(-1 == (utmpfd = open(fPath, O_RDONLY))) { perror("open utmp file failed!\n"); exit(-1); } while( read(utmpfd, &curtUsr, len) ==len) { //增加判断条件 if(curtUsr.ut_type == USER_PROCESS) showUtmp(&curtUsr); } close(utmpfd); return 0;}
编译运行如下:
嗯,不错不错,看起来更像是标准who命令的打印结果了。着手解决时间显示不正确的问题。
我们在打印时间的时候直接使用
printf("%-14.10ld", pUtmp->ut_tv.tv_sec);语句。这个ut_tv,tv_sec是什么呢?
从前文可知,ut_tv是一个struct timeval结构体类型,其定义如下:
struct timeval { time_t tv_sec; //表示秒 suseconds_t tv_usec; //表示微妙};
strct timeval表示一个时间点,该时间点从1970年1月1日0点0分0秒开始计时。若linux系统时间为1970年1月1日0点0分1秒,
则linux内核返回的时间时间可以用该类型变量time表示,且time.tv_sec = 1,time.tv_usec = 0。
上面打印结果zhouhao那一栏的时间 1364285795 即表示zhouhao这个用户登陆系统时,距离1970年1月1日0点0分0秒已经过去1364285795秒了。
我们可以使用其他函数将这一串数字转变成我们能够看懂的文字表示方式,修改showUtmp()函数如下:
//打印struct utmp的内容void showUtmp(struct utmp* pUtmp){ printf("%-13.13s", pUtmp->ut_user); printf("%-8.8s", pUtmp->ut_line); //显示时间 char* time_str = (char*)malloc(32 * sizeof(char)); strftime(time_str, 32 * sizeof(char), "%Y-%m-%d %H:%M", localtime(&(pUtmp->ut_tv.tv_sec))); printf("%-20s", time_str); printf("(%s)", pUtmp->ut_host); printf("\n"); //释放内存 free(time_str);}
编译运行,结果如下:
至此,一个自己的who命令就算是完成了,至于其他拓展参数,还是有时间在写吧。。。。