Serenity OS lsdir:基于 get_dir_entries 系统调用的底层目录项枚举工具
【免费下载链接】serenityThe Serenity Operating System 🐞项目地址: https://gitcode.com/GitHub_Trending/se/serenity
本文介绍 Serenity OS 用户态实用工具lsdir:它通过LibCore::DirIterator直接读取目录项(dirent)的原始字段,输出每个条目的名称、POSIXDT_*或人类可读的文件类型、以及 inode 号。读完本篇,你将掌握lsdir的完整命令语法与选项行为,并理解其“仅依赖get_dir_entries系统调用、不做逐条fstat”的实现原理。
1. 工具定位:列出目录项而非文件信息
lsdir的手册页位于 listdir.md,其定义为:
lsdir - list directory entries
用法概要:
# lsdir [options...] [path...]与常见的ls不同,lsdir关注的不是文件属性(大小、权限、时间戳),而是目录项本身最底层的三要素:条目名(name)、文件类型(type)、inode 号(inode number)。文件类型既可以用 POSIXDT_*常量格式输出,也可以用人类可读的名称输出。
手册页明确指出其实现路径:
The utility uses
LibCoreDirIteratorobject and restrict its functionality to theget_dir_entriessyscall only, to get the raw values of each directory entry.
也就是说,它刻意把功能收敛到一次get_dir_entries系统调用上,拿到的是 dirent 结构中的“原始值”(raw values),而不是通过stat二次推断出来的结果。这一点在下面第 4 节的源码分析中会展开。
2. 选项与参数详解
手册页定义了 2 个选项和 1 个位置参数,对应源码 listdir.cpp 中的注册代码:
| 选项 | 长选项名 | 默认值 | 作用 |
|---|---|---|---|
-P | --posix-names | 关闭 | 以 POSIX 常量名(如DT_REG、DT_DIR)显示文件类型;关闭时显示人类可读名称(如File、Directory) |
-t | --total-entries-count | 关闭 | 遍历完每个目录后,打印该目录实际列出的条目总数 |
path | —(位置参数,可重复,非必填) | .(当前工作目录) | 要列出的目录路径,可一次传入多个 |
源码中选项注册与默认值逻辑(listdir.cpp#L22-L32):
Vector<StringView> paths; Core::ArgsParser args_parser; args_parser.set_general_help("List Dirent entries in a directory."); args_parser.add_option(flag_show_unix_posix_file_type, "Show POSIX names for file types", "posix-names", 'P'); args_parser.add_option(flag_show_total_count, "Show total count for each directory being iterated", "total-entries-count", 't'); args_parser.add_positional_argument(paths, "Directory to list", "path", Core::ArgsParser::Required::No); args_parser.parse(arguments); if (paths.is_empty()) paths.append("."sv);两个值得注意的实现细节:
- 位置参数为
Required::No:不传路径时,程序自动以.(当前工作目录)作为枚举对象,这与手册页示例$ lsdir的行为一致; path是Vector<StringView>类型的位置参数:因此可以一次传入多个路径,程序会按传入顺序逐个遍历,每处理一个目录就打印一行Traversing <path>分隔头。
3. 手册页示例及输出行为
手册页给出的官方示例(listdir.md#L31-L40):
# List directory entries of working directory $ lsdir # List directory entries of /proc directory $ lsdir /proc # List directory entries of /proc directory with POSIX names for file types $ lsdir -P /proc # List directory entries of /proc directory and print in the end the count of traversed entries $ lsdir -t /proc结合源码可以精确描述其输出格式(listdir.cpp#L42-L62):
- 每个路径先输出一行
Traversing <path>; - 随后每个目录项输出一行固定格式:
<name> (Type: <type>, Inode number: <ino>),例如普通文件在默认模式下显示为Type: File,加-P后显示为Type: DT_REG; - 若启用
-t,遍历结束后追加一行Directory <path> has <count> which has being listed during the program runtime,其中count是实际成功枚举到的条目数; - 若某个路径打开失败,程序向 stderr 输出
Failed to open <path> - <error>并以该错误码退出,不会继续处理剩余路径。
一个典型运行效果(对某目录执行lsdir -P时的示意格式):
Traversing /proc 1 (Type: DT_DIR, Inode number: 2) self (Type: DT_LNK, Inode number: 3) cpuinfo (Type: DT_REG, Inode number: 4)(具体条目内容取决于运行时/proc的实际状态,此处仅展示输出格式。)
4. 源码剖析:从 pledge 到 DirIterator(NoStat)
lsdir的实现只有约 70 行(listdir.cpp),完整调用链为:
main → pledge → ArgsParser 解析 → DirIterator(NoStat) 循环 → DirectoryEntry 类型名映射 → 计数输出
4.1 沙箱限制:pledge
函数体第一步(listdir.cpp#L20):
TRY(Core::System::pledge("stdio rpath"));在 Serenity 的 pledge 机制下,lsdir声明自己只使用stdio和rpath(按路径解析访问文件系统)两类能力,之后任何超出该承诺的调用都会被拒绝。这也解释了为什么它“只受限于get_dir_entries一类只读目录遍历操作”——它既没有网络权限,也没有创建文件的能力。
4.2 核心循环:NoStat 模式的 DirIterator
Core::DirIterator di(path, Core::DirIterator::NoStat); ... while (di.has_next()) { auto dir_entry = di.next(); if (dir_entry.has_value()) { outln(" {} (Type: {}, Inode number: {})", dir_entry.value().name, name_from_directory_entry_type(dir_entry.value().type), dir_entry.value().inode_number); count++; } }DirIterator的NoStat标志定义于 DirIterator.h#L19-L24:
enum Flags { NoFlags = 0x0, SkipDots = 0x1, SkipParentAndBaseDir = 0x2, NoStat = 0x4, };NoStat的关键意义在于类型信息的来源。对照 DirectoryEntry.cpp 中的两条构造路径:
from_stat()(第 114-123 行):对每条目录项执行fstat(dirfd(d), &statbuf),再从st_mode用directory_entry_type_from_stat()推断类型——每个条目都要额外一次系统调用;from_dirent()(第 126-134 行):直接取dirent结构内嵌的de.d_type字段并映射,不产生额外fstat。
从源码结构看,lsdir传入NoStat后走的是“直接消费 dirent 原始字段”的路径,这与手册页“restrict its functionality to the get_dir_entries syscall only”的表述完全对应:类型来自 dirent 自带的d_type,inode 号来自d_ino,一次get_dir_entries返回的批数据即可同时覆盖两者。
而这条 dirent 数据的最终来源,在 LibC 的目录读取实现中可以确认——dirent.cpp#L115:
ssize_t nread = syscall(SC_get_dir_entries, dirp->fd, dirp->buffer, size_to_allocate);即readdir类操作在 Serenity 下并非逐条读目录项,而是向内核发起SC_get_dir_entries,由内核按批次把多条struct dirent填入用户态缓冲区,lsdir消费的就是这批原始数据。
4.3 文件类型的双格式映射
-P选项切换的是类型名的渲染函数(listdir.cpp#L45-L49):
Function<StringView(Core::DirectoryEntry::Type)> name_from_directory_entry_type; if (flag_show_unix_posix_file_type) name_from_directory_entry_type = Core::DirectoryEntry::posix_name_from_directory_entry_type; else name_from_directory_entry_type = Core::DirectoryEntry::representative_name_from_directory_entry_type;两个映射函数定义于 DirectoryEntry.cpp#L12-L60,覆盖Core::DirectoryEntry::Type枚举(DirectoryEntry.h#L15-L30)的全部 9 个取值:
| Type 枚举值 | POSIX 名(-P) | 人类可读名(默认) |
|---|---|---|
BlockDevice | DT_BLK | BlockDevice |
CharacterDevice | DT_CHR | CharacterDevice |
Directory | DT_DIR | Directory |
File | DT_REG | File |
NamedPipe | DT_FIFO | NamedPipe |
Socket | DT_SOCK | Socket |
SymbolicLink | DT_LNK | SymbolicLink |
Unknown | DT_UNKNOWN | Unknown |
Whiteout | DT_WHT | Whiteout |
其中Whiteout(DT_WHT)是 overlayfs 等联合文件系统使用的白洞类型标记,出现在 Serenity 的类型枚举中,说明get_dir_entries传递的类型位宽考虑了 POSIX 之外的扩展取值。
4.4 -t 计数逻辑
-t的统计口径值得注意:计数变量count只在di.next()返回有效值时自增(listdir.cpp#L51-L59),即统计的是程序运行期间实际成功枚举到的条目数,而不是“目录里应有的条目数”。这也与源码输出文案“which has being listed during the program runtime”的措辞一致。
5. 适用前提与相关工具
- 适用前提:
lsdir是 Serenity 用户态工具,源码位于 Userland/Utilities/listdir.cpp,构建时随用户态 Utilities 一起编译;其-P选项依赖 dirent 自带d_type的有效性——若某文件系统实现不在 dirent 中填充d_type,条目会显示为Unknown/DT_UNKNOWN(from_dirent对未知dt值会落入VERIFY_NOT_REACHED,实际行为取决于具体文件系统填充情况)。 - 与
ls的分工:ls面向人类展示目录内容与文件元信息;lsdir面向需要观察“内核返回的原始 dirent”的场景,例如验证某个文件系统是否正确填充d_type与d_ino。 - 相关手册页:ls(1)。
6. 小结
lsdir是 Serenity OS 中一个典型的“小而正”的底层工具:它用pledge收窄自身权限,用DirIterator::NoStat避开逐条fstat,让每一个目录条目的名称、类型、inode 号都直接来自SC_get_dir_entries返回的原始 dirent 数据。理解它的实现,实际上就是理解 Serenity 用户态目录遍历链路DirIterator → LibC readdir → get_dir_entries syscall → 内核目录项批处理的一次完整走查。
【免费下载链接】serenityThe Serenity Operating System 🐞项目地址: https://gitcode.com/GitHub_Trending/se/serenity
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考