Serenity OS lsdir:基于 get_dir_entries 系统调用的底层目录项枚举工具
2026/9/10 11:57:36 网站建设 项目流程

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 usesLibCoreDirIteratorobject 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_REGDT_DIR)显示文件类型;关闭时显示人类可读名称(如FileDirectory
-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);

两个值得注意的实现细节:

  1. 位置参数为Required::No:不传路径时,程序自动以.(当前工作目录)作为枚举对象,这与手册页示例$ lsdir的行为一致;
  2. pathVector<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声明自己只使用stdiorpath(按路径解析访问文件系统)两类能力,之后任何超出该承诺的调用都会被拒绝。这也解释了为什么它“只受限于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++; } }

DirIteratorNoStat标志定义于 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_modedirectory_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人类可读名(默认)
BlockDeviceDT_BLKBlockDevice
CharacterDeviceDT_CHRCharacterDevice
DirectoryDT_DIRDirectory
FileDT_REGFile
NamedPipeDT_FIFONamedPipe
SocketDT_SOCKSocket
SymbolicLinkDT_LNKSymbolicLink
UnknownDT_UNKNOWNUnknown
WhiteoutDT_WHTWhiteout

其中WhiteoutDT_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_UNKNOWNfrom_dirent对未知dt值会落入VERIFY_NOT_REACHED,实际行为取决于具体文件系统填充情况)。
  • ls的分工ls面向人类展示目录内容与文件元信息;lsdir面向需要观察“内核返回的原始 dirent”的场景,例如验证某个文件系统是否正确填充d_typed_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),仅供参考

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

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

立即咨询