☰
TLPI 第40章 读书笔记:Login Accounting
2026/10/9 2:44:17 网站建设 项目流程

笔记和练习博客总目录见:开始读TLPI。

登录记账(Login accounting)负责记录当前登录到系统的用户,同时留存历史登录与登出记录。本章介绍登录记账文件,以及用于读取和修改文件内信息的库函数。同时说明:提供登录服务的应用程序,在用户登录、登出时,为更新这些文件应当执行的操作步骤。

40.1 Overview of the utmp and wtmp Files

UNIX 系统维护两个数据文件,保存用户登录与登出系统的相关信息:

  • utmp 文件:保存当前已登录系统的用户记录(以及后文介绍的其他一些信息)。每当用户登录时,就会向 utmp 文件写入一条记录。该记录中的字段ut_user用于保存用户的登录名。用户登出后,这条记录会被清除。who(1)等程序读取 utmp 文件中的信息,展示当前登录用户列表。
  • wtmp 文件:作为所有用户登录、登出行为的审计日志(同时包含后文介绍的其他信息)。每次登录时,系统会把写入 utmp 的同一份记录追加到 wtmp 文件;用户登出时,会再追加一条记录。登出记录的内容与登录记录基本一致,只是ut_user字段置零。last(1)命令可用于查看、筛选 wtmp 文件内容。

💡 ut_user字段清零并非登出无需记录用户名,而是一种约定。即ut_user字段清零表示是登出记录,然后通过同一个终端(ut_line)来匹配登入记录。

在 Linux 系统上,utmp 文件位于/var/run/utmp,wtmp 文件位于/var/log/wtmp。一般情况下,应用程序无需关心这些路径名,因为路径已经编译进 glibc 库。如果程序确实需要引用这两个文件的位置,应当使用定义在<paths.h>(以及<utmpx.h>)中的路径常量_PATH_UTMP和_PATH_WTMP,而不要在代码里硬编码路径字符串。

$whovagrant pts/02026-09-30 07:09(10.0.2.2)$ last|head-5vagrant pts/010.0.2.2 Wed Sep3007:09 still loggedinrebootsystem boot5.15.0-314.193.5 Wed Sep3007:06 still running vagrant pts/110.0.2.2 Mon Sep2806:05 - 06:19(00:14)vagrant pts/010.0.2.2 Mon Sep2801:18 - 06:51(05:32)rebootsystem boot5.15.0-314.193.5 Mon Sep2801:16 - 06:52(05:35)

SUSv3 并未对 utmp、wtmp 文件的路径规定任何标准符号名。_PATH_UTMP和_PATH_WTMP是 Linux 与 BSD 系列所使用的常量名。许多其他 UNIX 实现会改用UTMP_FILE和WTMP_FILE这一组常量代表上述路径。Linux 也在<utmp.h>中定义了这两个名字,但不会在<utmpx.h>或<paths.h>中定义它们。

40.2 The utmpx API

utmp 和 wtmp 文件自 UNIX 早期版本就已存在,但在各类 UNIX 实现中持续演变并产生差异,尤其是 BSD 与 System V 两个分支之间。System V Release 4 对该 API 做了大幅扩展,在此过程中引入一套全新的、并行的utmpx结构体,以及配套的utmpx、wtmpx文件。头文件名和处理这类新文件的新增函数名中同样加入字母 x。很多其他 UNIX 实现也为这套 API 增加了各自的扩展。

本章介绍 Linux 的utmpxAPI,它融合了 BSD 和 System V 两种实现。Linux 并未沿用 System V 的做法,没有额外维护一套并行的utmpx、wtmpx文件;而是直接在 utmp 和 wtmp 文件中存放全部所需信息。但为兼容其他 UNIX 实现,Linux 同时提供传统 utmp API,以及源自 System V 的 utmpx API,用于读取这两个文件。在 Linux 上,这两套 API 返回的信息完全相同。(两套 API 为数不多的差别之一:utmp API 提供少数函数的可重入版本,而 utmpx API 没有。)不过我们只讨论 utmpx 接口,因为它是 SUSv3 规范定义的 API,更便于移植到其他 UNIX 实现,因此作为首选。

SUSv3 规范并未涵盖 utmpx API 的全部细节(例如,utmp、wtmp 文件的存放路径就不在标准规定范围内)。登录记账文件的具体内容在不同系统实现间略有差异;不少 UNIX 实现还提供 SUSv3 未定义的额外登录记账相关函数。

文献 [Frisch, 2002] 的第 17 章总结了不同 UNIX 实现下,wtmp 与 utmp 文件在存放位置和使用方式上的若干差异。该章节还介绍了ac(1)命令,该命令可汇总 wtmp 文件中的登录统计信息。

40.3 The utmpx Structure

utmp 和 wtmp 文件由多条 utmpx 记录构成。utmpx 结构体在头文件<utmpx.h>中定义,如代码清单 40-1 所示。

Listing 40-1: Definition of the utmpx structure
参见

#define_GNU_SOURCE/* Without _GNU_SOURCE the two field struct exit_status { names below are prepended by "__" */shorte_termination;/* Process termination status (signal) */shorte_exit;/* Process exit status */};#define__UT_LINESIZE32#define__UT_NAMESIZE32#define__UT_HOSTSIZE256structutmpx{shortut_type;/* Type of record */pid_tut_pid;/* PID of login process */charut_line[__UT_LINESIZE];/* Terminal device name */charut_id[4];/* Suffix from terminal name, or ID field from inittab(5) */charut_user[__UT_NAMESIZE];/* Username */charut_host[__UT_HOSTSIZE];/* Hostname for remote login, or kernel version for run-level messages */structexit_statusut_exit;/* Exit status of process marked as DEAD_PROCESS (not filled in by init(8) on Linux) */longut_session;/* Session ID */structtimevalut_tv;/* Time when entry was made */int32_tut_addr_v6[4];/* IP address of remote host (IPv4 address uses just ut_addr_v6[0], with other elements set to 0) */char__unused[20];/* Reserved for future use */};

💡 此结构定义可参见utmpx(5)。

utmpx 结构体中的各个字符串字段都以空字符结尾,除非字符串填满了整个数组。

对于登录进程,ut_line和ut_id字段保存的信息取自终端设备名。ut_line字段存放终端设备的完整文件名;ut_id字段存放文件名的后缀部分,也就是tty、pts或pty后面的字符串(后两者分别对应 System V 风格与 BSD 风格的伪终端)。
因此对于终端/dev/tty2,ut_line的值是tty2,ut_id的值是2。

在窗口化环境中,部分终端模拟器使用ut_session字段记录终端窗口的会话 ID。(会话 ID 的说明参见 34.3 节。)

ut_type字段是一个整数,用于定义写入文件的记录类型。以下常量(括号内为对应的数值)可作为该字段的取值:

EMPTY (0)
本条记录不包含有效的记账信息。

RUN_LVL (1)
本条记录表示系统在开机或关机过程中运行级别发生变更。(运行级别相关信息可参考init(8)手册页。)必须定义_GNU_SOURCE特性测试宏,才能从<utmpx.h>头文件获取该常量定义。

BOOT_TIME (2)
本条记录在ut_tv字段保存系统启动时间。RUN_LVL和BOOT_TIME类型记录通常由 init 进程生成,会同时写入 utmp 文件与 wtmp 文件。

NEW_TIME (3)
本条记录在ut_tv字段保存系统时钟修改后的新时间。

OLD_TIME (4)
本条记录在ut_tv字段保存系统时钟修改前的旧时间。当 NTP(或同类守护进程)修改系统时钟时,会向 utmp、wtmp 文件写入OLD_TIME和NEW_TIME类型的记录。

INIT_PROCESS (5)
代表由 init 派生的进程记录,例如 getty 进程。详细信息参见inittab(5)手册页。

LOGIN_PROCESS (6)
用户登录会话的会话首进程记录,例如login(1)进程。

USER_PROCESS (7)
用户进程记录,一般代表登录会话,用户名存放在ut_user字段。该登录会话可由login(1)启动,或是由提供远程登录能力的应用(如 ftp、ssh)创建。

DEAD_PROCESS (8)
本条标记已经退出的进程。

我们列出这些常量的数值,是因为各类应用程序依赖于上述常量保持这样的数字次序。例如,在 agetty 程序源码中,可以看到如下判断逻辑:

utp->ut_type >= INIT_PROCESS && utp->ut_type <= DEAD_PROCESS

INIT_PROCESS类型的记录通常对应 getty (8)(或同类程序,如 agetty (8)、mingetty (8))的调用。系统开机时,init 进程会为每一条终端线路和虚拟控制台创建子进程,每个子进程执行 getty 程序。getty 程序打开终端,提示用户输入登录名,随后执行 login (1)。login 程序成功校验用户身份并完成一系列其他操作后,会 fork 出子进程,加载用户的登录 Shell。

这样一次登录会话的完整生命周期,会按下面顺序向 wtmp 文件写入四条记录:

  • INIT_PROCESS记录:由 init 写入;
  • LOGIN_PROCESS记录:由 getty 写入;
  • USER_PROCESS记录:由 login 写入;
  • DEAD_PROCESS记录:当 init 检测到登录子进程终止时(用户登出时触发),由 init 写入。

40.4 Retrieving Information from the utmp and wtmp Files

本节介绍的函数用于从包含 utmpx 格式记录的文件中读取记录。默认情况下,这些函数操作标准 utmp 文件,但可以通过utmpxname()函数(下文介绍)切换目标文件。

这一组函数会维护一个文件内当前读取位置,读取记录就从该位置开始;每次调用函数后,这个位置会随之更新。

setutxent()函数将 utmp 文件的读取位置重置到文件开头。

#include<utmpx.h>voidsetutxent(void);

通常,在调用任意getutx*()系列函数(下文介绍)之前,都应当调用setutxent()。这可以避免一种潜在混乱:我们调用过的第三方函数可能先前已经使用过这一组接口。
根据所要完成的任务,程序后续在合适位置可能还需要再次调用setutxent()。

如果 utmp 文件尚未打开,setutxent()和getutx*()系列函数会自动打开该文件。当不再使用这个文件时,可以调用endutxent()将其关闭。

#include<utmpx.h>voidendutxent(void);

getutxent()、getutxid()和getutxline()函数从 utmp 文件读取一条记录,并返回指向静态分配的utmpx结构体的指针。

#include<utmpx.h>structutmpx*getutxent(void);structutmpx*getutxid(conststructutmpx*ut);structutmpx*getutxline(conststructutmpx*ut);Allreturna pointer to a statically allocated utmpx structure,orNULLifno matching record orEOFwas encountered

getutxent()函数从 utmp 文件读取下一条顺序记录。getutxid()和getutxline()从当前文件位置开始检索,查找与参数ut所指向的utmpx结构体中指定条件相匹配的记录。

getutxid()根据参数ut的ut_type和ut_id字段,在 utmp 文件中检索记录:

  • 若ut_type是RUN_LVL、BOOT_TIME、NEW_TIME或OLD_TIME,则getutxid()查找下一条ut_type与指定值相同的记录。(这类记录和用户登录无关),可用来检索系统时间变更、运行级别变更的记录。
  • 若ut_type是其余合法取值之一(INIT_PROCESS、LOGIN_PROCESS、USER_PROCESS、DEAD_PROCESS),则getutxid()查找下一条:ut_type属于上述类型并且ut_id和参数ut指定值一致的记录。利用该机制可以扫描文件,查找某一特定终端对应的记录。

getutxline()函数向前检索,寻找满足如下条件的记录:ut_type字段为LOGIN_PROCESS或USER_PROCESS,并且ut_line字段与参数ut指定的值一致。该函数适用于查找用户登录相关记录。

如果检索失败(读到文件末尾仍未找到匹配记录),getutxid()和getutxline()都会返回NULL。

在部分 UNIX 实现上,getutxline()和getutxid()将用于返回utmpx结构体的静态存储区视作缓存。如果函数判断,上一次getutx*()调用放入该缓存的记录与参数ut指定的检索条件匹配,则不会执行文件读取操作,直接再次返回这条记录(SUSv3 允许该行为)。
因此,若要在循环内调用getutxline()和getutxid(),避免重复返回同一条记录,必须先将这个静态结构体清零,示例代码如下:

structutmpx*res=NULL;/* Other code omitted */if(res!=NULL)/* If 'res' was set via a previous call */memset(res,0,sizeof(structutmpx));res=getutxline(&ut);

glibc 的实现不会采用这类缓存机制,但为保证代码可移植性,我们仍然应当使用该处理方式。

由于getutx*()系列函数返回指向静态分配结构体的指针,因此这些函数不可重入。GNU C 库为传统 utmp 函数提供了可重入版本(getutent_r()、getutid_r()和getutline_r()),但并未提供对应的 utmpx 系列可重入函数。(SUSv3 没有规范这些可重入版本。)

默认情况下,所有getutx*()函数都操作标准 utmp 文件。如果希望操作其他文件(例如 wtmp 文件),则必须先调用utmpxname(),传入目标文件路径。

#define_GNU_SOURCE#include<utmpx.h>intutmpxname(constchar*file);Returns0on success,or –1on error

utmpxname()函数仅仅保存传入路径名的一份副本。它不会打开文件,但会关闭此前由其他相关调用打开过的文件。这意味着:即使传入无效路径,utmpxname()本身不会返回错误;后续调用任意getutx*()函数尝试打开文件失败时,才会返回错误(即返回NULL,并将errno设置为ENOENT)。

虽然 SUSv3 并未规范该函数,但绝大多数 UNIX 实现都提供utmpxname(),或是功能相似的utmpname()函数。

Example program
代码清单 40-2 中的程序使用本节介绍的若干函数,转储一个 utmpx 格式文件的全部内容。下面的 shell 会话日志展示使用该程序读取/var/run/utmp的输出结果(若未调用utmpxname(),这些函数默认读取此文件):

$ ./dump_utmpx usertypePID lineidhostdate/timerebootBOOT_TIME0~ ~~5.15.0-31 Wed Sep3007:06:472026runlevel RUN_LVL51~ ~~5.15.0-31 Wed Sep3007:06:502026LOGIN LOGIN_PR1055tty1 tty1L Wed Sep3007:06:502026vagrant USER_PR15150pts/0 ts/0v10.0.2.2 Wed Sep3007:09:152026vagrant USER_PR15342pts/1 ts/1v10.0.2.2 Wed Sep3008:43:542026

为简洁起见,我们删减了程序输出的大部分内容。tty1对应的各行记录,是虚拟控制台(/dev/tty1)上的登录记录。输出的最后两行是伪终端上的 xterm 会话记录。

💡 tty 是内核给本地物理/虚拟控制台用的终端;pts 是内核给网络连接、图形窗口等“非物理终端”用的伪终端。当你用 PuTTY 从 Windows 连到远端 Linux 时:

你的 Windows ──SSH──> 远端 Linux 服务器 ├── tty1 ~ tty6 (服务器本地的虚拟控制台) └── pts/0, pts/1... (SSH 连接分配的伪终端)

下面是转储/var/log/wtmp得到的输出,可以看到:用户登录、登出时,会向 wtmp 文件写入两条记录(我们删减了程序其余全部输出)。通过顺序遍历 wtmp 文件(使用getutxline()),可借助ut_line字段将这两条记录配对。

$ ./dump_utmpx /var/log/wtmp|head-10usertypePID lineidhostdate/time... LOGIN LOGIN_PR816tty1 tty1L Fri Dec509:11:162025vagrant USER_PR40853pts/0 ts/0v10.0.2.2 Fri Dec509:26:592025DEAD_PR40853pts/0 Fri Dec509:27:162025...

Listing 40-2: Displaying the contents of a utmpx-format file

// loginacct/dump_utmpx.c// 略。

40.5 Retrieving the Login Name: getlogin()

getlogin()函数返回调用进程控制终端上登录的用户名。该函数读取 utmp 文件中维护的信息来获取结果。

#include<unistd.h>char*getlogin(void);Returns pointer to username string,orNULLon error

getlogin()函数会调用ttyname()(62.10 节),获取调用进程标准输入所关联终端的名称。随后它检索 utmp 文件,查找一条ut_line值与该终端名称匹配的记录。若找到匹配记录,getlogin()就返回该记录中的ut_user字符串。

如果找不到匹配项或发生错误,getlogin()返回NULL,并设置errno标识错误。getlogin()调用失败的一种情形:进程的标准输入没有关联终端(错误码ENOTTY),进程可能是守护进程。另一种可能:该终端会话没有在 utmp 中留下记录;例如,部分软件终端模拟器不会在 utmp 文件里创建条目。

还有一种少见场景:同一个用户 ID 在/etc/passwd中有多个登录名。此时getlogin()依然能返回本次终端实际登录所用的用户名,因为它依赖 utmp 文件。与之对比,getpwuid(getuid())总是从/etc/passwd返回第一条匹配记录,和实际登录时输入的用户名无关。

SUSv3 规范定义了getlogin()的可重入版本getlogin_r(),glibc 也实现了该函数。

环境变量LOGNAME同样可以用来获取用户登录名。但用户可以修改这个变量的值,因此不能依靠它安全地鉴别用户身份。

40.6 Updating the utmp and wtmp Files for a Login Session

编写用于创建登录会话的应用程序(类似login或sshd的工作方式)时,应按如下方式更新 utmp 和 wtmp 文件:

  • 用户登录时,需要向 utmp 文件写入一条记录,标记该用户已登录。应用程序必须检查 utmp 文件中是否已存在该终端对应的记录。若已有旧记录,则覆盖旧记录;若无,则向文件追加一条新记录。通常调用pututxline()(下文介绍)就足以正确完成上述步骤(示例见代码清单 40-3)。输出的utmpx记录至少要填充ut_type、ut_user、ut_tv、ut_pid、ut_id和ut_line字段。ut_type应设置为USER_PROCESS。ut_id存放用户登录所用设备(终端或伪终端)名称的后缀;ut_line存放登录设备名,要去掉开头的/dev/前缀。(这两个字段内容示例可参考代码清单 40-2 程序的运行样例。)同时,将一条内容完全相同的记录追加写入 wtmp 文件。

    终端名称(借助ut_line和ut_id字段)作为 utmp 文件中记录的唯一键。

  • 用户登出时,应当清除此前写入 utmp 文件的那条记录。做法是新建一条记录,将ut_type设置为DEAD_PROCESS,ut_id和ut_line的取值与登录时写入的记录保持一致,但把ut_user字段清零;用这条记录覆盖之前的登录记录。同时将一份相同的记录追加写入 wtmp 文件。

    如果登出时未能清理 utmp 记录(例如程序崩溃导致),那么下次系统重启时,init 进程会自动清理该记录:将它的ut_type修改为DEAD_PROCESS,并把记录其余多个字段清零。

utmp 和 wtmp 文件通常设有访问保护,仅特权用户能够修改这两个文件。getlogin()的正确性依赖 utmp 文件的完整性。出于这个以及其他原因,永远不应将 utmp、wtmp 的权限设置为允许非特权用户写入。

什么算作登录会话?正如我们所想,通过login、telnet、ssh 的登录都会记录到登录记账文件中。大多数 ftp 的实现也会创建登录记账记录。但有个问题:系统上打开的每一个终端窗口、或是执行su命令时,是否也会生成登录记账记录?
该问题的答案在不同 UNIX 实现上并不统一。

部分终端模拟器程序(例如 xterm),可以通过命令行选项或其他机制,控制程序是否更新登录记账文件。

pututxline()函数将ut指针指向的utmpx结构体写入/var/run/utmp;如果此前调用过utmpxname(),则写入用户指定的替代文件。

#include<utmpx.h>structutmpx*pututxline(conststructutmpx*ut);Returns pointer to copy of successfully updated record on success,orNULLon error

在写入记录之前,pututxline()会先调用getutxid()向前检索,寻找可以被覆盖的记录。如果找到该记录,则执行覆盖;否则,在文件末尾追加一条新记录。

很多场景下,应用程序会在调用pututxline()之前先调用某个getutx*()系列函数。该调用会把文件当前位置定位到目标记录,也就是与ut所指向utmpx结构体中getutxid风格检索条件相匹配的那条记录。如果pututxline()判断已经完成了定位,就不再内部调用getutxid()。

如果pututxline()在内部调用了getutxid(),这次内部调用不会修改getutx*()系列函数用于返回utmpx结构体的静态缓冲区。SUSv3 标准要求所有实现都必须遵守该行为。

更新 wtmp 文件时,只需打开文件并向其追加一条记录。由于这是一项标准操作,glibc 将其封装在updwtmpx()函数中。

#define_GNU_SOURCE#include<utmpx.h>voidupdwtmpx(char*wtmpx_file,structutmpx*ut);

updwtmpx()函数将ut指针指向的utmpx记录追加到参数wtmpx_file指定的文件中。

SUSv3 并未规定updwtmpx(),该函数仅在少数其他 UNIX 实现中存在。其他一些实现提供了相关函数:login(3)、logout(3)和logwtmp(3),glibc 同样包含这几个函数,相关说明可查阅手册页。

如果系统没有提供这类函数,就需要自行实现等价功能。(这些函数的实现逻辑并不复杂。)

Example program
代码清单 40-3 使用本节介绍的函数更新 utmp 和 wtmp 文件。该程序会按要求更新 utmp、wtmp,为命令行指定的用户完成登录记账;随后休眠数秒,再执行登出记账。正常情况下,这类操作对应用户登录会话的创建与终止。程序调用ttyname()获取某个文件描述符关联的终端设备名,ttyname()将在 62.10 节介绍。

下面的 shell 会话日志演示代码清单 40-3 程序的运行过程。我们需要特权权限,才能修改登录记账文件;然后使用该程序为用户 mtk 创建一条记录:

$sudo-s$ ./utmpx_login vagrant Creating login entriesinutmp and wtmp using pid15532, line pts/0,id/0# Type Control-Z to suspend program[1]+ Stopped ./utmpx_login vagrant

在utmpx_login程序休眠期间,我们按下 Ctrl+Z 将该程序挂起,并置入后台。随后,使用代码清单 40-2 中的程序查看 utmp 文件的内容:

$ ./dump_utmpx /var/run/utmp usertypePID lineidhostdate/timerebootBOOT_TIME0~ ~~5.15.0-31 Wed Sep3007:06:472026runlevel RUN_LVL51~ ~~5.15.0-31 Wed Sep3007:06:502026LOGIN LOGIN_PR1055tty1 tty1L Wed Sep3007:06:502026vagrant USER_PR15150pts/0 ts/0v10.0.2.2 Wed Sep3007:09:152026DEAD_PR15342pts/1 ts/1 Wed Sep3008:44:552026vagrant USER_PR15532pts/0 /0 Wed Sep3009:48:312026$whovagrant pts/02026-09-30 07:09(10.0.2.2)vagrant pts/02026-09-30 09:48

上面我们使用了who(1)命令,可以看出who的输出信息取自 utmp 文件。
接下来,用我们的程序查看 wtmp 文件的内容:

$ ./dump_utmpx /var/log/wtmp usertypePID lineidhostdate/time... vagrant USER_PR15532pts/0 /0 Wed Sep3009:48:312026$ last vagrant vagrant pts/0 Wed Sep3009:48 still loggedin...

上面我们使用last(1)命令,可以看出last的输出信息取自 wtmp 文件。
(为简洁起见,我们删减了本次 Shell 会话日志中dump_utmpx和last命令的输出,移除和本节讨论无关的行。)

接下来,执行fg命令,在前台恢复运行utmpx_login程序。随后该程序会向 utmp 和 wtmp 文件写入登出记录。

$fg./utmpx_login vagrant Creatinglogoutentriesinutmp and wtmp

随后我们再次查看 utmp 文件的内容,可以看到 utmp 中的那条记录已经被覆盖:

$ ./dump_utmpx /var/run/utmp usertypePID lineidhostdate/timerebootBOOT_TIME0~ ~~5.15.0-31 Wed Sep3007:06:472026runlevel RUN_LVL51~ ~~5.15.0-31 Wed Sep3007:06:502026LOGIN LOGIN_PR1055tty1 tty1L Wed Sep3007:06:502026vagrant USER_PR15150pts/0 ts/0v10.0.2.2 Wed Sep3007:09:152026DEAD_PR15342pts/1 ts/1 Wed Sep3008:44:552026DEAD_PR15532pts/0 /0 Wed Sep3009:56:232026$ $whovagrant pts/02026-09-30 07:09(10.0.2.2)

输出的最后一行表明,who会忽略类型为DEAD_PROCESS的记录。
当查看 wtmp 文件时,可以看到 wtmp 中追加了新记录:

$ ./dump_utmpx /var/log/wtmp usertypePID lineidhostdate/time... vagrant USER_PR15532pts/0 /0 Wed Sep3009:48:312026DEAD_PR15532pts/0 /0 Wed Sep3009:56:232026

上面输出的最后一行说明:last会匹配 wtmp 文件里的登录与登出记录,以此展示完整登录会话的起始和结束时间。

Listing 40-3: Updating the utmp and wtmp files

// loginacct/utmpx_login.c// 略。

40.7 The lastlog File

lastlog 文件记录每个用户最近一次登录系统的时间。(这与 wtmp 文件不同,wtmp 记录所有用户的每一次登录与登出事件。)
其中一个用途是:login程序在用户开启新登录会话时,借助 lastlog 向用户提示上次登录时间。提供登录服务的应用程序,除更新 utmp、wtmp 之外,也应当更新 lastlog。

与 utmp、wtmp 类似,lastlog 文件的存放路径和记录格式在不同系统上存在差异(少数 UNIX 实现没有提供该文件)。在 Linux 上,该文件位于/var/log/lastlog,头文件<paths.h>中定义常量_PATH_LASTLOG指向该路径。
和 utmp、wtmp 一样,lastlog 文件通常设置权限:所有用户可读,但仅特权进程能够修改。

lastlog 文件中的记录格式如下(在<lastlog.h>中定义):

#defineUT_NAMESIZE32#defineUT_HOSTSIZE256structlastlog{time_tll_time;/* Time of last login */charll_line[UT_NAMESIZE];/* Terminal for remote login */charll_host[UT_HOSTSIZE];/* Hostname for remote login */};

注意:这些记录并不包含用户名或用户 ID。lastlog 文件由一系列记录构成,以用户 ID 作为索引。因此,若要查找 UID 为 1000 用户的 lastlog 记录,需要将文件偏移定位到(1000 * sizeof(struct lastlog))字节处。

代码清单 40-4 演示了这一点,该程序可以查看命令行指定用户的 lastlog 记录,功能与lastlog(1)命令相近。下面是运行该程序的输出示例:

# 我的系统并没有annie用户$ ./view_lastlog annie vagrant annie Thu Jan100:00:001970vagrant pts/110.0.2.2 Wed Sep3008:43:542026

更新 lastlog 的操作方式类似:打开文件、定位到正确位置,然后执行写入。

由于 lastlog 文件以用户 ID 作为索引,无法区分同一 UID 下的不同用户名登录。(在 8.1 节中我们提到,虽然不常见,但确实可以存在多个登录名共用同一个用户 ID 的情况。)

Listing 40-4: Displaying information from the lastlog file

// loginacct/view_lastlog.c// 略。

40.8 Summary

登录记账用于记录当前在线用户以及全部历史登录记录。
相关信息保存在三个文件中:

  • utmp 文件:保存当前所有已登录用户的记录;
  • wtmp 文件:记录所有登录、登出事件的审计追踪日志;
  • lastlog 文件:记录每个用户最近一次的登录时间。

who、last等各类命令都会读取这几个文件里的信息。

C 标准库提供函数,用于读取和修改登录记账文件中的信息。提供登录服务的应用程序应当使用这些函数来更新登录记账文件,从而保证依赖该信息的各类命令可以正常工作。

Further information

除utmp(5)手册页之外,想要深入了解登录记账相关函数,最有价值的资料是各类使用这些函数的应用源码。例如:mingetty(或agetty)、login、init、telnet、ssh、ftp的源代码。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询