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.
然後就可以開始嘗試寫程式碼了,初版可能是這樣的。
/* 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
之類的選項引數都可以加。
執行效果:
有個大犇的實現做得比我好多了,可以很大程式減少系統呼叫,時間開銷比較小,而且能結合緩衝,可擴充套件性也挺好。連結