自己动手编写Linux指令——who

当我们在使用 Linux 系统时,有时我们想要知道:

  1. 系统是什么时候启动的
  2. 都有哪些用户登陆进来了
  3. 当前的 run level

who 指令可以给我们想要的答案,它的功能是显示都有谁在使用系统

例如,当 Maxiee 想要知道都有哪些用户登陆进来:

maxiee@debian:~$ who
maxiee   pts/0        2014-03-12 13:04 (:0.0)

从结果中可以看到,目前系统中只有 Maxiee 自己,且是于 2014-03-12 13:04 登陆到系统中的。(:0.0)  是登陆地址, pts/0 是终端名。

utmp

要说 who 指令的实现原理,就不得不说 utmp 这个文件。

utmp 文件保存了关于用户登陆的历史信息。who 指令的工作原理就是访问这个文件,将其中的记录解析出来,将访问结果美观、整齐地输出。

utmp 文件如何保存条目?它采用的是 C 语言里的结构体,一次将一个结构体写入到文件中。所以上面说的“解析”,就是指到时候再把这些结构体一个一个读出来

另外要注意的是,在 utmp 中保存的结构体中,跟登陆有关的只是其中一部分,还有一些其他记录,例如记录启动时间信息、用户登出信息,“解析”的另一层含义是从读到的结构体中挑选与登陆信息有关的

这个结构体在 <utmp.h> 中声明,要注意的是,系统中包含两个 utmp.h :

/usr/include/x86_64-linux-gnu/bits/utmp.h 包含结构体的声明,以及一些宏定义。

/usr/include/utmp.h 首先包含了上面文件,并声明了一系列函数,用来操作 utmp 文件。

在前者的头文件中,有这么一句:“Never include <bits/utmp.h> directly; use <utmp.h> instead.” 在实际使用中,我们始终使用后者。

现在来看结构体声明:

[c]

/* The structure describing an entry in the user accounting database.  /
struct utmp
{
short int ut_type;        /
登陆类型  /
pid_t ut_pid;            /
登陆进程的PID  /
char ut_line[UT_LINESIZE];    /
终端类型.  /
char ut_id[4];        /
Inittab ID.  /
char ut_user[UT_NAMESIZE];    /
用户名.  /
char ut_host[UT_HOSTSIZE];    /
登陆主机名  /
struct exit_status ut_exit;    /
Exit status of a process marked
as DEAD_PROCESS.  /
/
ut_session 和 ut_tv 在 32 位和 64 位编译必须大小一致。
这样数据文件和共享内存就能在 32 位和 64 位程序间实现共享*/
//64位系统情况
#if __WORDSIZE == 64 && defined __WORDSIZE_COMPAT32
int32_t ut_session;        /* Session ID, used for windowing.  */
struct
{
int32_t tv_sec;        /* Seconds.  */
int32_t tv_usec;        /* Microseconds.  */
} ut_tv;            /* 登陆时间  */
//32位系统情况
#else
long int ut_session;        /* Session ID, used for windowing.  */
struct timeval ut_tv;        /* 登录时间  */
#endif
int32_t ut_addr_v6[4];    /* 登陆网络地址  */
char __unused[20];        /* 保留供未来使用  */
}
[/c]

其中,ut_type 表示登陆类型,根据每个单位ut_type的不同,其用途也不同。比如有的是who需要的记录用户登陆信息的,还有的是记录启动时间信息、用户登出信息的(这些who用不到)。

ut_type 的值为7,其宏定义的是 USER_PROCESS ,是我们需要的。

在结构体中,我们需要的成员:

  • ut_user 用户名
  • ut_line 终端类型
  • ut_host 主机名
  • ut_tv 登陆时间

有了这些,就足够显示出一个用户的登陆信息了。

至此,我们就可以动手编写自己的 who 指令了。

mywho.c

[c]
#include<stdio.h>
#include<utmp.h>
#include<fcntl.h>
#include<unistd.h>
#include<time.h>

#define SHOWHOST

//还记得utmp结构体里的时间成员ut_time?
//它表示的是从1970年一月一日到现在经过的秒数
//而我们需要的是生活中的时间格式
//声明showtime完成这项工作
void showtime(long);

//每读出一条结构体utmp
//就交给这个函数
//判断类型,输出感兴趣的内容
void show_info( struct utmp * utbufp);

//主函数
int main()
{
struct utmp current_record;
int utmpfd; //utmp文件的文件描述符
int reclen = sizeof(current_record);//获得结构体大小

//打开utmp文件
//UTMP_FILE在utmp.h里,记录了utmp文件保存在哪里
if( (utmpfd = open(UTMP_FILE,O_RDONLY)) == -1){
    perror(UTMP_FILE);
    exit(1);
}

//每次读一个结构体,直到文件结尾
while( read(utmpfd,&amp;current_record,reclen ) == reclen)
    show_info(&amp;current_record);
close(utmpfd);
return 0;

}

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);
printf(" ");
//是否显示主机名
#ifdef SHOWHOST
if( utbufp->ut_host[0] != '\0' )
printf("(%s)",utbufp->ut_host);
#endif
printf("n");
}

void showtime(long timeval)
{
char *cp;
//ctime函数将秒数格式化
cp = ctime(&timeval);
printf("%12.12s",cp+4);
}

[/c]

参考文献:

Linux 'who' command with examples

man 5 utmp