Linux下who命令之C語言實現
Step1:前期准備
-
首先要有一個清楚的認識:linux中一切皆文件
-
實現who命令,who命令也是Linux中的一個文件,那我們怎么找到它呢?我們可以“找男人”(man),終端下執行命令:
man who
-
此處我裝了漢譯版的man手冊,查看到這樣一段提示如下:
-
-
所以退出man手冊,執行命令:
info who
-
找到如下圖相關信息:
-
-
也可以找到下面這樣的一段話,也就是說:who命令可以這樣實現,讀取utmp里邊的內容,然后顯示記錄,最后關閉utmp,who命令就是一個很簡單的體現吧,打開文件,保存文件id,根據文件id讀取用戶的登陸信息,顯示在終端。
-
-
我們嘗試地打開一下
/var/run/utmp
這個文件得到如下畫面,發現是一串亂碼,但還是能看出相關who的信息的 -
-
既然不清楚
utmp
是干嘛的,我們可以使用man -k
來檢索一下相關信息,得到信息如下 -
-
經過篩選,發現只有
utmpx(5)
是符合我們要求的,其中后面顯示的是login records
,而who命令不就是用來輸出登錄信息的;man
的第五個類型表示的是文件格式和規范,說明此處可能存儲了登錄記錄的數據結構 -
使用指令:
man 5 utmpx
,可以得到如下有用信息: -
-
也可以使用輸出重定向指令
man 5 utmpx > utmp.txt
,得到文本格式的utmp
數據結構體如下:
struct utmp {
short ut_type; /* Type of record */
pid_t ut_pid; /* PID of login process */
char ut_line[UT_LINESIZE]; /* Device name of tty - "/dev/" */
char ut_id[4]; /* Terminal name suffix,
or inittab(5) ID */
char ut_user[UT_NAMESIZE]; /* Username */
char ut_host[UT_HOSTSIZE]; /* Hostname for remote login, or
kernel version for run-level
messages */
struct exit_status ut_exit; /* Exit status of a process
marked as DEAD_PROCESS; not
used by Linux init (1 */
/* The ut_session and ut_tv fields must be the same size when
compiled 32- and 64-bit. This allows data files and shared
memory to be shared between 32- and 64-bit applications. */
#if __WORDSIZE == 64 && defined __WORDSIZE_COMPAT32
int32_t ut_session; /* Session ID (getsid(2)),
used for windowing */
struct {
int32_t tv_sec; /* Seconds */
int32_t tv_usec; /* Microseconds */
} ut_tv; /* Time entry was made */
#else
long ut_session; /* Session ID */
struct timeval ut_tv; /* Time entry was made */
#endif
int32_t ut_addr_v6[4]; /* Internet address of remote
host; IPv4 address uses
just ut_addr_v6[0] */
char __unused[20]; /* Reserved for future use */
};
-
此時萬里長征已走一半,對比系統who命令,發現該結構體中並沒有直接給出用戶登錄時間的成員變量,而是內嵌了一個
ut_tv
的時間結構體,其中成員變量tv_sec
才是我們需要的。 -
如果耐心地看完該幫助文檔,可以在下面一段代碼中有新的發現:
-
-
此處定義了一個ut_time的宏,指向的就是
ut_tv.ut_sec
,而我們需要用到的不是這樣一個以秒為單位的格林威治標准時間,所以才后面需用到ctime()函數
。 -
在這兒用的就是剛剛說的linux的一個思想,一切事物都是文件。
Step2:其余頭文件的准備
time.h
:將格林威治標准時間(GMT)長整形的數時間轉化為我們所熟悉的時間表示,運用ctime()函數
string.h
:調整輸出格式,后面調試時會提到用處。- 其余頭文件:因為要打開文件嘛,所以有些頭文件必不可少,這個在我本周博客中有詳細介紹:博客地址
Step3:編程思想
-
查看UTMP_FILE宏:
grep -nr UTMP_FILE /usr/include
-
-
讀取其結構體,將需要的變量提取出來並按照一定的格式輸出
-
其中,時間輸出本來是一串格林威治標准時間(GMT)長整形的數,可以用ctime()將把日期和時間轉換為字符串
Step4:代碼實現
- who代碼
- 其中等下需要修改的代碼貼在此處:
void showtime(long timeval)
{
char* cp;
cp = ctime(&timeval);
printf("%s",cp+4); //+4是因為*cp所指的一串字符前4個字符表示為“星期”,可以忽略此信息
}
Step5:編譯執行
-
執行結果如下:
-
-
發現“(:0)”被換行了,但是程序中並沒有輸出換行字符。經過一番思索后,猜想ctime()函數的返回值
*cp
可能自動在最后補了一個字符\n
。
Step6:調試代碼
- 那只要能回退一個字符就好了,比如輸出一個
\b(退格字符)
- 編譯再運行,發現輸出格式仍是原樣,最后經過了解
\b
確實是可以回退一個字符,但是並不能實現退到上行,也就是不能消除\n
帶來的影響 - 百度之后,說可以通過輸出
\r\b
來實現“退行”,但實踐后發現也不可取 - 最后考慮到直接修改
*cp
字符串中最后一個字符為\0
,使輸出達到與系統who
命令一樣的效果 - 在輸出語句前添加如下代碼(需用到
string.h
):cp[strlen(cp)-1] = '\0'
- 最后編譯執行效果,可以看出與who命令基本一致: