Linux编程实践——代码实现ac命令

ac命令是什么?

man ac命令后的解释是‘打印用户连接时间的统计数据’。

附带的两个常用的命令参数:

-d 日统计

-p 每个用户的统计

ac命令运行结果

进入命令行,依次敲下命令,输出结果比较了然:

$ ac

total 312.64

$ ac -d

Oct 8 total 48.11

Oct 9 total 74.71

Oct 12 total 86.85

Oct 14 total 58.57

Today total 44.82

$ ac -p

lizh 313.09

total 313.09

统计结果单位为小时。

要统计出这些系统信息,需要查询那些文档呢?那么把我们的问题列出个清单来对症下药即可:

  • ac的登陆时间和注销时间日志记录在哪个文件?
  • 何时会写入登陆和注销日志记录?
  • 日志记录的格式是什么?
  • 统计时间的算法是什么?

姑且只有这些问题吧,由此查阅我们需要的文档。

我们需要什么?

首先要做的是分析实现过程,我们需要以下工具作为参考:

ac文档手册:man ac;

wtmp文档手册:man 5 wtmp;

dump-utmp工具查看wtmp;

login(3):写utmp和wtmp入口;

logout(3):写utmp和wtmp入口;

编程我们借助《UnixLinux编程实践教程》Linux编程实践——代码实现ac命令这本书第二章51页的‘utmplib.c’代码文件,实现对wtmp文件的操作,主要是实现wtmp文件的打开、关闭、读取、定位。

ac的登陆时间和注销时间日志记录在哪个文件?

在ac文档手册中标出了数据来源:

wtmp

The system wide login record file. See wtmp(5) for further details.

ac的统计结果是基于这个文件的,之前一篇文章里也做过说明,可以查看这里

也可参考wtmp文件的在线文档,跟文档手册是一样的内容,地址为:http://www.kernel.org/doc/man-pages/online/pages/man5/wtmp.5.html

The wtmp file records all logins and logouts. Its format is exactly like utmp except that a null username indicates a logout on the associ‐ated terminal. Furthermore, the terminal name ~ with username shutdown or reboot indicates a system shutdown or reboot and the pair of termi‐nal names |/} logs the old/new system time when date(1) changes it.wtmp is maintained by login(1), init(8), and some versions of getty(8)(e.g., mingetty(8) or agetty(8)). None of these programs creates the file, so if it is removed, record-keeping is turned off.

wtmp文件记录了所有的登陆和注销。它的格式与utmp很相似,除了它用空用户名表示某个相关联终端的注销。此外,终端名称~并且用户名为shutdown或者reboot表明系统关闭或者重启,而且当date(1)改变系统时间时,终端名称里为|/}记录了原来的/新的系统时间。wtmp文件记录由login(1),init(8),和一些版本的getty(8)(例如:mingetty(i)或者agetty(8))来维护。这些程序并不创建该文件,所以如果文件被移除,保存记录的功能就被关闭了。

何时会写入登陆和注销记录?

查阅login(3)文档,里面有段话,大体意思是讲:login()的时候以struct utmp* ut为参数,用USER_PROCESS值填写ut->ut_type,并且填写ut->ut_pid等等。在logout()的时候,会将ut->ut_type写入DEAD_PROCESS;除此之外我们还得考虑reboot和shutdown的情况,在这种情况下,所有用户基本都在同一时间退出,这时wtmp文件中用户名被记录为reboot和shutdown。

目前,我们只考虑这两种情况。

日志记录的格式是什么?

有个工具:dump-utmp,我们利用这个工具查看wtmp文件 ,我们先用一个用户名在控制台1登陆再注销,运行命令:

$dump-utmp /var/log/wtmp

在wtmp文件尾部有4条新产生的记录为:

1 lizh                            |tty1                            |7|    | 1433|                                                                                                                                                                                                                                                                |Sat Oct 15 10:45:11 20112 lizh                            |tty1                            |7|1   | 8251|                                                                                                                                                                                                                                                                |Sat Oct 15 10:45:11 20113                                 |tty1                            |8|    | 1433|                                                                                                                                                                                                                                                                |Sat Oct 15 10:45:56 20114 LOGIN                           |tty1                            |6|1   | 8302|                                                                                                                                                                                                                                                                |Sat Oct 15 10:45:56 2011

这个输出格式在dump-utmp文档中有说明,是以'|'分隔, 每行以user name, tty, type, id, pid, hostaddr, host, time的顺序排列的,我们关心的是第一列user name,第二列tty,第三列type,最后一列time,除了type外都比较了然,type的定义在wtmp的文档中有说明,如下:
Linux编程实践——代码实现ac命令Linux编程实践——代码实现ac命令View Code

#define EMPTY         0 /* Record does not contain valid info                                      (formerly known as UT_UNKNOWN on Linux) */#define RUN_LVL       1 /* Change in system run-level (see                                      init(8)) */#define BOOT_TIME     2 /* Time of system boot (in ut_tv) */#define NEW_TIME      3 /* Time after system clock change                                      (in ut_tv) */#define OLD_TIME      4 /* Time before system clock change                                      (in ut_tv) */#define INIT_PROCESS  5 /* Process spawned by init(8) */#define LOGIN_PROCESS 6 /* Session leader process for user login */#define USER_PROCESS  7 /* Normal process */#define DEAD_PROCESS  8 /* Terminated process */#define ACCOUNTING    9 /* Not implemented */

原来,USER_PROCESS值为7,DEAD_PROCESS值为8。

Linux编程实践——代码实现ac命令

我们可以自己写个读取wtmp文件的代码,与dump-utmp打印出来的日志记录是一样的,这里参考《UnixLinux编程实践教程》Linux编程实践——代码实现ac命令第2章50页who3.c的源码改写而成,采用缓冲技术,原书中有个图例,看起来比较明了,如右图:

将原来读取utmp文件改为读取wtmp,并略微修改打印项即可,源码如下:
Linux编程实践——代码实现ac命令Linux编程实践——代码实现ac命令utmplib.c

/* utmplib.c  - functions to buffer reads from utmp file * *      functions are *              utmp_open( filename )   - open file *                      returns -1 on error *              utmp_next( )            - return pointer to next struct *                      returns NULL on eof *              utmp_close()            - close file * *      reads NRECS per read and then doles them out from the buffer */#include        <stdio.h>#include        <fcntl.h>#include        <sys/types.h>#include        <utmp.h>#define NRECS   16#define NULLUT  ((struct utmp *)NULL)#define UTSIZE  (sizeof(struct utmp))static  char    utmpbuf[NRECS * UTSIZE];                /* storage      */static  int     num_recs;                               /* num stored   */static  int     cur_rec;                                /* next to go   */static  int     fd_utmp = -1;                           /* read from    */utmp_open( char *filename ){        fd_utmp = open( filename, O_RDONLY );           /* open it      */        cur_rec = num_recs = 0;                         /* no recs yet  */        return fd_utmp;                                 /* report       */}struct utmp *utmp_next(){        struct utmp *recp;        if ( fd_utmp == -1 )                            /* error ?      */                return NULLUT;        if ( cur_rec==num_recs && utmp_reload()==0 )    /* any more ?   */                return NULLUT;                                        /* get address of next record    */        recp = ( struct utmp *) &utmpbuf[cur_rec * UTSIZE];        cur_rec++;        return recp;}int utmp_seek(int rec_offset, int base){    off_t newpos;    int   rv;    if ( base == SEEK_CUR )        rec_offset += ( cur_rec - num_recs );    newpos = lseek(fd_utmp, (off_t) rec_offset*UTSIZE, base);    if( newpos == -1 )        return -1;    cur_rec = num_recs ;            /* will force reload */    return  newpos/UTSIZE;}int utmp_reload()/* *      read next bunch of records into buffer */{        int     amt_read;/* read them in         */        amt_read = read( fd_utmp , utmpbuf, NRECS * UTSIZE );                                                /* how many did we get? */        num_recs = amt_read/UTSIZE;                                                /* reset pointer        */        cur_rec  = 0;        return num_recs;}utmp_close(){        if ( fd_utmp != -1 )                    /* don't close if not   */                close( fd_utmp );               /* open                 */}

Linux编程实践——代码实现ac命令Linux编程实践——代码实现ac命令print_wtmp

#include <stdio.h>#include <assert.h>#include <unistd.h>#include <fcntl.h>#include "utmplib.c"#define USERNUM 6struct utmp_record{    short ut_type;    char ut_user[UT_NAMESIZE];    char ut_line[UT_LINESIZE];    int32_t login_sec;};void show_info(struct utmp*);void showtime(long);int  main(int ac, char* av[]){    int wtmpfd;    struct utmp* utbuf=utmp_next();    if(utmp_open(WTMP_FILE) == -1){        perror(WTMP_FILE);        return -1;    }    while((utbuf=utmp_next()) != ((struct wtmp*)NULL))          show_info(utbuf);    utmp_close();    return 0;}/* *      show info() *                      displays the contents of the utmp struct *                      in human readable form *                      * displays nothing if record has no user name */void show_info( struct utmp *utbufp){    //  if ( utbufp->ut_type != USER_PROCESS )    //    return;     printf("%-8.8s", utbufp->ut_name);      /* the logname  */     printf("");                            /* a space      */     printf("|%-8.8s", utbufp->ut_line);      /* the tty      */     printf("");                            /* a space      */     printf("|%d", utbufp->ut_type);     printf("");     showtime( utbufp->ut_time );            /* display time */#ifdef SHOWHOST     if ( utbufp->ut_host[0] != '' )         printf(" (%s)", utbufp->ut_host);/* the host    */#endif     printf("n");                          /* newline      */}void showtime( long timeval )/* *      displays time in a format fit for human consumption *      uses ctime to build a string then picks parts out of it *      Note: %12.12s prints a string 12 chars wide and LIMITS *      it to 12chars. */{        char    *cp;                    /* to hold address of time      */        cp = ctime(&timeval);           /* convert time to string       */                                        /* string looks like            */                                        /* Mon Feb  4 00:46:40 EST 1991 */                                        /* 0123456789012345.            */        printf("|%12.12s", cp+4 );       /* pick 12 chars from pos 4     */}


统计时间的算法

正常情况下,同一用户名、同一tty,用户login将ut_type置为USER_PROCESS,这算是时间的起点;用户logout时,将ut_type置为DEAD_PROCESS,或者是shutdown或是reboot下,这两种情况算是时间的终点,这都算是一次完整的登陆。

那么,我们可以设计一个结构来保存初步日志记录的分析结果:

struct utmp_record{    short ut_type;                             //type    char ut_user[UT_NAMESIZE];     // user name    char ut_line[UT_LINESIZE];        //tty    int32_t login_sec;                       //登陆时间    int32_t logout_sec;                     //注销时间    double diff;                                 //登陆时间与注销时间的时间差};

我们,在遍历wtmp文件时,将相关数据整理和计算填入此结构。
Linux编程实践——代码实现ac命令Linux编程实践——代码实现ac命令acc.c

#include <stdio.h>#include <assert.h>#include <unistd.h>#include <fcntl.h>#include <string.h>#include <time.h>#include "utmplib.c"#define RECORDSIZE 256struct utmp_record{    short ut_type;                 //type    char ut_user[UT_NAMESIZE];     // user name    char ut_line[UT_LINESIZE];    //tty    int32_t login_sec;           //登陆时间    int32_t logout_sec;          //注销时间    double diff;                 //登陆时间与注销时间的时间差};struct utmp_record record[RECORDSIZE];int num_rd = 0;  //整理日志后的记录数量void show_wtmp(struct utmp*);  //打印wtmp内容void showtime(long);         //打印时间(按照时间格式)void show_sorted();void  show_totaltime();    //打印总连接时间double sec2hour(double );  //秒换算成小时int  main(int ac, char* av[]){    int wtmpfd;    struct utmp* utbuf=utmp_next();    int i;    if(utmp_open(WTMP_FILE) == -1){        perror(WTMP_FILE);        return -1;    }    memset(&record[0],0,sizeof(struct utmp_record)*RECORDSIZE);    while((utbuf=utmp_next()) != ((struct utmp*)NULL))    {        // show_wtmp(utbuf);        if((utbuf->ut_type == USER_PROCESS))        {                for(i=0; i < num_rd; i++)                {                    if( (strcmp(record[i].ut_user,utbuf->ut_user)==0)&&                        (record[i].login_sec == utbuf->ut_time))                    {                        break;                    }                }                if(i == num_rd)                {                    record[num_rd].ut_type = utbuf->ut_type;                    memcpy(record[num_rd].ut_user,utbuf->ut_user,UT_NAMESIZE);                    memcpy(record[num_rd].ut_line,utbuf->ut_line,UT_LINESIZE);                    record[num_rd].login_sec = utbuf->ut_time;                    num_rd++;                }        }        else if(utbuf->ut_type == DEAD_PROCESS){            for(i=0; i < num_rd; i++){                if(record[i].ut_type == USER_PROCESS                   && strcmp(record[i].ut_line,utbuf->ut_line)==0                   && utbuf->ut_time > record[i].login_sec)                    {                    record[i].logout_sec = utbuf->ut_time;                    record[i].ut_type = DEAD_PROCESS;                    record[i].diff = difftime(record[i].logout_sec,record[i].login_sec);//计算时间差                }            }        }        else if((utbuf->ut_type == RUN_LVL && strncmp(utbuf->ut_user,"shutdown",8)==0)                || (utbuf->ut_type == BOOT_TIME && strncmp(utbuf->ut_user,"reboot",6)==0)){            for(i=0; i < num_rd; i++){                if(record[i].ut_type == USER_PROCESS                   && utbuf->ut_time > record[i].login_sec ){                    record[i].logout_sec = utbuf->ut_time;                    record[i].ut_type = utbuf->ut_type;                    record[i].diff = difftime(record[i].logout_sec, record[i].login_sec);                }            }        }}    //  show_sorted();    utmp_close();    if(ac == 1 ){ //不带参数的ac令命        show_totaltime();     }    else if (ac == 2){        //..带参数的ac命令,例如:        //ac -p 按照用户名统计        //ac -d 按照日期统计    }    return 0;}/* *      show info() *                      displays the contents of the utmp struct *                      in human readable form1 *                      * displays nothing if record has no user name */void show_wtmp( struct utmp *utbufp){    //  if ( utbufp->ut_type != USER_PROCESS )    //    return;     printf("%-8.8s", utbufp->ut_name);      /* the logname  */     printf("");                            /* a space      */     printf("|%-8.8s", utbufp->ut_line);      /* the tty      */     printf("");                            /* a space      */     printf("|%d", utbufp->ut_type);     printf("");     showtime( utbufp->ut_time );            /* display time */#ifdef SHOWHOST     if ( utbufp->ut_host[0] != '' )         printf(" (%s)", utbufp->ut_host);/* the host    */#endif     printf("n");                          /* newline      */}void show_sorted(){    int i = 0;    for(i=0; i < num_rd; i++){        printf("%-8.8s",record[i].ut_name);        printf("");        printf("|%-8.8s",record[i].ut_line);        printf("|%d",record[i].ut_type);        printf("");        printf("%d",record[i].login_sec);        printf("");        showtime(record[i].login_sec);        printf("");        printf("%d",record[i].logout_sec);        printf("");        showtime(record[i].logout_sec);        printf("");        printf("|%f",record[i].diff);        printf("n");    }}void show_totaltime(){    int i=0;    double total =0.0;    time_t now_t = time(NULL);    for(i=0; i < num_rd;i++){        if(record[i].ut_type != USER_PROCESS){            total += record[i].diff;        }        else {            record[i].diff = difftime(now_t,record[i].login_sec);            total += record[i].diff;        }    }    printf("   total    %.2fn",sec2hour(total));}void showtime( long timeval )/* *      displays time in a format fit for human consumption *      uses ctime to build a string then picks parts out of it *      Note: %12.12s prints a string 12 chars wide and LIMITS *      it to 12chars. */{        char    *cp;                    /* to hold address of time      */        cp = ctime(&timeval);           /* convert time to string       */                                        /* string looks like            */                                        /* Mon Feb  4 00:46:40 EST 1991 */                                        /* 0123456789012345.            */        printf("|%15.15s",cp+4 );       /* pick 12 chars from pos 4     */}double sec2hour(double secs){    double hours = (secs/60.0/60.0);    return hours;}

对于当前用户的登陆时间计算,是采用执行acc时取当前时间然后与登陆时间做差的方法。

该程序只是一定程度上实现了ac工具,并不完美:

  • 为了便于编程,整理后的数据结构数组个数,用的宏RECORDSIZE直接定义;
  • 带参数执行未实现,像-p、-d参数,可在目前程序基础上再做扩展;
  • 用户更改系统时间时对执行结果的影响,未作作相应处理。




原文链接: https://www.cnblogs.com/blueclue/archive/2011/10/24/linux_system_program_ac.html

欢迎关注

微信关注下方公众号,第一时间获取干货硬货;公众号内回复【pdf】免费获取数百本计算机经典书籍

原创文章受到原创版权保护。转载请注明出处:https://www.ccppcoding.com/archives/34975

非原创文章文中已经注明原地址,如有侵权,联系删除

关注公众号【高性能架构探索】,第一时间获取最新文章

转载文章受原作者版权保护。转载请注明原作者出处!

(0)
上一篇 2023年2月8日 上午11:52
下一篇 2023年2月8日 上午11:53

相关推荐