UTMP文件详解

utmp 文件保存了系统的登陆信息。比如我们可以通过访问这个文件,看看当前都有哪些用户正在使用系统。 这个文件的内容是由一系列的 utmp 结构体组成的。没错,这里说的结构体就是 C 语言的,在生成的时候,通过 write 一股脑写进去的。 现在我们可以建立关于 utmp 的宏观认识: [utmp]、[utmp]、[utmp]、[utmp]、…… 现在,我们十分想知道这些内容:

1. utmp 声明在何处

2. utmp 声明内容是什么

3. 系统信息是多种多样的,它们都储存为 utmp 结构体。那么,utmp 结构体是怎么表征不同信息的?

utmp 声明在何处

在这里 Maxiee 要推荐一个 Linux 指令: locate ,它会对检索系统里的所有文件,建立一个数据库,当我们要查找系统中的文件时,例如这里想搜索 utmp.h ,我们只需要输入 locate utmp.h ,locate 会一瞬间就帮我们找出它存在的目录! Maxiee 在系统里找到了俩: /usr/include/x86_64-linux-gnu/bits/utmp.h 包含结构体的声明,以及一些宏定义。 /usr/include/utmp.h 首先包含了上面文件,并额外声明了一系列函数,用来操作 utmp 文件。 在前者的头文件中,有这么一句:“Never include <bits/utmp.h> directly; use <utmp.h> instead.” 在实际使用中,我们始终使用后者。 所以后者便是我们要找的,我们不仅找到了 utmp 结构体的声明,还找到了一个能够操作 utmp 的库,可谓收获颇丰。

utmp 声明是什么

[code language="cpp"]
/* 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]; /* 保留供未来使用 */
}
[/code]

utmp如何表征不同的信息?

从中我们注意到,整数型的 ut_type 表示登陆类型,它决定了这个结构体是表示的何种信息。它的取值范围也位于 utmp.h ,有一串宏定义组成:

[code language="cpp"]
/* Values for the ut_type' field of astruct utmp'. /
#define EMPTY 0 /
无效记录信息 */

#define RUN_LVL 1 /* 系统的 runlevel. /
#define BOOT_TIME 2 /
系统启动的时刻 /
#define NEW_TIME 3 /
Time after system clock changed. /
#define OLD_TIME 4 /
Time when system clock changed. */

#define INIT_PROCESS 5 /* Process spawned by the init process. /
#define LOGIN_PROCESS 6 /
Session leader of a logged in user. /
#define USER_PROCESS 7 /
Normal process. /
#define DEAD_PROCESS 8 /
Terminated process. */

#define ACCOUNTING 9

/* Old Linux name for the EMPTY type. */
#define UT_UNKNOWN EMPTY
[/code]

对于 who 指令而言,只需要 USER_PROCESS ,来检查都有哪些用户在使用系统。详见《自己动手编写Linux指令——who》。

读取utmp文件内容

现在我们可以写一个程序,试着来读取 utmp 文件的内容,并按照结构体声明,结构化地显示出来。

[code language="cpp"]
#include
#include
#include
#include
#include

#define SHOWHOST

void showtime(long);

void show_info( struct utmp * utbufp, int i);

int main()
{
struct utmp current_record;
int utmpfd;
int reclen = sizeof(current_record);
int i=0;

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, i++);
close(utmpfd);
return 0;

}

show_info( struct utmp * utbufp, int i)
{
printf("Struct %d:n",i);
printf("tut_type=%dn",utbufp->ut_type);
printf("tut_pid=%dn",utbufp->ut_pid);
printf("tut_line=%sn",utbufp->ut_line);
printf("tut_id=%sn",utbufp->ut_id);
printf("tut_user=%sn",utbufp->ut_user);
printf("tut_host=%sn",utbufp->ut_host);
printf("tut_exit->e_termination=%dn",utbufp->ut_exit.e_termination);
printf("tut_exit->e_exit=%dn",utbufp->ut_exit.e_exit);
showtime( utbufp->ut_time);
}

void showtime(long timeval)
{
char *cp;
cp = ctime(&timeval);
printf("t%12.12sn",cp+4);
}
[/code]

编译、运行后,可以看到程序返回结果:

maxiee@Maxiee ~/code/linuxprograming $ ./utmp_read 
Struct 0:
    ut_type=2
    ut_pid=0
    ut_line=~
    ut_id=~~
    ut_user=reboot
    ut_host=
    ut_exit->e_termination=0
    ut_exit->e_exit=0
    Apr 11 09:08
Struct 1:
    ut_type=8
    ut_pid=15654
    ut_line=
    ut_id=rc
    ut_user=
    ut_host=
    ut_exit->e_termination=0
    ut_exit->e_exit=0
    Apr 11 09:08
Struct 2:
    ut_type=1
    ut_pid=20019
    ut_line=~
    ut_id=~~
    ut_user=runlevel
    ut_host=
    ut_exit->e_termination=0
    ut_exit->e_exit=0
    Apr 11 09:08
Struct 3:
    ut_type=8
    ut_pid=16074
    ut_line=
    ut_id=l3
    ut_user=
    ut_host=
    ut_exit->e_termination=0
    ut_exit->e_exit=0
    Apr 11 09:08
Struct 4:
    ut_type=6
    ut_pid=16298
    ut_line=tty2
    ut_id=c2
    ut_user=LOGIN
    ut_host=
    ut_exit->e_termination=0
    ut_exit->e_exit=0
    Apr 11 09:08
Struct 5:
    ut_type=6
    ut_pid=16302
    ut_line=tty6
    ut_id=c6
    ut_user=LOGIN
    ut_host=
    ut_exit->e_termination=0
    ut_exit->e_exit=0
    Apr 11 09:08
Struct 6:
    ut_type=6
    ut_pid=16301
    ut_line=tty5
    ut_id=c5
    ut_user=LOGIN
    ut_host=
    ut_exit->e_termination=0
    ut_exit->e_exit=0
    Apr 11 09:08
Struct 7:
    ut_type=6
    ut_pid=16300
    ut_line=tty4
    ut_id=c4
    ut_user=LOGIN
    ut_host=
    ut_exit->e_termination=0
    ut_exit->e_exit=0
    Apr 11 09:08
Struct 8:
    ut_type=6
    ut_pid=16299
    ut_line=tty3
    ut_id=c3
    ut_user=LOGIN
    ut_host=
    ut_exit->e_termination=0
    ut_exit->e_exit=0
    Apr 11 09:08
Struct 9:
    ut_type=6
    ut_pid=16297
    ut_line=tty1
    ut_id=c1
    ut_user=LOGIN
    ut_host=
    ut_exit->e_termination=0
    ut_exit->e_exit=0
    Apr 11 09:08
Struct 10:
    ut_type=7
    ut_pid=16395
    ut_line=:0.0
    ut_id=:0.0maxiee
    ut_user=maxiee
    ut_host=
    ut_exit->e_termination=0
    ut_exit->e_exit=0
    Apr 11 09:09
Struct 11:
    ut_type=7
    ut_pid=16883
    ut_line=pts/0
    ut_id=/0
    ut_user=maxiee
    ut_host=:0.0
    ut_exit->e_termination=0
    ut_exit->e_exit=0
    Apr 11 11:08

从中我们可以看出,utmp 结构体中的这么多变量,并不是在每一种信息中都能全部用得上。对于一种特定的信息,有的变量用不上,就那么空着。

总结

用一个结构体来表征多种相似的信息类型,在结构体变量中保存信息,这点是很值得参考的。 想到 amule 和 emule 的代码中,有一种基于 TAG 的保存数据的形式,与这里的结构体是相似的。它克服了上面的变量用不上,就空着的问题,而是根据信息的类型( ut_type ),来动态地创建随后的变量,不需要的就不创建了。