在Linux系统里,经常听到别人说ut、utmp、wtmp这类词,尤其在排查谁登录过服务器、为什么who命令看不到用户的时候。这里的ut并不是某个独立命令,而是user accounting中关于用户会话记录的一组接口与文件的简称,核心包括utmp(记录当前登录)、wtmp(记录历史登录注销)以及后来扩展的utmpx。它们由系统C库和工具共同维护,是Linux用户审计的基础。

一、utmp与wtmp到底是什么
utmp文件通常位于/var/run/utmp,它保存了系统当前所有活跃的用户会话信息,比如谁从哪个终端、哪个IP登录,登录时间是多少。系统启动时的init、用户登录时的login、远程连接的sshd,都会往这个文件里写一条记录;注销或断线时再更新或标记退出。因为只保留当前状态,所以文件一般不大。
wtmp文件一般在/var/log/wtmp,可以理解为utmp的追加日志版。每次utmp发生写入(登录或注销)时,相似内容会被追加到wtmp末尾,因此它能反映历史记录。last命令就是读取wtmp来显示曾经登录过的用户、重启时间等。这两个文件都是二进制结构,不能直接cat,需要用who、last或者专门程序解析。
1.1 utmpx的出现原因
传统utmp结构在不同Unix版本里字段长度和布局不一致,移植程序很麻烦。后来定义了utmpx(extended utmp),通过额外函数和头文件提供更统一的记录方式。Linux下utmpx往往和utmp共用底层文件,只是结构体更规范,例如增加了更长的用户名、终端名以及会话ID等字段,减少溢出风险。
在glibc中,打开utmpx会使用同样路径,程序调用getutxent、pututxline等函数操作记录。对应用开发者来说,直接依赖utmpx接口比自己解析utmp更稳妥,也能兼容不同发行版。这也是为什么很多新工具源码里看到的是utmpx而不是老utmp。
二、核心数据结构与字段含义
在Linux上,相关结构定义在头文件utmp.h或utmpx.h中。以utmpx为例,主要字段包括ut_type(记录类型,如USER_PROCESS表示普通用户登录)、ut_pid(进程号)、ut_line(终端名,如pts/0)、ut_id(终端标识)、ut_user(用户名)、ut_host(来源主机)、ut_tv(时间戳)。理解这些字段才能明白who和last显示内容的来源。
下面是一段简化版的C代码示例,展示如何读取当前utmpx记录并打印登录用户。注意其中的小于号和大于号都已经转义,符合文本代码块要求。
#include <stdio.h>
#include <utmpx.h>
#include <time.h>
int main() {
struct utmpx *u;
setutxent(); // 打开utmpx文件并移到开头
while ((u = getutxent()) != NULL) {
if (u->ut_type == USER_PROCESS) {
printf("user: %s, line: %s, host: %sn",
u->ut_user, u->ut_line, u->ut_host);
}
}
endutxent(); // 关闭文件
return 0;
}
2.1 记录类型ut_type的常见值
ut_type决定了这条记录表达什么事件。EMPTY表示空记录;RUN_LVL记录系统运行级别变化;BOOT_TIME是系统启动时间;NEW_TIME和OLD_TIME与系统时钟修改有关;INIT_PROCESS、LOGIN_PROCESS代表初始化或登录进程;USER_PROCESS就是真正用户会话;DEAD_PROCESS表示进程结束。who只关心USER_PROCESS,而last会把启动、重启也列出来。
如果某个程序异常退出没有写DEAD_PROCESS,utmp里就可能残留假在线用户,导致who显示已经断线的人。这时候手动清理或重启相关服务才管用,也说明ut文件靠应用自觉维护,并非内核强制保证一致。
三、常用命令如何依赖ut文件
who命令读取utmp,列出当前登录会话;w在who基础上附加CPU和正在执行的命令;last读取wtmp,显示历史;lastb读btmp记录失败登录尝试。它们本质都是封装了C库读取函数,把二进制翻译成人能看的表格。在容器或最小系统里,如果相关文件没挂载或没被写入,这些命令就会输出空或报错。
例如下面用shell查看wtmp最后五行,需要专门工具因为不能直接看二进制:
# 显示最近五次登录历史 last -n 5 # 查看当前登录用户 who # 检查失败登录(需要btmp存在) lastb
3.1 为什么容器里who经常为空
很多Docker镜像为了精简不会运行login或utmp维护机制,也没有真实tty分配流程,于是/var/run/utmp要么不存在要么始终为空。此时who只能返回无结果,但这不代表没人用容器,只是没有走传统会话记录通道。若需要在容器内审计,应改用进程列表或集中式日志,而非依赖ut机制。
同理,某些无状态服务器禁用wtmp写入来减少磁盘IO,历史登录就查不到。运维时要确认文件权限和挂载情况,避免误判为安全事件。
四、编程中如何正确使用ut接口
写需要登记会话的程序时,不要直接open utmp文件写字节,而应使用C库提供的pututxline等函数。它们会加锁、处理结构对齐,并同步到wtmp(通过updwtmpx)。例如写一个简易登录记录器,应在验证用户后填充utmpx结构再提交,退出前写DEAD_PROCESS。
下面示例展示如何写一条用户登录记录:
#include <utmpx.h>
#include <string.h>
void write_login(const char *user, const char *line) {
struct utmpx u;
memset(&u, 0, sizeof(u));
u.ut_type = USER_PROCESS;
strncpy(u.ut_user, user, sizeof(u.ut_user) - 1);
strncpy(u.ut_line, line, sizeof(u.ut_line) - 1);
gettimeofday(&u.ut_tv, NULL);
pututxline(&u); // 写入utmpx并追加wtmpx
}
4.1 权限与并发问题
utmp文件通常权限为644,但写入需要组或特定守护进程协助;直接让普通程序写可能失败。生产环境一般由login、systemd-logind等统一维护。自己开发工具时应避免破坏已有记录,使用库函数而非裸写,并在异常退出时尽量补写退出标记,减少脏数据。
高并发场景下多个会话同时登录,库内部通过文件锁保证不互相覆盖。理解这一点,就不会试图用shell脚本每秒echo重定向去伪造utmp,那样极易损坏二进制结构。
五、总结与排查建议
回到开头的问题,Linux中的ut是指用户记账(user accounting)里围绕utmp/wtmp的那套机制和文件,utmpx是其现代化扩展。它们不是内核强制日志,而是用户态程序协作结果。排查登录异常时,先确认文件存在与权限,再用last、who交叉验证;程序开发优先调用标准库,别手动解析二进制。
掌握这些概念,不仅能读懂系统命令输出,还能在自研审计组件时少踩坑,也明白为什么某些环境下传统命令会失效。对安全运维而言,ut文件是辅助线索,真正可靠审计仍要结合auth日志与网络层记录。