自己动手编写last指令

这是《unix/linux编程实践教程》第二章的一道习题。通过联机手册,我们可以看到last的功能如下: show listing of last logged in users,将新登录的用户做成一张表显示出来。

其显示效果(部分)如下:

可以发现:

1.其显示信息相当全,特别是登录日期,登录登出时间,连登录时长也算出来了。并且,对于最新的这一次上机,并未登出,还显示“still logged in”,很全面。

2.重启也被记录在内,只是被当作一个特殊用户“reboot”的登录= =

如何来编写这个程序呢?我们跟着联机帮助一点一点来分析看:

a) 不同于who利用了utmp文件,last利用了wtmp文件。

b) utmpwtmp在一个文档页里,上面说wtmputmp结构十分相似。对于wtmp,空用户名代(貌似应该是空主机名)表对应终端的登出;终端名为~代表着关机或者重启;终端名为| /} 分别代表着登录登出时间。

3.先不管wtmp的特殊要求,既然他也是用utmp结构体定义的,我们不妨把who1的代码修改一下,先显示wtmp看看。代码附在文末,程序1-1。(只要把who1.copen路径改了就行……)

4.效果(部分)如下:

5.通过对比可以发现一些问题:

    1. 他的是降序我的是升序= =,这个可以通过倒着读utmp结构体解决
    2. 我的用户名全是maxiee,没有rebootshutdown,这是由于在who1.c程序里的show_info函数中,我们筛选了 ut_typeUSER_PROCESS,而在wtmpreboot shutdown都不是USER_PROCESS。去掉这个筛选,再次运行,结果(部分)如下:

图中所示这个reboot原来指的是我开机啊= =

    1. 我们可以发现,多了LOGINrunlevel,这些先留着也不错~现在最重要的是,每个终端登入和登出时间是分开的,我们要把它们合并起来,并算出登录时长。

怎么样实现呢?有两个想法。一个是,每次读到一条终端登录的记录,都接着遍历一遍剩下的记录,找出其登出的时间,若没有登出,就显示 still logged in。另一个是,开辟一个缓冲区,把wtmp先遍历一遍,将输出结果先放在缓冲区里,在集中输出。这里尝试前一种,感觉虽然繁琐,但结构简单不一定比后者慢。代码附在文末,程序1-2

在这里,需要一个指针来记录wtmp的当前读取位置,因为当发现一个登录记录是,要遍历整个wtmp,有了这个指针就能回到当前位置,继续读取记录。

如何知道那两条记录是要合并的呢?很简单,先判它们的用户名是否相同,再判它们的终端是否相同,就行了。若没有找到匹配的,那就说明还在登录中。

关于时间,结构体utmp中的时间元素又是一个结构体ut_tv,这个结构体里面有两个int32_t的元素,一个代表秒,另一个代表毫秒。这里我们只用到秒,及ut_time(其实是ut_tv.tv_sec的宏定义)。而利用ctime()函数,可将秒转换为字符串形式的当前时间。而显示登出时间,只需显示这个字符串的一部分即可。而统计时长,可以直接用秒来。

当一个条目显示完后,还要调用lseek恢复文件指针。

  1. 这是我编写的最终效果:少了 still logged insystem boot等信息,但登录登出时间的记录和时长统计是一样的~

程序1.1last1.c

[c]

//last1.c

#include<stdio.h>

#include<utmp.h>

#include<fcntl.h>

#include<unistd.h>

#include<time.h>

&nbsp;

#define SHOWHOST

&nbsp;

//utmp结构体里的时间元素ut_time,表示的是

//从1970年一月一日到现在经过的秒数

//这就必须转换成一个显示生活中的时间

//具体工作是有time.h里的ctime()执行的

void showtime(long);

&nbsp;

//每读出一条结构体utmp,就调用此函数

//将其中的用户信息输出

void show_info( struct utmp * utbufp);

&nbsp;

int main()

{

//用这个结构体来扫描utmp文件

struct utmp current_record;

&nbsp;

//utmp的文件描述符

int utmpfd;

&nbsp;

//获得结构体大小,每次缓冲,缓冲这么多个字节,

//就是缓冲了一个结构体

int reclen = sizeof(current_record);

&nbsp;

//UTMP_FILE在utmp.h里,记录了utmp文件保存在哪里

if( (utmpfd = open("/var/log/wtmp",O_RDONLY)) == -1){

perror(UTMP_FILE);

exit(1);

}

&nbsp;

//把read放在while里,说明每一次回read出来一个结构体,放在

//current_record里,while的内容负责显示,

//之后又执行while循环,再读一个结构体,以此类推

//直到读到文件结尾

while( read(utmpfd,&current_record,reclen ) == reclen)

show_info(&current_record);

close(utmpfd);

return 0;

}

&nbsp;

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(" ");

//这个条件宏定义,通过在最开始是否定义SHOWHOST

//来决定编译时是否编译其所包含的代码

#ifdef SHOWHOST

if( utbufp->ut_host[0] != '\0' )

printf("(%s)",utbufp->ut_host);

#endif

printf("n");

}

&nbsp;

void showtime(long timeval)

{

char *cp;

cp = ctime(&timeval);

printf("%12.12s",cp+4);

}

[/c]

程序1.2last2.c

[c]

//last2.c

&nbsp;

#include<stdio.h>

#include<utmp.h>

#include<fcntl.h>

#include<unistd.h>

#include<time.h>

#include<string.h>

&nbsp;

#define SHOWHOST

&nbsp;

//utmp结构体里的时间元素ut_time,表示的是

//从1970年一月一日到现在经过的秒数

//这就必须转换成一个显示生活中的时间

//具体工作是有time.h里的ctime()执行的

void showtime(long);

void showtime2(long timeval);

&nbsp;

//每读出一条结构体utmp,就调用此函数

//将其中的用户信息输出

void show();

&nbsp;

//utmp的文件描述符

int utmpfd;

//用这个结构体来扫描utmp文件

struct utmp current_record;

//记录wtmp的当前位置

int current_offset=0;

//获得结构体大小,每次缓冲,缓冲这么多个字节,

//就是缓冲了一个结构体

int reclen = sizeof(current_record);

&nbsp;

int main()

{

&nbsp;

//UTMP_FILE在utmp.h里,记录了utmp文件保存在哪里

if( (utmpfd = open("/var/log/wtmp",O_RDONLY)) == -1){

perror(UTMP_FILE);

exit(1);

}

&nbsp;

//把read放在while里,说明每一次回read出来一个结构体,放在

//current_record里,while的内容负责显示,

//之后又执行while循环,再读一个结构体,以此类推

//直到读到文件结尾

while( read(utmpfd,&current_record,reclen ) == reclen){

current_offset += reclen;

show_info();

}

close(utmpfd);

return 0;

}

&nbsp;

show_info()

{

struct utmp utbufp2;

long compare,hour,minute;

if(current_record.ut_host[0] == '\0' )

return;

printf("%-13s",current_record.ut_name);

printf(" ");

printf("%-13s",current_record.ut_line);

printf(" ");

#ifdef SHOWHOST

printf("%-17s",current_record.ut_host);

#endif

showtime(current_record.ut_time);

printf(" ");

while(read(utmpfd,&utbufp2,reclen) == reclen)

if((strcmp(current_record.ut_name,utbufp2.ut_name) == 0) && (strcmp(current_record.ut_line,utbufp2.ut_line) == 0))

{

showtime2(utbufp2.ut_time);

printf(" ");

compare = utbufp2.ut_time - current_record.ut_time;

minute = (compare /60) % 60;

hour = ((compare/60) - minute)/60;

printf("(%ld:%ld)",hour,minute);

//这个条件宏定义,通过在最开始是否定义SHOWHOST

//来决定编译时是否编译其所包含的代码

printf("n");

return;

}

printf("n");

lseek(utmpfd,current_offset,SEEK_SET);

}

&nbsp;

void showtime(long timeval)

{

char *cp;

cp = ctime(&timeval);

printf("%16.16s",cp);

}

&nbsp;

void showtime2(long timeval)

{

char *cp;

cp = ctime(&timeval);

printf("- %5.5s",cp + 11);

}

[/c]



      • 额. . .想看看,不过还没付诸行动。
        以前纯写C语言的,觉得还是要了解点系统编程,又不愿意总看书,这本书不是光说不练的就好。