1. 程式人生 > >Unix/Linux程式設計實踐教程–ac在Ubuntu 14.04的實現

Unix/Linux程式設計實踐教程–ac在Ubuntu 14.04的實現

環境:Ubuntu 14.04 32位

為什麼這回不寫在OS x上的實現了?因為OS X使用的是utmpx,然後我用getutxent_wtmp()這個函式也沒有辦法正確獲取wtmp的日誌資訊,所以先在Ubuntu上實現好了。

預設沒有帶ac這個程式,需要自行安裝。(sudo apt-get insall acct

好的,開始我們的節目。man ac,可以看到ac是一個統計使用者連線時長(以小時為單位)的工具。使用者的登入、退出的資訊儲存在utmp這個檔案中。

那我們就man 5 wtmp,注意到它的描述裡有這麼一句話。The wtmp file records all logins and logouts. Its format is exactly like utmp except that a null username indicates a logout on the associated terminal.

這個檔案的格式是和utmp類似的,而且如果使用者名稱為空的話,說明使用者在對應的終端上退出了

然後就可以開始嘗試寫程式碼了,初版可能是這樣的。

/* ubuntu 14.04 */
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <utmp.h>
#include <string.h>

void utmpOut(struct utmp *p);

int main(void)
{
    int fd;
    struct utmp utmpBuf;
    int
siz = sizeof(struct utmp); if((fd = open(WTMP_FILE, O_RDONLY)) == -1){ perror("ac_ubuntu"); exit(1); } while(read(fd, &utmpBuf, siz) == siz) utmpOut(&utmpBuf); close(fd); return 0; } void utmpOut(struct utmp *p) { //if(strcmp(p->ut_user, "superxc"))
// return ; printf("%d\t%s\t%s\t%s", p->ut_type, p->ut_user, p->ut_line, asctime(localtime(&p->ut_time))); }

執行一下看看wtmp裡都寫啥了。

1   shutdown    ~  Mon May 29 09:11:18 2017
2   reboot  ~  Sun Jun  4 18:46:09 2017
1   runlevel    ~  Sun Jun  4 18:46:09 2017
6   LOGIN   tty4    Sun Jun  4 18:46:09 2017
6   LOGIN   tty5    Sun Jun  4 18:46:09 2017
6   LOGIN   tty2    Sun Jun  4 18:46:09 2017
6   LOGIN   tty3    Sun Jun  4 18:46:09 2017
6   LOGIN   tty6    Sun Jun  4 18:46:09 2017
6   LOGIN   tty1    Sun Jun  4 18:46:09 2017
7   superxc tty1    Sun Jun  4 18:46:42 2017
7   superxc pts/0   Sun Jun  4 18:57:52 2017
7   superxc pts/2   Sun Jun  4 18:58:07 2017
1   runlevel    ~  Sun Jun  4 19:03:16 2017
8       tty4    Sun Jun  4 19:03:17 2017
8       tty5    Sun Jun  4 19:03:17 2017
8       tty2    Sun Jun  4 19:03:17 2017
8       tty3    Sun Jun  4 19:03:17 2017
8       tty6    Sun Jun  4 19:03:17 2017
8       pts/0   Sun Jun  4 19:03:17 2017
8       pts/2   Sun Jun  4 19:03:17 2017
1   shutdown    ~  Sun Jun  4 19:03:17 2017
1   shutdown    ~  Sun Jun  4 19:03:17 2017
2   reboot  ~  Mon Jun  5 08:21:34 2017
1   runlevel    ~  Mon Jun  5 08:21:34 2017
6   LOGIN   tty4    Mon Jun  5 08:21:34 2017
6   LOGIN   tty5    Mon Jun  5 08:21:34 2017
6   LOGIN   tty2    Mon Jun  5 08:21:34 2017
6   LOGIN   tty3    Mon Jun  5 08:21:34 2017
6   LOGIN   tty6    Mon Jun  5 08:21:34 2017
6   LOGIN   tty1    Mon Jun  5 08:21:34 2017
7   superxc pts/0   Mon Jun  5 08:22:25 2017
7   superxc pts/2   Mon Jun  5 08:22:38 2017
7   root    tty1    Mon Jun  5 09:05:05 2017
8       tty1    Mon Jun  5 09:07:35 2017
6   LOGIN   tty1    Mon Jun  5 09:07:35 2017

我這裡只截取了6月4日和6月5日的記錄,再拿ac -pd的記錄對比一下。

    superxc                              0.45
Jun  4  total        0.45
    superxc                              2.47
    root                                 0.04
Today   total        2.51

這裡的Today 指的是6月5日,可以看到6月4日,superxc總共連線了0.45個小時。怎麼算的呢?注意看wtmp的輸出。關於superxc使用者的登入有三條,分別是:

7   superxc tty1    Sun Jun  4 18:46:42 2017
7   superxc pts/0   Sun Jun  4 18:57:52 2017
7   superxc pts/2   Sun Jun  4 18:58:07 2017

退出的話,注意剛才文件裡寫的那句話,也就是我標重點的部分,如果使用者名稱為空的話,說明使用者在對應的終端上退出了,所以可以找到下面這兩行,就是退出的記錄。

8       pts/0   Sun Jun  4 19:03:17 2017
8       pts/2   Sun Jun  4 19:03:17 2017

這裡要注意到tty1沒有退出,直接就關機了,所有沒有對應的記錄。

同一個使用者在三個終端登入,時間是怎麼算的?由計算得知是累加的。

(63 - 46 + (17 - 42)/60 + /* tty1 */
63 - 57 + (17 - 52)/60 + /* pts/0 */
63 - 58 + (17 - 07)/60 /* pts/2 */
)/60 = 0.45277

剛好和ac的結果一致。

wtmp檔案中有些記錄是我們並不需要的,ut_type為6、 2、 1的不需要,但是ut_type為1且ty_user為shutdown為需要保留(用於作為關機時沒有退出的使用者的退出時間)。

所以ac的實現大概是這樣的:
Step1: 在ut_type為7的記錄中找到第一條未處理的資訊、記錄下是哪個終端,ut_time記為t1。
Step2: 向後找ut_type為8的記錄中與Step1中記錄的終端相匹配的(如果遇到到shutdown,就不再向下查詢,如果找到EOF,就以當前時間為準),對應的ut_time記為t2。
Step3: t2 - t1的結果累加到sum中。
Step4: 重複Step1-3直到所有記錄處理完畢。

所以程式碼可能是這樣的:

/* ubuntu 14.04 */
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <utmp.h>
#include <string.h>
#include <time.h>

#define UTMPSIZ sizeof(struct utmp)

void utmpOut(struct utmp *p);
int cntTime(int fd, struct utmp *p);

int main(void)
{
    int fd;
    struct utmp utmpBuf;
    int sum = 0; /* seconds */
    if((fd = open(WTMP_FILE, O_RDONLY)) == -1){
        perror("ac_ubuntu");
        exit(1);
    }

    while(read(fd, &utmpBuf, UTMPSIZ) == UTMPSIZ){
        sum += cntTime(fd, &utmpBuf);
        // utmpOut(&utmpBuf);
    }
    close(fd);
    printf("%d seconds => %f hours\n", sum, sum / (60.0 * 60));
    return 0;
}
void utmpOut(struct utmp *p)
{
    //if(strcmp(p->ut_user, "superxc"))
    //    return ;
    printf("%d\t%s\t%s\t%s", p->ut_type, p->ut_user, p->ut_line, asctime(localtime(&p->ut_time)));
}
int cntTime(int fd, struct utmp *p)
{
    char line[256];
    struct utmp utmpBuf;
    int cnt = 0; /* forward how many time */
    int flag = 0;
    int t1, t2;

    if(p->ut_type != USER_PROCESS) /* USER_PROCESS 7 */
        return 0;
    strcpy(line, p->ut_line);
    t1 = (int)p->ut_time;
    /* printf("user %s login on %s at %s", p->ut_user, p->ut_line, asctime(localtime(&p->ut_time))); */
    while(read(fd, &utmpBuf, UTMPSIZ) == UTMPSIZ){
        ++cnt;
        /* DEAD_PROCESS 8, RUN_LVL 1 */
        /* printf("test record: "); */
        /* utmpOut(&utmpBuf); */
        if((utmpBuf.ut_type == DEAD_PROCESS && strcmp(line, utmpBuf.ut_line) == 0)
            || (utmpBuf.ut_type == RUN_LVL && strcmp("shutdown", utmpBuf.ut_user) == 0)){
            flag = 1;
            /* printf("find!\n"); */
            break;
        }
    }
    t2 = flag ? utmpBuf.ut_time : (int)time(NULL);
    /*
    if(flag){
        printf("log out at %s", asctime(localtime(&utmpBuf.ut_time)));
    }else{
        printf("log out at now\n");
    }
    printf(" tabkes %d seconds\n", t2 - t1);
    */
    lseek(fd, -cnt * UTMPSIZ, SEEK_CUR); /* roll back */ 
    return t2 - t1;
}

程式碼中使用了lseek進行回滾指標,本程式並沒有使用快取,有興趣的可以自己加上,還有比如-p-d之類的選項引數都可以加。

執行效果:

ac

有個大犇的實現做得比我好多了,可以很大程式減少系統呼叫,時間開銷比較小,而且能結合緩衝,可擴充套件性也挺好。連結