SerenityOS stat 命令完全指南:深入解析文件状态查询工具的实现与用法
导读
stat 是 SerenityOS 用户态中的一款基础文件查询工具,用于显示指定文件的详细状态信息,包括 inode 编号、文件大小、链接数、属主/属组、权限模式以及访问/修改/变更时间。本文以 SerenityOS 官方手册 Base/usr/share/man/man1/stat.md 为骨架,结合其源码实现 Userland/Utilities/stat.cpp、内核系统调用 Kernel/Syscalls/stat.cpp 与相关测试,带你同时掌握 stat 的实战用法与底层原理,读完即可熟练诊断文件类型、权限与时间戳信息。
命令概览:Synopsis 与核心用途
手册给出的命令形态为:
$ stat [-L] file
其核心语义是"Display file status"——显示文件状态,并提供关于该文件的更多信息。与 ls -l 只展示目录条目不同,stat 直接针对给定的路径查询文件系统的元数据(metadata),并以结构化、人类可读的方式输出,是排查文件权限问题、确认符号链接指向、核对时间戳的得力工具。
从源码看,stat 实际支持多个文件参数(位置参数被定义为 Vector<StringView> files),因此一次可以查询多个目标:
args_parser.add_positional_argument(files, "File(s) to stat", "file", Core::ArgsParser::Required::Yes);
也就是说,stat file1 file2 的用法在实现层面完全合法,手册仅展示了最简形式。
选项详解:-L、--help 与 --version
手册列出的选项如下:
-L:Follow links to files(跟随符号链接,查询链接指向的目标文件)--help:显示帮助信息并退出--version:打印版本信息
--help 与 --version 是 SerenityOS 命令行工具的通用约定,由参数解析库 Userland/Libraries/LibCore/ArgsParser.h 统一提供,几乎所有 Userland 工具(如 ls、du、file)都遵循这一约定。
-L 的底层语义:stat 与 lstat 的分野
-L 选项是理解 stat 的关键。在源码 Userland/Utilities/stat.cpp 中,选项被解析后直接决定调用哪个系统调用封装:
auto st = TRY(should_follow_links ? Core::System::stat(file) : Core::System::lstat(file));
- 不带
-L:调用lstat,查询符号链接本身的元数据(链接文件自身的 inode、大小、类型为l); - 带
-L:调用stat,跟随符号链接,返回链接最终指向目标文件的元数据。
这两个封装定义于 Userland/Libraries/LibCore/System.h:
ErrorOr<struct stat> stat(StringView path);
ErrorOr<struct stat> lstat(StringView path);
默认不跟随链接的原因
默认不跟随符号链接(即默认 lstat 行为)是 POSIX 体系的常见设计,目的是让用户能精确检查链接本身(例如确认链接是否悬空、链接自身的 inode 与时间戳),避免在链接指向不存在或不可访问的目标时误报错误。这也正是手册示例中使用 stat -L /bin/ls 的原因——/bin/ls 在 SerenityOS 中通常是符号链接,加 -L 才能看到最终可执行文件的真实状态。
输出字段逐项解析
手册给出的示例输出(stat -L /bin/ls):
$ stat -L /bin/ls
File: /bin/ls
Inode: 8288
Size: 184896
Links: 1
Blocks: 376
UID: 0 (root)
GID: 0 (root)
Mode: (100755/-rwxr-xr-x)
Accessed: 2022-10-20 03:56:56
Modified: 2022-10-20 03:55:34
Changed: 2022-10-20 03:56:57
结合 Userland/Utilities/stat.cpp 的打印逻辑,各字段含义如下:
| 字段 | 输出示例 | 对应 struct stat 成员 | 含义 |
|---|---|---|---|
File |
/bin/ls |
— | 传入的路径名 |
Device |
见下文 | st_dev |
文件所在设备的设备号 |
Inode |
8288 |
st_ino |
inode 编号,同一文件系统内唯一标识文件 |
Size |
184896 |
st_size |
文件字节大小;对字符/块设备则替换为设备号输出 |
Links |
1 |
st_nlink |
硬链接数量 |
Blocks |
376 |
st_blocks |
文件占用的磁盘块数 |
UID |
0 (root) |
st_uid |
属主用户 ID 及用户名 |
GID |
0 (root) |
st_gid |
属主组 ID 及组名 |
Mode |
(100755/-rwxr-xr-x) |
st_mode |
文件类型 + 权限位的数字与符号双重表示 |
Accessed |
2022-10-20 03:56:56 |
st_atim |
最后访问时间 |
Modified |
2022-10-20 03:55:34 |
st_mtim |
最后修改时间(内容变更) |
Changed |
2022-10-20 03:56:57 |
st_ctim |
状态变更时间(元数据变更,如 chmod/chown) |
时间戳的特殊处理:纳秒精度
Accessed、Modified、Changed 三个时间戳在实现上并非简单地打印秒数,而是由 lambda 输出带纳秒部分的时间(Userland/Utilities/stat.cpp):
auto print_time = [](timespec t) {
outln("{}.{:09}", Core::DateTime::from_timestamp(t.tv_sec).to_byte_string(), t.tv_nsec);
};
可见其使用 timespec 结构,时间字符串后接 . 与 9 位纳秒补零输出。示例输出中的时间之所以未显示纳秒,是因为手册记录时省略了后续精度字段;实际运行时(如对新建文件执行 stat)会看到类似 2026-09-09 03:32:23.123456789 的完整格式。这也侧面印证了 SerenityOS 文件系统对高精度时间戳的完整支持。
权限模式的符号渲染逻辑
Mode 字段是输出中最"精密"的部分,源码在 Userland/Utilities/stat.cpp 中手工渲染:
- 先以八进制打印完整
st_mode(如100755,其中前导1表示普通文件类型位); - 再根据
S_ISDIR/S_ISLNK/S_ISBLK/S_ISCHR/S_ISFIFO/S_ISSOCK/S_ISREG宏判定类型字符:d(目录)、l(符号链接)、b(块设备)、c(字符设备)、f(FIFO)、s(套接字)、-(普通文件)、?(未知); - 随后逐位输出 9 个权限字符:读
r、写w;执行位遇S_ISUID/S_ISGID时显示s(setuid/setgid),否则显示x或-;末位遇S_ISVTX(粘滞位)显示t,否则显示x或-。
因此 -rwxr-xr-x 表示"普通文件、属主可读写执行、属组可读执行、其他可读执行",与 ls -l 的显示约定一致。
实战:常见使用场景
1. 查看文件详细信息
$ stat /etc/passwd
File: /etc/passwd
Inode: 36869
Size: 1412
Links: 1
Blocks: 8
UID: 0 (root)
GID: 0 (root)
Mode: (100644/-rw-r--r--)
Accessed: 2026-09-09 02:10:11.000000000
Modified: 2026-09-09 02:10:11.000000000
Changed: 2026-09-09 02:10:11.000000000
2. 检查符号链接本身 vs 其目标
$ stat /bin/sh # 查看链接本身(类型为 l,大小较小)
$ stat -L /bin/sh # 查看链接指向的实际 shell(类型为 -,大小较大)
对比两次输出的 Mode 首字符(l vs -)与 Size,即可快速判断链接的指向关系。
3. 一次性查询多个文件
$ stat file1.txt file2.txt
每个文件依次输出一组字段块,任一文件出错(如不存在)时,程序会继续处理后续文件,并在最后以非零退出码结束——见 Userland/Utilities/stat.cpp 的错误处理循环:
bool had_error = false;
for (auto& file : files) {
auto r = stat(file, should_follow_links);
if (r.is_error()) {
had_error = true;
warnln("stat: cannot stat '{}': {}", file, strerror(r.error().code()));
}
}
return had_error;
对不存在的路径,stderr 会输出 stat: cannot stat 'xxx': No such file or directory 之类的诊断信息。
4. 查看设备文件的设备号
对字符设备或块设备,stat 不打印 Size,而是打印主/次设备号(Userland/Utilities/stat.cpp):
if (S_ISCHR(st.st_mode) || S_ISBLK(st.st_mode))
outln(" Device: {},{}", major(st.st_rdev), minor(st.st_rdev));
else
outln(" Size: {}", st.st_size);
例如 stat /dev/tty 会输出类似 Device: 4,0 的主次设备号,同时 Device(st_dev)字段仍保留文件所在设备的编号。
源码级原理:stat 的完整调用链
用户态:pledge 与参数解析
stat 的入口是 serenity_main(Userland/Utilities/stat.cpp),首先执行系统调用承诺:
TRY(Core::System::pledge("stdio rpath"));
pledge 声明该进程只需要 stdio(标准 I/O)与 rpath(只读路径访问)两类权限,这是 SerenityOS 的安全机制——即使程序被攻破,也无法执行写入类系统调用。
随后用 Core::ArgsParser 解析 -L 与位置参数,再逐文件调用内部的 stat() 函数。
内核态:sysfstat
用户态的 Core::System::stat/lstat 最终陷入内核系统调用。内核实现在 Kernel/Syscalls/stat.cpp:
ErrorOr<FlatPtr> Process::sys$stat(Userspace<Syscall::SC_stat_params const*> user_params)
{
TRY(require_promise(Pledge::rpath));
auto params = TRY(copy_typed_from_user(user_params));
auto path = TRY(get_syscall_path_argument(params.path));
CustodyBase base(params.dirfd, path->view());
auto metadata = TRY(VirtualFileSystem::lookup_metadata(
vfs_root_context(), credentials(), path->view(), base,
params.follow_symlinks ? 0 : O_NOFOLLOW_NOERROR));
auto statbuf = TRY(metadata.stat());
TRY(copy_to_user(params.statbuf, &statbuf));
return 0;
}
关键点:
- 权限检查:内核要求进程持有
rpathpledge,与用户态pledge("stdio rpath")一一对应; - 符号链接策略:
follow_symlinks参数为真时传0,否则传O_NOFOLLOW_NOERROR——这就是-L在内核层面的最终落点; - 元数据获取:通过
VirtualFileSystem::lookup_metadata在虚拟文件系统层查找路径对应的 inode 元数据,再由metadata.stat()填充struct stat; - 跨进程拷贝:用
copy_to_user把内核态statbuf安全地写回用户态缓冲区。
同文件还实现了基于文件描述符的 sys$fstat(Kernel/Syscalls/stat.cpp),它从 open_file_description(fd) 直接取描述符对应的文件元数据,不涉及路径解析——这也是 fstat 不受符号链接影响、且可用于已打开文件的原因。
测试佐证
内核文件权限测试 Tests/Kernel/TestKernelFilePermissions.cpp 与 Tests/Kernel/TestKernelFilePermissions.cpp 分别用 fstat 验证打开文件后的元数据读取、用 lstat 验证符号链接自身的状态查询,与 stat 工具内部的 lstat/stat 选择逻辑互相印证,可作为理解系统调用语义的补充材料。
在 SerenityOS 中查看本文对应手册
SerenityOS 内置 man 页面查看器,运行:
$ man stat
即可在终端内阅读与 Base/usr/share/man/man1/stat.md 同源的格式化手册页(含 Name、Synopsis、Description、Options、Examples 五节)。同时,该手册文件也随系统镜像分发到 /usr/share/man/man1/stat.md,其他命令的同类手册位于同一目录(如 man1/ls.md、man1/du.md 等)。
小结
stat 虽然是一个"小工具",但它串联了 SerenityOS 的多层设计:ArgsParser 的参数解析、pledge 权限模型、lstat/stat 的符号链接语义、虚拟文件系统层的元数据查询,以及 struct stat 中 inode、权限位、纳秒时间戳的完整表达。掌握它,等于同时理解了一条 SerenityOS 用户态→内核态的文件元数据查询链路——这份能力在调试权限问题、诊断挂载设备、排查时间戳异常时都能直接复用。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0631
MiniCPM5-2BMiniCPM5-2B 是一款面向端侧、本地部署和资源受限场景的 2B 稠密 Transformer,能够达到同尺寸开源模型 SOTA 水平。Markdown00
video-shotcraftAI宣传片skill,使用 Remotion 制作电影级产品视频:提供106 张镜头配方卡和可复用的视频魔板。适用于 Claude Code 与 Codex以及所有其他智能体Markdown00
HivisionIDPhotos⚡️HivisionIDPhotos: a lightweight and efficient AI ID photos tools. 一个轻量级的AI证件照制作算法。Python09
DragonOSDragonOS is an operating system developed from scratch using Rust, with Linux compatibility. It is designed for **Serverless** scenarios. 使用Rust从0自研内核,具有Linux兼容性的操作系统,面向云计算Serverless场景而设计。Rust00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00