diff --git a/.gitignore b/.gitignore index 35347d40de..dd14ccd621 100644 --- a/.gitignore +++ b/.gitignore @@ -29,3 +29,6 @@ config/rootfs.generated.toml *.cpio.xz *.cpio* .direnv/ + +*.md +.cursor diff --git a/docs/kernel/filesystem/index.rst b/docs/kernel/filesystem/index.rst index 2c5a538b94..e1e9314c0d 100644 --- a/docs/kernel/filesystem/index.rst +++ b/docs/kernel/filesystem/index.rst @@ -12,6 +12,7 @@ todo: 由于文件系统模块重构,文档暂时不可用,预计在2023年4 overview fuse vfs/index + proc/index sysfs kernfs unionfs/index diff --git a/docs/kernel/filesystem/proc/index.rst b/docs/kernel/filesystem/proc/index.rst new file mode 100644 index 0000000000..0d0999560c --- /dev/null +++ b/docs/kernel/filesystem/proc/index.rst @@ -0,0 +1,17 @@ +==================================== +ProcFS +==================================== + +ProcFS 用于向用户态导出内核中的进程、挂载、命名空间等运行时信息。 + +当前目录主要介绍 DragonOS 中与挂载导出相关的 proc 接口实现,包括: + +- `/proc/mounts` +- `/proc/[pid]/mounts` +- `/proc/[pid]/mountinfo` +- `/proc/[pid]/mountstats` + +.. toctree:: + :maxdepth: 1 + + mounts diff --git a/docs/kernel/filesystem/proc/mounts.md b/docs/kernel/filesystem/proc/mounts.md new file mode 100644 index 0000000000..95d8f26001 --- /dev/null +++ b/docs/kernel/filesystem/proc/mounts.md @@ -0,0 +1,220 @@ +# Proc 挂载导出接口 + +## 1. 概述 + +DragonOS 通过 procfs 向用户态导出挂载命名空间视图,主要入口如下: + +| 路径 | 类型 | 视角 | +|------|------|------| +| `/proc/mounts` | 符号链接 → `self/mounts` | 当前读取进程 | +| `/proc/self/mounts` | 普通文件 | 当前读取进程 | +| `/proc/[pid]/mounts` | 普通文件 | 目标 `pid` | +| `/proc/[pid]/mountinfo` | 普通文件 | 目标 `pid` | +| `/proc/[pid]/mountstats` | 普通文件 | 目标 `pid` | + +其中: + +- **`mounts`**:传统格式,字段少,兼容 `mount(8)`、shell 脚本等。 +- **`mountinfo`**:现代格式,含 mount id、父子关系、传播标签、superblock 选项等。 +- **`mountstats`**:每个 mount 一行描述前缀,并可追加文件系统自定义统计(`proc_show_mount_stats`)。 + +传播类型(`shared` / `master` / `propagate_from` / `unbindable`)**仅出现在 `mountinfo` 的 optional 字段**,不会写入 `/proc/*/mounts` 的普通 option 列。 + +## 2. 各接口的功能作用 + +### 2.1 `/proc/mounts` 与 `/proc/self/mounts` + +`/proc/mounts` 在实现上是 **指向 `self/mounts` 的符号链接**(`readlink` 结果为 `self/mounts`),解析后等价于读取 `/proc/self/mounts`,即 **当前读取进程** 在其 mount namespace 与 `fs root` 下的挂载列表。 + +每一行通常包含: + +- 设备名(或文件系统名) +- 挂载点 +- 文件系统类型 +- 挂载选项(`rw` 及 `nosuid,nodev,...` 等 per-mount 选项;不含传播标签) +- 两个兼容字段 `0 0` + +### 2.2 `/proc/[pid]/mounts` + +格式与 `/proc/self/mounts` 相同,但 **open 时绑定目标线程组 leader** 的 `mnt_ns` 与 `fs_struct.root()`,导出的是目标进程视角下的可见挂载。 + +### 2.3 `/proc/[pid]/mountinfo` + +在 `mounts` 基础上增加: + +- mount id、parent mount id +- 主设备号(`major:minor`) +- mount root(`proc_show_mountinfo_root`) +- per-mount options 与 superblock 选项(以 `-` 分隔的两段 optional 字段) +- propagation tagged fields(`MountPropagation::proc_mountinfo_tags()`) +- 文件系统类型名 + +### 2.4 `/proc/[pid]/mountstats` + +每个可见 mount 至少一行: + +```text +device mounted on with fstype +``` + +若底层文件系统通过 `proc_show_mount_stats` 返回额外内容,则追加在同一行末尾。权限为 **0400**(仅 owner 可读),与 `mounts` / `mountinfo` 的 0444 不同。 + +## 3. 内核源码布局 + +挂载导出逻辑集中在 **`kernel/src/filesystem/procfs/mount/`**,不再使用历史上的 `mount_view.rs` 单文件或 `procfs/mounts.rs`、`pid/mountinfo.rs` 等分散实现。 + +``` +kernel/src/filesystem/procfs/mount/ +├── mod.rs # 模块入口;导出 render API +├── collect.rs # ProcMountEntry、collect_visible_mounts() +├── fields.rs # MountProcFields:open 前预计算各导出字段 +├── escape.rs # proc 字段转义(空格、制表符、反斜杠等) +├── render.rs # ProcMountRenderKind;open 渲染 + read 读缓存 +├── format/ +│ ├── mounts_line.rs # /proc/*/mounts 行格式 +│ ├── mountinfo_line.rs # /proc/*/mountinfo 行格式 +│ └── mountstats_line.rs # /proc/*/mountstats 行格式 +└── inode/ + ├── mounts_symlink.rs # /proc/mounts → self/mounts(MountsSymOps) + └── pid_mount.rs # /proc/[pid]/{mounts,mountinfo,mountstats}(MountProcFileOps) +``` + +**注册位置:** + +- `kernel/src/filesystem/procfs/root.rs`:根目录项 `("mounts", MountsSymOps::new_inode)` +- `kernel/src/filesystem/procfs/pid/mod.rs`:`PidDirOps::STATIC_ENTRIES` 中为 `mountinfo` / `mounts` / `mountstats` 各注册一个 `MountProcFileOps`(通过 `ProcMountRenderKind` 区分格式) + +**相关但不在 `procfs/mount/` 内的依赖:** + +- `kernel/src/filesystem/vfs/mount/mod.rs`:`MountFlags::proc_rw_token()`、`proc_per_mount_options()`、`proc_super_block_options()`、`options_string()` +- `kernel/src/filesystem/vfs/mod.rs`:文件系统 trait 钩子 `proc_show_devname`、`proc_show_mount_options`、`proc_show_mountinfo_root`、`proc_show_mount_stats` +- `kernel/src/process/namespace/propagation.rs`:`MountPropagation::proc_mountinfo_tags()` + +用户态测例:`user/apps/tests/dunitest/suites/normal/proc_mount_exports.cc`(whitelist:`normal/proc_mount_exports`)。 + +## 4. DragonOS 实现原理 + +### 4.1 统一渲染流水线 + +三种 proc 文件共用一条流水线(`render.rs`): + +1. **`open()`**(`MountProcFileOps::open` 或经 symlink 打开 `/proc/self/mounts`) + - 解析目标:`ProcPidTarget` → 线程组 leader `ProcessControlBlock` +2. **`collect_visible_mounts()`**(`collect.rs`) + - 遍历目标 `mnt_ns.mount_list()`,按 mount id 排序 + - 用目标 `fs_struct.root()` 做 **可见性裁剪**(`visible_mountpoint`) +3. **`MountProcFields::from_entry()`**(`fields.rs`) + - 为每个 `ProcMountEntry` 快照 devname、fstype、各类 options、mountinfo root/tags 等 +4. **按 `ProcMountRenderKind` 调用 `format::*_line::render`** +5. 将完整文本写入 `FilePrivateData::Procfs.data` +6. **`read_at()`** 仅通过 `read_cached_mount_file()` → `proc_read()` 从缓存拷贝 + +因此当前模型是:**open 时生成整文件快照,同一次打开期间 read 不再重新遍历挂载树**。 + +### 4.2 目标进程视角 + +- `/proc/mounts` → `self/mounts` → 当前进程的 pid 目录下的 `mounts` +- `/proc/[pid]/mounts|mountinfo|mountstats` 在 open 时固定绑定该 `pid` 对应线程组的 namespace 与 root + +导出内容反映的是 **目标进程的 `mnt_ns` + `fs root`**,不是读取者自己的挂载表(除非读取的就是自己的 `/proc/self/...`)。 + +### 4.3 可见性裁剪 + +`collect.rs` 中 `visible_mountpoint(mount_path, root_path)`: + +- 目标 root 为 `/` 时,挂载点路径原样导出 +- 目标处于 chroot 等受限 root 时,只保留该 root 子树内的 mount,并将显示路径归一化到以 `/` 为根的视图 + +### 4.4 选项与传播字段的拆分 + +| 字段来源 | 用于 | 说明 | +|----------|------|------| +| `MountFlags::proc_rw_token()` | mounts / mountinfo per-mount | `ro` 或 `rw` | +| `MountFlags::proc_per_mount_options()` | mountinfo per-mount | `nosuid,nodev,...`,不含 `rw` | +| `MountFlags::proc_super_block_options()` + sb 只读状态 | mountinfo superblock 段 | `sync,mand,...` | +| `FileSystem::proc_show_mount_options()` | mounts 行、mountinfo superblock 段 | 文件系统私有选项 | +| `MountPropagation::proc_mountinfo_tags()` | 仅 mountinfo 尾部 | `shared:N` 等,**不进入 mounts** | + +`mounts_line` 使用预合并的 `mounts_options`;`mountinfo_line` 将 per-mount 与 superblock 选项用 `-` 分隔,再追加 propagation tags。 + +### 4.5 三种格式的职责划分 + +- **`format/mounts_line.rs`**:设备、挂载点、类型、选项、`0 0` +- **`format/mountinfo_line.rs`**:id、parent、major:minor、root、挂载点、选项段、`-`、fstype、super 选项、tags +- **`format/mountstats_line.rs`**:通用 `device ... mounted on ...` 前缀 + 可选 fs stats + +文件系统差异通过 VFS trait 钩子注入,procfs 只负责通用行结构与转义。 + +## 5. 当前接口的语义特点 + +### 5.1 `mounts` + +兼容性强、字段少;**不包含** propagation 标签。与 Linux 一样,应通过 `/proc/mounts` symlink 访问当前进程视图。 + +### 5.2 `mountinfo` + +恢复挂载拓扑与传播属性的首选接口;per-mount 与 superblock 选项、传播标签分列展示。 + +### 5.3 `mountstats` + +- 不是 mount 变更通知接口 +- 同一次 `open()` 内内容为快照;重新 `open()` 可看到更新后的挂载集合与统计 +- 行格式允许 `device` 或 `no device` 前缀(由 devname 是否为空决定,测例见 `proc_mount_exports.cc`) + +## 6. 与 Linux 的实现差异 + +### 6.1 总体差异概览 + +| 维度 | Linux | DragonOS 当前实现 | +|------|-------|-------------------| +| 打开方式 | `seq_file` + 迭代器 | `open()` 时一次性渲染并缓存 | +| 读取方式 | 读时按需生成 | 从 `FilePrivateData` 缓存读取 | +| `/proc/mounts` | symlink → `self/mounts` | 已实现(`MountsSymOps`) | +| 视角绑定 | 目标 task 的 `mnt_ns + fs root` | 同左(`collect_visible_mounts`) | +| `mounts` / `mountinfo` poll | 支持 mount namespace 事件 | 未实现 | +| 遍历基础 | namespace list + cursor | `mnt_ns.mount_list()` 排序后迭代 | +| 代码组织 | `fs/proc_namespace.c` 等 | `procfs/mount/{collect,fields,format,render,inode}` | + +### 6.2 Linux 的 `seq_file` 语义 + +Linux 使用 `mounts_open_common()` + `seq_file` 在读取过程中迭代 mount 列表。DragonOS 选择在 open 时拼完整字符串并缓存,实现更简单,同一次 fd 内结果稳定,但与 Linux 的迭代模型不完全等价。 + +### 6.3 `mounts` / `mountinfo` 的 `poll` + +Linux 可通过 mount namespace 事件对 `mounts` / `mountinfo` 做 `poll`/`epoll`。DragonOS 尚未实现 namespace 事件序号与等待队列,不能作为挂载变更通知源。 + +### 6.4 `mountstats` 的动态性与 `poll` + +Linux 无专门的 `mountstats` poll 语义;DragonOS 同样不为 `mountstats` 发明额外 poll。统计与拓扑变化通过重新打开文件观察。 + +### 6.5 可见性裁剪语义 + +Linux 使用 `seq_path_root` 等基于路径对象的 root 裁剪。DragonOS 当前基于 **绝对路径字符串** 与目标 `fs root` 比较,大方向一致,细节上与 Linux 路径对象语义仍有差距。 + +### 6.6 遍历与权威数据源 + +Linux 以 namespace 级 mount 链表为权威数据源。DragonOS 从 `MntNamespace::mount_list()` 取表并排序,而非从单棵 mount 树 DFS;后续若要对齐 Linux 迭代顺序与事件模型,需要在 `MntNamespace` 侧继续演进。 + +## 7. 当前适用场景与建议 + +已支持: + +- 通过 `/proc/mounts`(symlink)或 `/proc/self/mounts` 读取当前进程挂载表 +- 调试时读取 `/proc/[pid]/mounts`、`mountinfo`、`mountstats` +- 容器/命名空间工具解析 `mountinfo` 中的 propagation 字段 + +需注意: + +- 依赖 **mount namespace `poll` 通知** 的用户态工具尚未兼容 +- 强依赖 Linux `seq_file` 逐行迭代语义的程序可能观察到行为差异 +- 修改导出逻辑时,应同时更新 `procfs/mount/` 与 `proc_mount_exports` 测例 + +## 8. 小结 + +DragonOS 将 proc 挂载导出收拢到 **`kernel/src/filesystem/procfs/mount/`**: + +- **inode 层**:`/proc/mounts` symlink + `/proc/[pid]/*` 统一 `MountProcFileOps` +- **数据层**:`collect` → `fields` 快照 → `format` 三种行渲染 +- **选项语义**:传播标签仅在 `mountinfo`;`MountFlags` 与 VFS 钩子分工导出 + +对外功能定位已接近 Linux;底层仍为 **open 快照 + 字符串裁剪**,在 `poll`、迭代模型与路径语义上继续演进。 diff --git a/kernel/src/filesystem/procfs/mod.rs b/kernel/src/filesystem/procfs/mod.rs index b9fd35d5bc..09bbf3a1d1 100644 --- a/kernel/src/filesystem/procfs/mod.rs +++ b/kernel/src/filesystem/procfs/mod.rs @@ -20,7 +20,7 @@ pub mod kmsg; mod kmsg_file; mod loadavg; mod meminfo; -mod mounts; +mod mount; mod net; mod pid; pub mod root; diff --git a/kernel/src/filesystem/procfs/mount/collect.rs b/kernel/src/filesystem/procfs/mount/collect.rs new file mode 100644 index 0000000000..be331432b2 --- /dev/null +++ b/kernel/src/filesystem/procfs/mount/collect.rs @@ -0,0 +1,81 @@ +use alloc::{string::String, string::ToString, sync::Arc, vec::Vec}; + +use system_error::SystemError; + +use crate::{filesystem::vfs::MountFS, process::ProcessControlBlock}; + +#[derive(Debug)] +pub(crate) struct ProcMountEntry { + pub mount: Arc, + pub mountpoint_display: String, + pub parent_mount_id: usize, +} + +pub(crate) fn collect_visible_mounts( + target: &Arc, +) -> Result<(Vec, String), SystemError> { + let nsproxy = target.nsproxy(); + let mount_list = nsproxy.mnt_ns.mount_list(); + let root_path = target + .fs_struct() + .root() + .absolute_path() + .unwrap_or_else(|_| "/".to_string()); + + let mut mounts = mount_list + .clone_inner() + .into_iter() + .map(|(path, mfs)| (path.as_str().to_string(), mfs)) + .collect::>(); + + mounts.sort_by_key(|(_, mfs)| { + let mount_id: usize = mfs.mount_id().into(); + mount_id + }); + + let mut entries = Vec::new(); + for (mount_path, mount) in mounts { + let Some(mountpoint_display) = visible_mountpoint(&mount_path, &root_path) else { + continue; + }; + + let parent_mount_id: usize = mount + .self_mountpoint() + .map(|mountpoint_inode| mountpoint_inode.mount_fs().mount_id().into()) + .unwrap_or_else(|| mount.mount_id().into()); + + entries.push(ProcMountEntry { + mount, + mountpoint_display, + parent_mount_id, + }); + } + + Ok((entries, root_path)) +} + +fn visible_mountpoint(mountpoint: &str, root_path: &str) -> Option { + if root_path == "/" { + return Some(mountpoint.to_string()); + } + + let root_prefix_with_slash = if root_path.ends_with('/') { + root_path.to_string() + } else { + format!("{root_path}/") + }; + + if mountpoint == root_path { + return Some("/".to_string()); + } + + if let Some(stripped) = mountpoint.strip_prefix(&root_prefix_with_slash) { + return Some(if stripped.is_empty() { + "/".to_string() + } else { + format!("/{stripped}") + }); + } + + None +} diff --git a/kernel/src/filesystem/procfs/mount/escape.rs b/kernel/src/filesystem/procfs/mount/escape.rs new file mode 100644 index 0000000000..500f0d5954 --- /dev/null +++ b/kernel/src/filesystem/procfs/mount/escape.rs @@ -0,0 +1,24 @@ +use alloc::string::String; + +pub(crate) fn escape_mount_token(input: &str, escape_hash: bool) -> String { + escape_proc_field(input, escape_hash) +} + +pub(crate) fn escape_path_token(input: &str) -> String { + escape_proc_field(input, false) +} + +fn escape_proc_field(input: &str, escape_hash: bool) -> String { + let mut escaped = String::with_capacity(input.len()); + for ch in input.chars() { + match ch { + ' ' => escaped.push_str("\\040"), + '\t' => escaped.push_str("\\011"), + '\n' => escaped.push_str("\\012"), + '\\' => escaped.push_str("\\134"), + '#' if escape_hash => escaped.push_str("\\043"), + _ => escaped.push(ch), + } + } + escaped +} diff --git a/kernel/src/filesystem/procfs/mount/fields.rs b/kernel/src/filesystem/procfs/mount/fields.rs new file mode 100644 index 0000000000..e881d3b952 --- /dev/null +++ b/kernel/src/filesystem/procfs/mount/fields.rs @@ -0,0 +1,102 @@ +use alloc::{string::String, string::ToString, sync::Arc}; + +use system_error::SystemError; + +use crate::{ + driver::base::device::device_number::DeviceNumber, + filesystem::vfs::{mount::append_comma_options, IndexNode, MountFS}, +}; + +use super::collect::ProcMountEntry; + +pub(crate) struct MountProcFields { + pub mount: Arc, + pub mountpoint_display: String, + pub parent_mount_id: usize, + pub devname: String, + pub fstype: String, + pub per_mount_options: String, + pub mounts_options: String, + pub super_block_options: String, + pub mountinfo_root: String, + pub mountinfo_tags: String, + pub mount_id: usize, + pub dev: DeviceNumber, +} + +impl MountProcFields { + pub(crate) fn from_entry(entry: &ProcMountEntry) -> Result { + let mount = entry.mount.clone(); + let devname = render_devname(&mount)?; + let mountinfo_root = render_mountinfo_root(&mount)?; + let per_mount_options = build_per_mount_options(&mount)?; + let mut mounts_options = per_mount_options.clone(); + append_fs_mount_options(&mount, &mut mounts_options)?; + let super_block_options = build_super_block_options(&mount)?; + let mountinfo_tags = mount.propagation().proc_mountinfo_tags(); + let dev = mount + .mountpoint_root_inode() + .metadata() + .map(|md| DeviceNumber::from(md.dev_id as u32)) + .unwrap_or_default(); + + Ok(Self { + mount_id: mount.mount_id().into(), + dev, + mount, + mountpoint_display: entry.mountpoint_display.clone(), + parent_mount_id: entry.parent_mount_id, + devname, + fstype: entry.mount.fs_type().to_string(), + per_mount_options, + mounts_options, + super_block_options, + mountinfo_root, + mountinfo_tags, + }) + } +} + +fn build_per_mount_options(mount: &MountFS) -> Result { + let flags = mount.mount_flags(); + let mut options = flags.proc_rw_token().to_string(); + append_comma_options(&mut options, flags.proc_per_mount_options()); + Ok(options) +} + +fn build_super_block_options(mount: &MountFS) -> Result { + let sb = mount.super_block_flags(); + let mut options = if mount.is_sb_readonly() { + "ro".to_string() + } else { + "rw".to_string() + }; + append_comma_options(&mut options, sb.proc_super_block_options()); + append_fs_mount_options(mount, &mut options)?; + Ok(options) +} + +fn append_fs_mount_options(mount: &MountFS, options: &mut String) -> Result<(), SystemError> { + let mut extra = String::new(); + mount + .inner_filesystem() + .proc_show_mount_options(mount, &mut extra)?; + append_comma_options(options, extra); + Ok(()) +} + +fn render_devname(mount: &Arc) -> Result { + let mut devname = String::new(); + mount + .inner_filesystem() + .proc_show_devname(mount, &mut devname)?; + Ok(devname) +} + +fn render_mountinfo_root(mount: &Arc) -> Result { + let mut root = String::new(); + mount + .inner_filesystem() + .proc_show_mountinfo_root(mount, &mut root)?; + Ok(root) +} diff --git a/kernel/src/filesystem/procfs/mount/format/mod.rs b/kernel/src/filesystem/procfs/mount/format/mod.rs new file mode 100644 index 0000000000..91087fb9c0 --- /dev/null +++ b/kernel/src/filesystem/procfs/mount/format/mod.rs @@ -0,0 +1,3 @@ +pub(crate) mod mountinfo_line; +pub(crate) mod mounts_line; +pub(crate) mod mountstats_line; diff --git a/kernel/src/filesystem/procfs/mount/format/mountinfo_line.rs b/kernel/src/filesystem/procfs/mount/format/mountinfo_line.rs new file mode 100644 index 0000000000..e06c363cd9 --- /dev/null +++ b/kernel/src/filesystem/procfs/mount/format/mountinfo_line.rs @@ -0,0 +1,33 @@ +use alloc::string::String; +use core::fmt::Write; + +use system_error::SystemError; + +use super::super::{ + escape::escape_mount_token, escape::escape_path_token, fields::MountProcFields, +}; + +pub(crate) fn render(fields: &MountProcFields, out: &mut String) -> Result<(), SystemError> { + let root = escape_path_token(&fields.mountinfo_root); + let mountpoint = escape_path_token(&fields.mountpoint_display); + let mount_options = &fields.per_mount_options; + let fstype = escape_mount_token(&fields.fstype, true); + let source = escape_mount_token(&fields.devname, true); + let super_options = &fields.super_block_options; + + write!( + out, + "{} {} {}:{} {root} {mountpoint} {mount_options}", + fields.mount_id, + fields.parent_mount_id, + fields.dev.major().data(), + fields.dev.minor(), + ) + .map_err(|_| SystemError::EINVAL)?; + + if !fields.mountinfo_tags.is_empty() { + write!(out, " {}", fields.mountinfo_tags).map_err(|_| SystemError::EINVAL)?; + } + + writeln!(out, " - {fstype} {source} {super_options}").map_err(|_| SystemError::EINVAL) +} diff --git a/kernel/src/filesystem/procfs/mount/format/mounts_line.rs b/kernel/src/filesystem/procfs/mount/format/mounts_line.rs new file mode 100644 index 0000000000..84fab0e6e9 --- /dev/null +++ b/kernel/src/filesystem/procfs/mount/format/mounts_line.rs @@ -0,0 +1,17 @@ +use alloc::string::String; +use core::fmt::Write; + +use system_error::SystemError; + +use super::super::{ + escape::escape_mount_token, escape::escape_path_token, fields::MountProcFields, +}; + +pub(crate) fn render(fields: &MountProcFields, out: &mut String) -> Result<(), SystemError> { + let devname = escape_mount_token(&fields.devname, true); + let mountpoint = escape_path_token(&fields.mountpoint_display); + let fstype = escape_mount_token(&fields.fstype, true); + let options = &fields.mounts_options; + + writeln!(out, "{devname} {mountpoint} {fstype} {options} 0 0").map_err(|_| SystemError::EINVAL) +} diff --git a/kernel/src/filesystem/procfs/mount/format/mountstats_line.rs b/kernel/src/filesystem/procfs/mount/format/mountstats_line.rs new file mode 100644 index 0000000000..dabc21caf4 --- /dev/null +++ b/kernel/src/filesystem/procfs/mount/format/mountstats_line.rs @@ -0,0 +1,40 @@ +use alloc::string::String; +use core::fmt::Write; + +use system_error::SystemError; + +use super::super::{ + escape::escape_mount_token, escape::escape_path_token, fields::MountProcFields, +}; + +pub(crate) fn render(fields: &MountProcFields, out: &mut String) -> Result<(), SystemError> { + let devname = escape_mount_token(&fields.devname, true); + let mountpoint = escape_path_token(&fields.mountpoint_display); + let fstype = escape_mount_token(&fields.fstype, true); + let mut stats = String::new(); + let has_stats = match fields + .mount + .inner_filesystem() + .proc_show_mount_stats(&fields.mount, &mut stats) + { + Ok(value) => value, + Err(err) => { + log::warn!( + "proc_show_mount_stats failed for {}: {:?}", + fields.mountpoint_display, + err + ); + false + } + }; + + write!( + out, + "device {devname} mounted on {mountpoint} with fstype {fstype}" + ) + .map_err(|_| SystemError::EINVAL)?; + if has_stats && !stats.is_empty() { + write!(out, " {stats}").map_err(|_| SystemError::EINVAL)?; + } + out.write_char('\n').map_err(|_| SystemError::EINVAL) +} diff --git a/kernel/src/filesystem/procfs/mount/inode/mod.rs b/kernel/src/filesystem/procfs/mount/inode/mod.rs new file mode 100644 index 0000000000..e8a384c76f --- /dev/null +++ b/kernel/src/filesystem/procfs/mount/inode/mod.rs @@ -0,0 +1,5 @@ +pub(crate) mod mounts_symlink; +pub(crate) mod pid_mount; + +pub(crate) use mounts_symlink::MountsSymOps; +pub(crate) use pid_mount::MountProcFileOps; diff --git a/kernel/src/filesystem/procfs/mount/inode/mounts_symlink.rs b/kernel/src/filesystem/procfs/mount/inode/mounts_symlink.rs new file mode 100644 index 0000000000..ffd81bf16e --- /dev/null +++ b/kernel/src/filesystem/procfs/mount/inode/mounts_symlink.rs @@ -0,0 +1,30 @@ +use core::fmt::Debug; + +use crate::filesystem::{ + procfs::template::{Builder, ProcSymBuilder, SymOps}, + vfs::{IndexNode, InodeMode}, +}; +use alloc::sync::{Arc, Weak}; +use system_error::SystemError; + +/// `/proc/mounts` -> `self/mounts` +#[derive(Debug)] +pub struct MountsSymOps; + +impl MountsSymOps { + pub fn new_inode(parent: Weak) -> Arc { + ProcSymBuilder::new(Self, InodeMode::S_IRUGO) + .parent(parent) + .build() + .unwrap() + } +} + +impl SymOps for MountsSymOps { + fn read_link(&self, buf: &mut [u8]) -> Result { + const TARGET: &[u8] = b"self/mounts"; + let len = TARGET.len().min(buf.len()); + buf[..len].copy_from_slice(&TARGET[..len]); + Ok(len) + } +} diff --git a/kernel/src/filesystem/procfs/mount/inode/pid_mount.rs b/kernel/src/filesystem/procfs/mount/inode/pid_mount.rs new file mode 100644 index 0000000000..d3aed3214d --- /dev/null +++ b/kernel/src/filesystem/procfs/mount/inode/pid_mount.rs @@ -0,0 +1,60 @@ +use core::fmt::Debug; + +use crate::filesystem::{ + procfs::{ + mount::{open_mount_file_for_target, read_cached_mount_file, ProcMountRenderKind}, + pid::ProcPidTarget, + template::{Builder, FileOps, ProcFileBuilder}, + }, + vfs::{FilePrivateData, IndexNode, InodeMode}, +}; +use crate::libs::mutex::MutexGuard; +use alloc::sync::{Arc, Weak}; +use system_error::SystemError; + +#[derive(Debug)] +pub struct MountProcFileOps { + target: ProcPidTarget, + kind: ProcMountRenderKind, +} + +impl MountProcFileOps { + pub fn new_inode( + target: ProcPidTarget, + kind: ProcMountRenderKind, + parent: Weak, + ) -> Arc { + // Linux: mounts/mountinfo are world-readable; mountstats is owner-read only (0400). + let mode = match kind { + ProcMountRenderKind::MountStats => InodeMode::S_IRUSR, + _ => InodeMode::S_IRUGO, + }; + ProcFileBuilder::new(Self { target, kind }, mode) + .parent(parent) + .build() + .unwrap() + } +} + +impl FileOps for MountProcFileOps { + fn owner(&self) -> Option<(usize, usize)> { + self.target.owner_uid_gid() + } + + fn open(&self, data: &mut MutexGuard) -> Result<(), SystemError> { + open_mount_file_for_target(&self.target, self.kind, data) + } + + fn read_at( + &self, + offset: usize, + len: usize, + buf: &mut [u8], + data: MutexGuard, + ) -> Result { + self.target + .thread_group_leader() + .ok_or(SystemError::ESRCH)?; + read_cached_mount_file(offset, len, buf, data) + } +} diff --git a/kernel/src/filesystem/procfs/mount/mod.rs b/kernel/src/filesystem/procfs/mount/mod.rs new file mode 100644 index 0000000000..308857483d --- /dev/null +++ b/kernel/src/filesystem/procfs/mount/mod.rs @@ -0,0 +1,11 @@ +//! Unified rendering for `/proc/mounts`, `/proc/[pid]/mounts`, `/proc/[pid]/mountinfo`, +//! and `/proc/[pid]/mountstats`. + +mod collect; +mod escape; +mod fields; +pub(crate) mod format; +pub(crate) mod inode; +mod render; + +pub(crate) use render::{open_mount_file_for_target, read_cached_mount_file, ProcMountRenderKind}; diff --git a/kernel/src/filesystem/procfs/mount/render.rs b/kernel/src/filesystem/procfs/mount/render.rs new file mode 100644 index 0000000000..b6fff86718 --- /dev/null +++ b/kernel/src/filesystem/procfs/mount/render.rs @@ -0,0 +1,78 @@ +use alloc::{string::String, sync::Arc, vec::Vec}; + +use system_error::SystemError; + +use crate::{ + filesystem::{ + procfs::{pid::ProcPidTarget, utils::proc_read}, + vfs::FilePrivateData, + }, + libs::mutex::MutexGuard, + process::ProcessControlBlock, +}; + +use super::{ + collect::collect_visible_mounts, + fields::MountProcFields, + format::{mountinfo_line, mounts_line, mountstats_line}, +}; + +#[derive(Clone, Copy, Debug)] +pub(crate) enum ProcMountRenderKind { + Mounts, + MountInfo, + MountStats, +} + +pub(crate) fn open_mount_file_for_target( + target: &ProcPidTarget, + kind: ProcMountRenderKind, + data: &mut MutexGuard, +) -> Result<(), SystemError> { + let task = target.thread_group_leader().ok_or(SystemError::ESRCH)?; + open_mount_file_for_task(&task, kind, data) +} + +fn open_mount_file_for_task( + task: &Arc, + kind: ProcMountRenderKind, + data: &mut MutexGuard, +) -> Result<(), SystemError> { + let rendered = render_mount_file_for_task(task, kind)?; + let FilePrivateData::Procfs(pdata) = &mut **data else { + return Err(SystemError::EIO); + }; + pdata.data = rendered; + Ok(()) +} + +pub(crate) fn read_cached_mount_file( + offset: usize, + len: usize, + buf: &mut [u8], + data: MutexGuard, +) -> Result { + match &*data { + FilePrivateData::Procfs(pdata) => proc_read(offset, len, buf, &pdata.data), + _ => Err(SystemError::EINVAL), + } +} + +fn render_mount_file_for_task( + target: &Arc, + kind: ProcMountRenderKind, +) -> Result, SystemError> { + let (entries, _root_path) = collect_visible_mounts(target)?; + let mut rendered = String::new(); + + for entry in &entries { + let fields = MountProcFields::from_entry(entry)?; + match kind { + ProcMountRenderKind::Mounts => mounts_line::render(&fields, &mut rendered)?, + ProcMountRenderKind::MountInfo => mountinfo_line::render(&fields, &mut rendered)?, + ProcMountRenderKind::MountStats => mountstats_line::render(&fields, &mut rendered)?, + } + } + + Ok(rendered.into_bytes()) +} diff --git a/kernel/src/filesystem/procfs/mounts.rs b/kernel/src/filesystem/procfs/mounts.rs deleted file mode 100644 index 06ee79c0ad..0000000000 --- a/kernel/src/filesystem/procfs/mounts.rs +++ /dev/null @@ -1,269 +0,0 @@ -//! /proc/mounts - 系统挂载点信息 -//! -//! 这个文件展示了系统当前的所有挂载点 - -use crate::libs::mutex::MutexGuard; -use crate::{ - driver::base::device::device_number::DeviceNumber, - filesystem::{ - procfs::{ - template::{Builder, FileOps, ProcFileBuilder}, - utils::proc_read, - }, - vfs::{mount::MountFS, FilePrivateData, IndexNode, InodeMode}, - }, - process::{namespace::mnt::MntNamespace, ProcessControlBlock, ProcessManager}, -}; -use alloc::{ - format, - string::{String, ToString}, - sync::{Arc, Weak}, - vec::Vec, -}; -use system_error::SystemError; - -/// /proc/mounts 文件的 FileOps 实现 -#[derive(Debug)] -pub struct MountsFileOps; - -impl MountsFileOps { - pub fn new_inode(parent: Weak) -> Arc { - ProcFileBuilder::new(Self, InodeMode::S_IRUGO) - .parent(parent) - .build() - .unwrap() - } -} - -#[derive(Clone, Copy)] -enum MountsFormat { - Mounts, - MountInfo, -} - -fn escape_mount_field(raw: &str) -> String { - let mut escaped = String::with_capacity(raw.len()); - for ch in raw.chars() { - match ch { - ' ' => escaped.push_str("\\040"), - '\t' => escaped.push_str("\\011"), - '\n' => escaped.push_str("\\012"), - '\\' => escaped.push_str("\\\\"), - _ => escaped.push(ch), - } - } - escaped -} - -fn mount_source_display(mfs: &Arc, fs_type: &str) -> String { - mfs.mount_source().unwrap_or_else(|| match fs_type { - "devfs" | "devpts" | "sysfs" | "procfs" | "tmpfs" | "ramfs" | "rootfs" | "debugfs" - | "configfs" => fs_type.to_string(), - _ => fs_type.to_string(), - }) -} - -fn mount_root_display(mfs: &Arc) -> String { - let root = mfs - .root_inner_inode() - .absolute_path() - .unwrap_or_else(|_| "/".to_string()); - - if root.is_empty() { - "/".to_string() - } else { - root - } -} - -fn mount_dev_display(mfs: &Arc) -> DeviceNumber { - mfs.mountpoint_root_inode() - .metadata() - .map(|md| DeviceNumber::from(md.dev_id as u32)) - .unwrap_or_default() -} - -fn rewrite_mountpoint_for_root(mountpoint: &str, root_prefix: &str) -> Option { - if root_prefix == "/" { - return Some(mountpoint.to_string()); - } - - let root_prefix_with_slash = if root_prefix.ends_with('/') { - root_prefix.to_string() - } else { - format!("{root_prefix}/") - }; - - if mountpoint == root_prefix { - Some("/".to_string()) - } else if let Some(stripped) = mountpoint.strip_prefix(&root_prefix_with_slash) { - if stripped.is_empty() { - Some("/".to_string()) - } else { - Some(format!("/{stripped}")) - } - } else { - None - } -} - -fn mountinfo_optional_fields(mfs: &Arc) -> String { - let propagation = mfs.propagation(); - let mut fields = Vec::new(); - let info = propagation.info_string(); - - if !info.is_empty() { - fields.push(info); - } - if propagation.is_unbindable() { - fields.push("unbindable".to_string()); - } - - if fields.is_empty() { - String::new() - } else { - format!(" {}", fields.join(" ")) - } -} - -fn collect_mounts(mntns: &Arc) -> Vec<(String, Arc)> { - let mut mounts = mntns - .mount_list() - .clone_inner() - .into_iter() - .map(|(path, mfs)| (path.as_str().to_string(), mfs)) - .collect::>(); - - mounts.sort_by_key(|(_, mfs)| { - let mount_id: usize = mfs.mount_id().into(); - mount_id - }); - mounts -} - -#[inline(never)] -fn generate_mounts_like_content_for_view( - mntns: &Arc, - root_inode: Arc, - fmt: MountsFormat, -) -> String { - let mounts = collect_mounts(mntns); - let root_prefix = root_inode - .absolute_path() - .unwrap_or_else(|_| "/".to_string()); - - let mut lines = Vec::with_capacity(mounts.len()); - let mut cap = 0; - - for (mount_path, mfs) in mounts { - let Some(mountpoint) = rewrite_mountpoint_for_root(&mount_path, &root_prefix) else { - continue; - }; - - let fs_type = mfs.fs_type(); - let source = escape_mount_field(&mount_source_display(&mfs, fs_type)); - let fs_type = escape_mount_field(fs_type); - let mountpoint = escape_mount_field(&mountpoint); - let mount_opts = mfs.mount_flags().options_string(); - - let line = match fmt { - MountsFormat::Mounts => { - format!("{source} {mountpoint} {fs_type} {mount_opts} 0 0\n") - } - MountsFormat::MountInfo => { - let mount_id: usize = mfs.mount_id().into(); - let parent_id: usize = mfs - .self_mountpoint() - .map(|mountpoint_inode| { - let parent_id: usize = mountpoint_inode.mount_fs().mount_id().into(); - parent_id - }) - .unwrap_or(mount_id); - let dev = mount_dev_display(&mfs); - let root = escape_mount_field(&mount_root_display(&mfs)); - let optional_fields = mountinfo_optional_fields(&mfs); - - format!( - "{mount_id} {parent_id} {}:{} {root} {mountpoint} {mount_opts}{optional_fields} - {fs_type} {source} {mount_opts}\n", - dev.major().data(), - dev.minor() - ) - } - }; - - cap += line.len(); - lines.push(line); - } - - let mut content = String::with_capacity(cap); - for line in lines { - content.push_str(&line); - } - content -} - -fn generate_mounts_like_content_for_task( - task: &Arc, - fmt: MountsFormat, -) -> String { - let nsproxy = task.nsproxy(); - let fs = task.fs_struct(); - generate_mounts_like_content_for_view(nsproxy.mnt_namespace(), fs.root(), fmt) -} - -pub(super) fn cache_procfs_file_content( - data: &mut MutexGuard, - content: String, -) -> Result<(), SystemError> { - let FilePrivateData::Procfs(pdata) = &mut **data else { - return Err(SystemError::EIO); - }; - pdata.data = content.into_bytes(); - Ok(()) -} - -pub(super) fn read_cached_procfs_file_content( - offset: usize, - len: usize, - buf: &mut [u8], - data: MutexGuard, -) -> Result { - let bytes = match &*data { - FilePrivateData::Procfs(pdata) => pdata.data.as_slice(), - _ => return Err(SystemError::EIO), - }; - - proc_read(offset, len, buf, bytes) -} - -impl FileOps for MountsFileOps { - fn open(&self, data: &mut MutexGuard) -> Result<(), SystemError> { - cache_procfs_file_content(data, generate_mounts_content()) - } - - fn read_at( - &self, - offset: usize, - len: usize, - buf: &mut [u8], - data: MutexGuard, - ) -> Result { - read_cached_procfs_file_content(offset, len, buf, data) - } -} - -/// 为 /proc//mountinfo 生成内容(目标任务视角)。 -pub(super) fn generate_mountinfo_content_for_task(task: &Arc) -> String { - generate_mounts_like_content_for_task(task, MountsFormat::MountInfo) -} - -/// 为 /proc//mounts 生成内容。 -pub(super) fn generate_mounts_content() -> String { - let current = ProcessManager::current_pcb(); - generate_mounts_like_content_for_task(¤t, MountsFormat::Mounts) -} - -/// 为 /proc//mounts 生成内容(目标任务视角)。 -pub(super) fn generate_mounts_content_for_task(task: &Arc) -> String { - generate_mounts_like_content_for_task(task, MountsFormat::Mounts) -} diff --git a/kernel/src/filesystem/procfs/pid/mod.rs b/kernel/src/filesystem/procfs/pid/mod.rs index b40f87a7e6..d8298cec7a 100644 --- a/kernel/src/filesystem/procfs/pid/mod.rs +++ b/kernel/src/filesystem/procfs/pid/mod.rs @@ -28,14 +28,13 @@ mod fdinfo; mod id_map; mod limits; mod maps; -mod mountinfo; -mod mounts; mod ns; pub mod stat; mod statm; mod status; mod task; +use crate::filesystem::procfs::mount::{inode::MountProcFileOps, ProcMountRenderKind}; use cgroup::CgroupFileOps; use cmdline::CmdlineFileOps; use exe::ExeSymOps; @@ -44,8 +43,6 @@ use fdinfo::FdInfoDirOps; use id_map::{IdMapFileOps, SetgroupsFileOps}; use limits::LimitsFile; use maps::MapsFileOps; -use mountinfo::MountInfoFileOps; -use mounts::PidMountsFileOps; use ns::NsDirOps; use stat::StatFileOps; use statm::StatmFileOps; @@ -111,6 +108,15 @@ impl ProcPidTarget { self.thread_group_leader()?.task_pid_ptr(PidType::TGID) } + pub(super) fn owner_uid_gid(&self) -> Option<(usize, usize)> { + let pcb = self.thread_group_leader()?; + if pcb.is_kthread() { + return Some((0, 0)); + } + let cred = pcb.cred(); + Some((cred.euid.data(), cred.egid.data())) + } + pub fn tgid(&self) -> RawPid { self.thread_group_pid() .map(|pid| pid.pid_nr_ns(&self.view_pid_ns)) @@ -165,10 +171,13 @@ impl PidDirOps { LimitsFile::new_inode(ops.target.clone(), parent) }), ("mountinfo", |ops, parent| { - MountInfoFileOps::new_inode(ops.target.clone(), parent) + MountProcFileOps::new_inode(ops.target.clone(), ProcMountRenderKind::MountInfo, parent) }), ("mounts", |ops, parent| { - PidMountsFileOps::new_inode(ops.target.clone(), parent) + MountProcFileOps::new_inode(ops.target.clone(), ProcMountRenderKind::Mounts, parent) + }), + ("mountstats", |ops, parent| { + MountProcFileOps::new_inode(ops.target.clone(), ProcMountRenderKind::MountStats, parent) }), ("ns", |ops, parent| { NsDirOps::new_inode(ops.target.clone(), parent) @@ -254,12 +263,7 @@ impl PidDirOps { impl DirOps for PidDirOps { fn owner(&self) -> Option<(usize, usize)> { - let pcb = self.target.thread_group_leader()?; - if pcb.is_kthread() { - return Some((0, 0)); - } - let cred = pcb.cred(); - Some((cred.euid.data(), cred.egid.data())) + self.target.owner_uid_gid() } fn lookup_child( diff --git a/kernel/src/filesystem/procfs/pid/mountinfo.rs b/kernel/src/filesystem/procfs/pid/mountinfo.rs deleted file mode 100644 index 795b83e778..0000000000 --- a/kernel/src/filesystem/procfs/pid/mountinfo.rs +++ /dev/null @@ -1,56 +0,0 @@ -//! /proc/[pid]/mountinfo - 进程挂载点详细信息 -//! -//! 显示进程的挂载点详细信息 - -use crate::filesystem::{ - procfs::{ - mounts::{ - cache_procfs_file_content, generate_mountinfo_content_for_task, - read_cached_procfs_file_content, - }, - pid::ProcPidTarget, - template::{Builder, FileOps, ProcFileBuilder}, - }, - vfs::{FilePrivateData, IndexNode, InodeMode}, -}; -use crate::libs::mutex::MutexGuard; -use alloc::sync::{Arc, Weak}; -use system_error::SystemError; - -/// /proc/[pid]/mountinfo 文件的 FileOps 实现 -#[derive(Debug)] -pub struct MountInfoFileOps { - target: ProcPidTarget, -} - -impl MountInfoFileOps { - pub fn new_inode(target: ProcPidTarget, parent: Weak) -> Arc { - ProcFileBuilder::new(Self { target }, InodeMode::S_IRUGO) - .parent(parent) - .build() - .unwrap() - } -} - -impl FileOps for MountInfoFileOps { - fn open(&self, data: &mut MutexGuard) -> Result<(), SystemError> { - let target = self - .target - .thread_group_leader() - .ok_or(SystemError::ESRCH)?; - cache_procfs_file_content(data, generate_mountinfo_content_for_task(&target)) - } - - fn read_at( - &self, - offset: usize, - len: usize, - buf: &mut [u8], - data: MutexGuard, - ) -> Result { - self.target - .thread_group_leader() - .ok_or(SystemError::ESRCH)?; - read_cached_procfs_file_content(offset, len, buf, data) - } -} diff --git a/kernel/src/filesystem/procfs/pid/mounts.rs b/kernel/src/filesystem/procfs/pid/mounts.rs deleted file mode 100644 index 5265747e54..0000000000 --- a/kernel/src/filesystem/procfs/pid/mounts.rs +++ /dev/null @@ -1,56 +0,0 @@ -//! /proc/[pid]/mounts - 进程挂载点信息 -//! -//! 显示进程的挂载点信息 - -use crate::filesystem::{ - procfs::{ - mounts::{ - cache_procfs_file_content, generate_mounts_content_for_task, - read_cached_procfs_file_content, - }, - pid::ProcPidTarget, - template::{Builder, FileOps, ProcFileBuilder}, - }, - vfs::{FilePrivateData, IndexNode, InodeMode}, -}; -use crate::libs::mutex::MutexGuard; -use alloc::sync::{Arc, Weak}; -use system_error::SystemError; - -/// /proc/[pid]/mounts 文件的 FileOps 实现 -#[derive(Debug)] -pub struct PidMountsFileOps { - target: ProcPidTarget, -} - -impl PidMountsFileOps { - pub fn new_inode(target: ProcPidTarget, parent: Weak) -> Arc { - ProcFileBuilder::new(Self { target }, InodeMode::S_IRUGO) - .parent(parent) - .build() - .unwrap() - } -} - -impl FileOps for PidMountsFileOps { - fn open(&self, data: &mut MutexGuard) -> Result<(), SystemError> { - let target = self - .target - .thread_group_leader() - .ok_or(SystemError::ESRCH)?; - cache_procfs_file_content(data, generate_mounts_content_for_task(&target)) - } - - fn read_at( - &self, - offset: usize, - len: usize, - buf: &mut [u8], - data: MutexGuard, - ) -> Result { - self.target - .thread_group_leader() - .ok_or(SystemError::ESRCH)?; - read_cached_procfs_file_content(offset, len, buf, data) - } -} diff --git a/kernel/src/filesystem/procfs/root.rs b/kernel/src/filesystem/procfs/root.rs index 771c112c55..9f504638f9 100644 --- a/kernel/src/filesystem/procfs/root.rs +++ b/kernel/src/filesystem/procfs/root.rs @@ -12,7 +12,6 @@ use crate::{ kmsg_file::KmsgFileOps, loadavg::LoadavgFileOps, meminfo::MeminfoFileOps, - mounts::MountsFileOps, net::NetDirOps, pid::PidDirOps, self_::SelfSymOps, @@ -90,7 +89,10 @@ impl RootDirOps { ("kmsg", KmsgFileOps::new_inode), ("loadavg", LoadavgFileOps::new_inode), ("meminfo", MeminfoFileOps::new_inode), - ("mounts", MountsFileOps::new_inode), + ( + "mounts", + crate::filesystem::procfs::mount::inode::MountsSymOps::new_inode, + ), ("net", NetDirOps::new_inode), ("self", SelfSymOps::new_inode), ("stat", StatFileOps::new_inode), diff --git a/kernel/src/filesystem/procfs/template/file.rs b/kernel/src/filesystem/procfs/template/file.rs index cbc890e6d5..f0abbe9a16 100644 --- a/kernel/src/filesystem/procfs/template/file.rs +++ b/kernel/src/filesystem/procfs/template/file.rs @@ -141,7 +141,6 @@ impl IndexNode for ProcFile { buf: &mut [u8], data: MutexGuard, ) -> Result { - // log::info!("ProcFile read_at called"); self.inner.read_at(offset, len, buf, data) } diff --git a/kernel/src/filesystem/vfs/mod.rs b/kernel/src/filesystem/vfs/mod.rs index fcc83100bc..cd09658cb0 100644 --- a/kernel/src/filesystem/vfs/mod.rs +++ b/kernel/src/filesystem/vfs/mod.rs @@ -15,7 +15,11 @@ pub mod vcore; pub mod writeback; use alloc::{string::String, sync::Arc, vec::Vec}; -use core::{any::Any, fmt::Debug, fmt::Display, sync::atomic::AtomicUsize}; +use core::{ + any::Any, + fmt::{Debug, Display, Write}, + sync::atomic::AtomicUsize, +}; use derive_builder::Builder; use intertrait::CastFromSync; use mount::MountFlags; @@ -1447,6 +1451,49 @@ pub trait FileSystem: Any + Sync + Send + Debug { FsPermissionPolicy::Dac } + /// Render the device/source field used by procfs mount exports. + fn proc_show_devname(&self, mount: &MountFS, out: &mut dyn Write) -> Result<(), SystemError> { + if let Some(source) = mount.mount_source() { + out.write_str(&source).map_err(|_| SystemError::EINVAL)?; + } else { + out.write_str(self.name()) + .map_err(|_| SystemError::EINVAL)?; + } + Ok(()) + } + + /// Render extra mount options for `/proc/*/mounts` and `mountinfo`. + fn proc_show_mount_options( + &self, + _mount: &MountFS, + _out: &mut dyn Write, + ) -> Result<(), SystemError> { + Ok(()) + } + + /// Render the mount root field used by `/proc/*/mountinfo`. + fn proc_show_mountinfo_root( + &self, + mount: &MountFS, + out: &mut dyn Write, + ) -> Result<(), SystemError> { + match mount.root_inner_inode().absolute_path() { + Ok(root) if !root.is_empty() => out.write_str(&root).map_err(|_| SystemError::EINVAL), + _ => out.write_char('/').map_err(|_| SystemError::EINVAL), + } + } + + /// Render fs-specific stats for `/proc/*/mountstats`. + /// + /// Returns `true` if any fs-specific payload was written. + fn proc_show_mount_stats( + &self, + _mount: &MountFS, + _out: &mut dyn Write, + ) -> Result { + Ok(false) + } + /// Called after a filesystem is successfully unmounted. /// Default is no-op. fn on_umount(&self) {} diff --git a/kernel/src/filesystem/vfs/mount/mod.rs b/kernel/src/filesystem/vfs/mount/mod.rs index df7564d3f1..305af98c80 100644 --- a/kernel/src/filesystem/vfs/mount/mod.rs +++ b/kernel/src/filesystem/vfs/mount/mod.rs @@ -154,26 +154,19 @@ bitflags! { } impl MountFlags { - /// Convert mount flags to a comma-separated string representation - /// - /// This function converts MountFlags to a string format similar to /proc/mounts, - /// such as "rw,nosuid,nodev,noexec,relatime". - /// - /// # Returns - /// - /// A String containing the mount options in comma-separated format. - #[inline(never)] - pub fn options_string(&self) -> String { - let mut options = Vec::new(); - - // Check read/write flag + /// `ro` or `rw` token for proc mount options. + pub fn proc_rw_token(&self) -> &'static str { if self.contains(MountFlags::RDONLY) { - options.push("ro"); + "ro" } else { - options.push("rw"); + "rw" } + } + + /// Per-mount options excluding rw and super-block flags. + pub fn proc_per_mount_options(&self) -> String { + let mut options = Vec::new(); - // Check other flags if self.contains(MountFlags::NOSUID) { options.push("nosuid"); } @@ -183,15 +176,6 @@ impl MountFlags { if self.contains(MountFlags::NOEXEC) { options.push("noexec"); } - if self.contains(MountFlags::SYNCHRONOUS) { - options.push("sync"); - } - if self.contains(MountFlags::MANDLOCK) { - options.push("mand"); - } - if self.contains(MountFlags::DIRSYNC) { - options.push("dirsync"); - } if self.contains(MountFlags::NOSYMFOLLOW) { options.push("nosymfollow"); } @@ -207,30 +191,51 @@ impl MountFlags { if self.contains(MountFlags::STRICTATIME) { options.push("strictatime"); } - if self.contains(MountFlags::LAZYTIME) { - options.push("lazytime"); - } - // Mount propagation flags - if self.contains(MountFlags::UNBINDABLE) { - options.push("unbindable"); + options.join(",") + } + + /// Super-block options excluding rw and per-mount flags. + pub fn proc_super_block_options(&self) -> String { + let mut options = Vec::new(); + + if self.contains(MountFlags::SYNCHRONOUS) { + options.push("sync"); } - if self.contains(MountFlags::PRIVATE) { - options.push("private"); + if self.contains(MountFlags::MANDLOCK) { + options.push("mand"); } - if self.contains(MountFlags::SLAVE) { - options.push("slave"); + if self.contains(MountFlags::DIRSYNC) { + options.push("dirsync"); } - if self.contains(MountFlags::SHARED) { - options.push("shared"); + if self.contains(MountFlags::LAZYTIME) { + options.push("lazytime"); } - // Internal flags (typically not shown in /proc/mounts) - // We'll skip flags like BIND, MOVE, REC, REMOUNT, etc. as they're - // not typically displayed in mount options - options.join(",") } + + /// Convert mount flags to a comma-separated string representation + /// + /// This function converts MountFlags to a string format similar to /proc/mounts, + /// such as "rw,nosuid,nodev,noexec,relatime". + #[inline(never)] + pub fn options_string(&self) -> String { + let mut options = self.proc_rw_token().to_string(); + append_comma_options(&mut options, self.proc_per_mount_options()); + append_comma_options(&mut options, self.proc_super_block_options()); + options + } +} + +pub(crate) fn append_comma_options(base: &mut String, extra: String) { + if extra.is_empty() { + return; + } + if !base.is_empty() { + base.push(','); + } + base.push_str(&extra); } // MountId type @@ -568,6 +573,10 @@ impl MountFS { self.self_mountpoint.read().as_ref().cloned() } + pub fn parent_mount(&self) -> Option> { + self.self_mountpoint().map(|inode| inode.mount_fs.clone()) + } + pub fn set_self_mountpoint(&self, mountpoint: Option>) { *self.self_mountpoint.write() = mountpoint; } @@ -1981,6 +1990,21 @@ impl MountList { .collect() } + /// Clone every mount record, including lower entries in a same-path mount stack. + pub fn clone_records(&self) -> Vec<(Arc, Arc)> { + self.inner + .read() + .mounts + .iter() + .flat_map(|(path, stack)| { + stack + .iter() + .map(|rec| (path.clone(), rec.fs.clone())) + .collect::>() + }) + .collect() + } + pub fn get>(&self, path: T) -> Option> { let inner = self.inner.read(); let path: MountPath = path.into(); diff --git a/kernel/src/init/initial_kthread.rs b/kernel/src/init/initial_kthread.rs index ce027f14ee..f8cccf2068 100644 --- a/kernel/src/init/initial_kthread.rs +++ b/kernel/src/init/initial_kthread.rs @@ -24,10 +24,11 @@ use crate::{ use super::{cmdline::kenrel_cmdline_param_manager, initcall::do_initcalls}; -const INIT_PROC_TRYLIST: [(&str, Option<&str>); 5] = [ +const INIT_PROC_TRYLIST: [(&str, Option<&str>); 6] = [ ("/bin/dragonreach", None), ("/bin/busybox", Some("init")), ("/bin/init", None), + ("/sbin/init", None), ("/bin/sh", None), ("/bin/riscv_rust_init", None), // 对vf2, 目前没做cmdline的适配, 加个默认寻找 ]; diff --git a/kernel/src/process/namespace/propagation.rs b/kernel/src/process/namespace/propagation.rs index 3fbdbc60e6..59e32d6483 100644 --- a/kernel/src/process/namespace/propagation.rs +++ b/kernel/src/process/namespace/propagation.rs @@ -618,6 +618,11 @@ impl MountPropagation { /// /// Returns a string like "shared:1" or "master:2" or empty for private. pub fn info_string(&self) -> alloc::string::String { + self.proc_mountinfo_tags() + } + + /// Optional mountinfo fields: `shared:N`, `master:N`, `propagate_from:N`, `unbindable`. + pub fn proc_mountinfo_tags(&self) -> alloc::string::String { let inner = self.inner.lock(); let mut parts = Vec::new(); if inner.flags.contains(PropagationFlags::SHARED) && inner.peer_group_id.is_valid() { @@ -627,12 +632,33 @@ impl MountPropagation { let master_group = master.propagation().peer_group_id(); if master_group.is_valid() { parts.push(alloc::format!("master:{}", master_group.0)); + if let Some(dom) = dominating_peer_group_id(&master) { + if dom != master_group.0 { + parts.push(alloc::format!("propagate_from:{dom}")); + } + } } } + if inner.flags.contains(PropagationFlags::UNBINDABLE) { + parts.push("unbindable".into()); + } parts.join(" ") } } +fn dominating_peer_group_id(immediate_master: &Arc) -> Option { + let mut dominating = None; + let mut current = immediate_master.propagation().master(); + while let Some(master) = current { + let group = master.propagation().peer_group_id(); + if group.is_valid() { + dominating = Some(group.0); + } + current = master.propagation().master(); + } + dominating +} + impl Clone for MountPropagation { fn clone(&self) -> Self { let inner = self.inner.lock(); diff --git a/user/apps/tests/dunitest/suites/normal/proc_mount_exports.cc b/user/apps/tests/dunitest/suites/normal/proc_mount_exports.cc new file mode 100644 index 0000000000..7fcd3ee460 --- /dev/null +++ b/user/apps/tests/dunitest/suites/normal/proc_mount_exports.cc @@ -0,0 +1,537 @@ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#ifndef CLONE_NEWNS +#define CLONE_NEWNS 0x00020000 +#endif + +namespace { + +struct ChildProcessGuard { + pid_t pid = -1; + int quit_fd = -1; + int detail_fd = -1; + + ~ChildProcessGuard() { + if (quit_fd >= 0) { + char quit = 'Q'; + const ssize_t ignored = write(quit_fd, &quit, 1); + (void)ignored; + close(quit_fd); + } + + if (detail_fd >= 0) { + close(detail_fd); + } + + if (pid > 0) { + int status = 0; + while (waitpid(pid, &status, 0) < 0 && errno == EINTR) { + } + } + } +}; + +int ensure_dir(const char* path) { + struct stat st = {}; + + if (stat(path, &st) == 0) { + return S_ISDIR(st.st_mode) ? 0 : -1; + } + + return mkdir(path, 0755); +} + +void best_effort_rmdir(const char* path) { + if (rmdir(path) != 0 && errno != ENOENT && errno != ENOTEMPTY) { + ADD_FAILURE() << "rmdir failed for " << path << ": errno=" << errno << " (" + << strerror(errno) << ")"; + } +} + +std::string read_all_from_fd(int fd) { + std::string out; + char buf[512]; + ssize_t n = 0; + + while ((n = read(fd, buf, sizeof(buf))) > 0) { + out.append(buf, static_cast(n)); + } + + return out; +} + +bool read_text_file(const char* path, std::string* out) { + int fd = open(path, O_RDONLY); + if (fd < 0) { + return false; + } + + out->clear(); + char buf[1024]; + ssize_t n = 0; + while ((n = read(fd, buf, sizeof(buf))) > 0) { + out->append(buf, static_cast(n)); + } + + const int saved_errno = errno; + close(fd); + errno = saved_errno; + return n >= 0; +} + +size_t count_nonempty_lines(const std::string& content) { + size_t count = 0; + size_t start = 0; + + while (start <= content.size()) { + const size_t end = content.find('\n', start); + const size_t line_end = end == std::string::npos ? content.size() : end; + if (line_end > start) { + ++count; + } + if (end == std::string::npos) { + break; + } + start = end + 1; + } + + return count; +} + +size_t count_mountstats_entries(const std::string& content) { + size_t count = 0; + size_t start = 0; + + while (start <= content.size()) { + const size_t end = content.find('\n', start); + const size_t line_end = end == std::string::npos ? content.size() : end; + if (line_end > start) { + const std::string line = content.substr(start, line_end - start); + if (line.rfind("device ", 0) == 0 || line.rfind("no device ", 0) == 0) { + ++count; + } + } + if (end == std::string::npos) { + break; + } + start = end + 1; + } + + return count; +} + +void expect_contains(const char* path, const std::string& content, const char* needle) { + EXPECT_NE(std::string::npos, content.find(needle)) << path << " missing substring\nneedle=" + << needle << "\ncontent=" << content; +} + +void expect_not_contains(const char* path, const std::string& content, const char* needle) { + EXPECT_EQ(std::string::npos, content.find(needle)) + << path << " unexpectedly contains substring\nneedle=" << needle << "\ncontent=" + << content; +} + +[[noreturn]] void child_fail(int detail_fd, const char* step) { + dprintf(detail_fd, + "%s: errno=%d (%s)", + step, + errno, + errno == 0 ? "no error information" : strerror(errno)); + _exit(1); +} + +bool can_use_mount_namespaces() { + if (geteuid() == 0) { + return true; + } + + const pid_t probe = fork(); + if (probe < 0) { + return false; + } + + if (probe == 0) { + _exit(unshare(CLONE_NEWNS) == 0 ? 0 : 1); + } + + int status = 0; + if (waitpid(probe, &status, 0) != probe) { + return false; + } + + return WIFEXITED(status) && WEXITSTATUS(status) == 0; +} + +} // namespace + +TEST(ProcMountExports, ProcMountsSymlinkTarget) { + char target[256] = {}; + const ssize_t len = readlink("/proc/mounts", target, sizeof(target) - 1); + ASSERT_GE(len, 0) << "readlink /proc/mounts failed: errno=" << errno << " (" + << strerror(errno) << ")"; + target[len] = '\0'; + EXPECT_STREQ(target, "self/mounts"); +} + +TEST(ProcMountExports, ProcMountsMatchesSelf) { + std::string proc_mounts; + std::string self_mounts; + + ASSERT_TRUE(read_text_file("/proc/mounts", &proc_mounts)) + << "read /proc/mounts failed: errno=" << errno << " (" << strerror(errno) << ")"; + ASSERT_TRUE(read_text_file("/proc/self/mounts", &self_mounts)) + << "read /proc/self/mounts failed: errno=" << errno << " (" << strerror(errno) << ")"; + + EXPECT_EQ(proc_mounts, self_mounts); +} + +TEST(ProcMountExports, SelfMountExportLineCountsMatch) { + std::string mounts; + std::string mountinfo; + std::string mountstats; + + ASSERT_TRUE(read_text_file("/proc/self/mounts", &mounts)) + << "read /proc/self/mounts failed: errno=" << errno << " (" << strerror(errno) << ")"; + ASSERT_TRUE(read_text_file("/proc/self/mountinfo", &mountinfo)) + << "read /proc/self/mountinfo failed: errno=" << errno << " (" << strerror(errno) + << ")"; + ASSERT_TRUE(read_text_file("/proc/self/mountstats", &mountstats)) + << "read /proc/self/mountstats failed: errno=" << errno << " (" << strerror(errno) + << ")"; + + EXPECT_GT(count_nonempty_lines(mounts), 0U); + EXPECT_EQ(count_nonempty_lines(mounts), count_nonempty_lines(mountinfo)); + EXPECT_EQ(count_nonempty_lines(mounts), count_mountstats_entries(mountstats)); +} + +TEST(ProcMountExports, SelfMountExportOwnershipAndModes) { + struct stat mounts = {}; + struct stat mountinfo = {}; + struct stat mountstats = {}; + + ASSERT_EQ(0, stat("/proc/self/mounts", &mounts)) + << "stat /proc/self/mounts failed: errno=" << errno << " (" << strerror(errno) << ")"; + ASSERT_EQ(0, stat("/proc/self/mountinfo", &mountinfo)) + << "stat /proc/self/mountinfo failed: errno=" << errno << " (" << strerror(errno) + << ")"; + ASSERT_EQ(0, stat("/proc/self/mountstats", &mountstats)) + << "stat /proc/self/mountstats failed: errno=" << errno << " (" << strerror(errno) + << ")"; + + EXPECT_EQ(static_cast(geteuid()), mounts.st_uid); + EXPECT_EQ(static_cast(getegid()), mounts.st_gid); + EXPECT_EQ(static_cast(geteuid()), mountinfo.st_uid); + EXPECT_EQ(static_cast(getegid()), mountinfo.st_gid); + EXPECT_EQ(static_cast(geteuid()), mountstats.st_uid); + EXPECT_EQ(static_cast(getegid()), mountstats.st_gid); + + EXPECT_EQ(0444U, static_cast(mounts.st_mode & 0777)); + EXPECT_EQ(0444U, static_cast(mountinfo.st_mode & 0777)); + EXPECT_EQ(0400U, static_cast(mountstats.st_mode & 0777)); +} + +TEST(ProcMountExports, NonRootCanReadOwnMountstats) { + if (geteuid() != 0) { + GTEST_SKIP() << "requires root to drop credentials"; + } + + int detail_pipe[2] = {-1, -1}; + ASSERT_EQ(0, pipe(detail_pipe)) << "pipe detail failed: errno=" << errno << " (" + << strerror(errno) << ")"; + + const pid_t child = fork(); + ASSERT_GE(child, 0) << "fork failed: errno=" << errno << " (" << strerror(errno) << ")"; + + if (child == 0) { + close(detail_pipe[0]); + + if (setgid(1000) != 0) { + child_fail(detail_pipe[1], "setgid(1000)"); + } + if (setuid(1000) != 0) { + child_fail(detail_pipe[1], "setuid(1000)"); + } + if (prctl(PR_SET_DUMPABLE, 1) != 0) { + child_fail(detail_pipe[1], "prctl(PR_SET_DUMPABLE)"); + } + + struct stat mountstats = {}; + if (stat("/proc/self/mountstats", &mountstats) != 0) { + child_fail(detail_pipe[1], "stat(/proc/self/mountstats)"); + } + if (mountstats.st_uid != geteuid() || mountstats.st_gid != getegid()) { + dprintf(detail_pipe[1], + "mountstats owner mismatch: st_uid=%u st_gid=%u euid=%u egid=%u", + static_cast(mountstats.st_uid), + static_cast(mountstats.st_gid), + static_cast(geteuid()), + static_cast(getegid())); + _exit(1); + } + if ((mountstats.st_mode & 0777) != 0400) { + dprintf(detail_pipe[1], + "mountstats mode mismatch: mode=%o", + static_cast(mountstats.st_mode & 0777)); + _exit(1); + } + + std::string content; + if (!read_text_file("/proc/self/mountstats", &content)) { + child_fail(detail_pipe[1], "read(/proc/self/mountstats)"); + } + if (content.empty()) { + dprintf(detail_pipe[1], "mountstats content is empty"); + _exit(1); + } + + close(detail_pipe[1]); + _exit(0); + } + + close(detail_pipe[1]); + + int status = 0; + ASSERT_EQ(child, waitpid(child, &status, 0)) << "waitpid failed: errno=" << errno << " (" + << strerror(errno) << ")"; + + const std::string detail = read_all_from_fd(detail_pipe[0]); + close(detail_pipe[0]); + + ASSERT_TRUE(WIFEXITED(status)) << "child terminated abnormally, status=0x" << std::hex + << status; + EXPECT_EQ(0, WEXITSTATUS(status)) << detail; +} + +TEST(ProcMountExports, SelfMountstatsLineFormat) { + std::string mountstats; + + ASSERT_TRUE(read_text_file("/proc/self/mountstats", &mountstats)) + << "read /proc/self/mountstats failed: errno=" << errno << " (" << strerror(errno) + << ")"; + + const std::regex line_re( + R"(^(device \S+|no device) mounted on \S+ with fstype \S+( .*)?$)", + std::regex::ECMAScript); + + size_t start = 0; + while (start <= mountstats.size()) { + const size_t end = mountstats.find('\n', start); + const size_t line_end = end == std::string::npos ? mountstats.size() : end; + if (line_end > start) { + const std::string line = mountstats.substr(start, line_end - start); + EXPECT_TRUE(std::regex_match(line, line_re)) << "bad mountstats line: " << line; + } + if (end == std::string::npos) { + break; + } + start = end + 1; + } +} + +TEST(ProcMountExports, UsesTargetTaskRootAndMountNamespace) { + if (!can_use_mount_namespaces()) { + GTEST_SKIP() << "requires CAP_SYS_ADMIN or unprivileged mount namespaces"; + } + + char base[256] = {}; + char rootfs[256] = {}; + char inside_name[64] = {}; + char inside[256] = {}; + char outside[256] = {}; + char proc_mounts_path[64] = {}; + char proc_mountinfo_path[64] = {}; + char proc_mountstats_path[64] = {}; + int ready_pipe[2] = {-1, -1}; + int quit_pipe[2] = {-1, -1}; + int detail_pipe[2] = {-1, -1}; + std::string proc_mounts; + std::string proc_mountinfo; + std::string proc_mountstats; + std::string self_mounts; + std::string self_mountinfo; + std::string self_mountstats; + char inside_mounts_needle[96] = {}; + char inside_mountinfo_needle[96] = {}; + char inside_mountstats_needle[96] = {}; + + ASSERT_EQ(0, ensure_dir("/tmp")) << "mkdir /tmp failed: errno=" << errno << " (" + << strerror(errno) << ")"; + + snprintf(base, sizeof(base), "/tmp/proc_mount_exports_%d", getpid()); + snprintf(rootfs, sizeof(rootfs), "%s/rootfs", base); + snprintf(inside_name, sizeof(inside_name), "inside_%d", getpid()); + snprintf(inside, sizeof(inside), "%s/%s", rootfs, inside_name); + snprintf(outside, sizeof(outside), "%s/outside", base); + snprintf(inside_mounts_needle, sizeof(inside_mounts_needle), " /%s ramfs ", inside_name); + snprintf(inside_mountinfo_needle, sizeof(inside_mountinfo_needle), " /%s ", inside_name); + snprintf(inside_mountstats_needle, sizeof(inside_mountstats_needle), + " mounted on /%s with fstype ramfs", inside_name); + + ASSERT_EQ(0, ensure_dir(base)) << "mkdir base failed: errno=" << errno << " (" + << strerror(errno) << ")"; + ASSERT_EQ(0, ensure_dir(rootfs)) << "mkdir rootfs failed: errno=" << errno << " (" + << strerror(errno) << ")"; + ASSERT_EQ(0, ensure_dir(outside)) << "mkdir outside failed: errno=" << errno << " (" + << strerror(errno) << ")"; + + ASSERT_EQ(0, pipe(ready_pipe)) << "pipe ready failed: errno=" << errno << " (" + << strerror(errno) << ")"; + ASSERT_EQ(0, pipe(quit_pipe)) << "pipe quit failed: errno=" << errno << " (" + << strerror(errno) << ")"; + ASSERT_EQ(0, pipe(detail_pipe)) << "pipe detail failed: errno=" << errno << " (" + << strerror(errno) << ")"; + + const pid_t child = fork(); + ASSERT_GE(child, 0) << "fork failed: errno=" << errno << " (" << strerror(errno) << ")"; + + if (child == 0) { + close(ready_pipe[0]); + close(quit_pipe[1]); + close(detail_pipe[0]); + + if (unshare(CLONE_NEWNS) != 0) { + child_fail(detail_pipe[1], "unshare(CLONE_NEWNS)"); + } + if (mount("", "/", nullptr, MS_REC | MS_PRIVATE, nullptr) != 0) { + child_fail(detail_pipe[1], "mount(/, MS_PRIVATE)"); + } + if (mount("", rootfs, "ramfs", 0, nullptr) != 0) { + child_fail(detail_pipe[1], "mount(rootfs, ramfs)"); + } + if (ensure_dir(inside) != 0) { + child_fail(detail_pipe[1], "mkdir(inside)"); + } + if (mount("", inside, "ramfs", 0, nullptr) != 0) { + child_fail(detail_pipe[1], "mount(inside, ramfs)"); + } + if (mount("", outside, "ramfs", 0, nullptr) != 0) { + child_fail(detail_pipe[1], "mount(outside, ramfs)"); + } + if (chdir(rootfs) != 0) { + child_fail(detail_pipe[1], "chdir(rootfs)"); + } + if (chroot(rootfs) != 0) { + child_fail(detail_pipe[1], "chroot(rootfs)"); + } + if (chdir("/") != 0) { + child_fail(detail_pipe[1], "chdir(/)"); + } + + const char ready = 'R'; + if (write(ready_pipe[1], &ready, 1) != 1) { + child_fail(detail_pipe[1], "notify parent ready"); + } + + char quit = 0; + if (read(quit_pipe[0], &quit, 1) != 1) { + child_fail(detail_pipe[1], "wait parent quit"); + } + + close(ready_pipe[1]); + close(quit_pipe[0]); + close(detail_pipe[1]); + _exit(0); + } + + close(ready_pipe[1]); + close(quit_pipe[0]); + close(detail_pipe[1]); + + ChildProcessGuard guard; + guard.pid = child; + guard.quit_fd = quit_pipe[1]; + guard.detail_fd = detail_pipe[0]; + + char ready = 0; + ASSERT_EQ(1, read(ready_pipe[0], &ready, 1)) << "read child ready failed: errno=" << errno + << " (" << strerror(errno) << ")"; + EXPECT_EQ('R', ready); + close(ready_pipe[0]); + + snprintf(proc_mounts_path, sizeof(proc_mounts_path), "/proc/%d/mounts", child); + ASSERT_TRUE(read_text_file(proc_mounts_path, &proc_mounts)) + << "read " << proc_mounts_path << " failed: errno=" << errno << " (" << strerror(errno) + << ")"; + expect_contains(proc_mounts_path, proc_mounts, " / ramfs "); + expect_contains(proc_mounts_path, proc_mounts, inside_mounts_needle); + expect_not_contains(proc_mounts_path, proc_mounts, "/outside"); + + snprintf(proc_mountinfo_path, sizeof(proc_mountinfo_path), "/proc/%d/mountinfo", child); + ASSERT_TRUE(read_text_file(proc_mountinfo_path, &proc_mountinfo)) + << "read " << proc_mountinfo_path << " failed: errno=" << errno << " (" + << strerror(errno) << ")"; + expect_contains(proc_mountinfo_path, proc_mountinfo, inside_mountinfo_needle); + expect_not_contains(proc_mountinfo_path, proc_mountinfo, outside); + + snprintf(proc_mountstats_path, sizeof(proc_mountstats_path), "/proc/%d/mountstats", child); + ASSERT_TRUE(read_text_file(proc_mountstats_path, &proc_mountstats)) + << "read " << proc_mountstats_path << " failed: errno=" << errno << " (" + << strerror(errno) << ")"; + expect_contains(proc_mountstats_path, proc_mountstats, inside_mountstats_needle); + expect_not_contains(proc_mountstats_path, proc_mountstats, outside); + EXPECT_EQ(count_nonempty_lines(proc_mounts), count_nonempty_lines(proc_mountinfo)); + EXPECT_EQ(count_nonempty_lines(proc_mounts), count_mountstats_entries(proc_mountstats)); + + ASSERT_TRUE(read_text_file("/proc/self/mounts", &self_mounts)) + << "read /proc/self/mounts failed: errno=" << errno << " (" << strerror(errno) << ")"; + expect_not_contains("/proc/self/mounts", self_mounts, inside_mountinfo_needle); + expect_not_contains("/proc/self/mounts", self_mounts, outside); + + ASSERT_TRUE(read_text_file("/proc/self/mountinfo", &self_mountinfo)) + << "read /proc/self/mountinfo failed: errno=" << errno << " (" << strerror(errno) + << ")"; + expect_not_contains("/proc/self/mountinfo", self_mountinfo, inside_mountinfo_needle); + expect_not_contains("/proc/self/mountinfo", self_mountinfo, outside); + + ASSERT_TRUE(read_text_file("/proc/self/mountstats", &self_mountstats)) + << "read /proc/self/mountstats failed: errno=" << errno << " (" << strerror(errno) + << ")"; + expect_not_contains("/proc/self/mountstats", self_mountstats, inside_mountstats_needle); + expect_not_contains("/proc/self/mountstats", self_mountstats, outside); + + char quit = 'Q'; + ASSERT_EQ(1, write(guard.quit_fd, &quit, 1)) << "write child quit failed: errno=" << errno + << " (" << strerror(errno) << ")"; + close(guard.quit_fd); + guard.quit_fd = -1; + + int status = 0; + ASSERT_EQ(child, waitpid(child, &status, 0)) << "waitpid failed: errno=" << errno << " (" + << strerror(errno) << ")"; + + const std::string detail = read_all_from_fd(guard.detail_fd); + close(guard.detail_fd); + guard.detail_fd = -1; + + ASSERT_TRUE(WIFEXITED(status)) << "child terminated abnormally, status=0x" << std::hex + << status; + EXPECT_EQ(0, WEXITSTATUS(status)) << detail; + + guard.pid = -1; + + best_effort_rmdir(inside); + best_effort_rmdir(rootfs); + best_effort_rmdir(outside); + best_effort_rmdir(base); +} + +int main(int argc, char** argv) { + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/user/apps/tests/dunitest/suites/normal/proc_pid_mounts_target_view.cc b/user/apps/tests/dunitest/suites/normal/proc_pid_mounts_target_view.cc deleted file mode 100644 index 8c37d28c01..0000000000 --- a/user/apps/tests/dunitest/suites/normal/proc_pid_mounts_target_view.cc +++ /dev/null @@ -1,282 +0,0 @@ -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#ifndef CLONE_NEWNS -#define CLONE_NEWNS 0x00020000 -#endif - -namespace { - -struct ChildProcessGuard { - pid_t pid = -1; - int quit_fd = -1; - int detail_fd = -1; - - ~ChildProcessGuard() { - if (quit_fd >= 0) { - char quit = 'Q'; - const ssize_t ignored = write(quit_fd, &quit, 1); - (void)ignored; - close(quit_fd); - } - - if (detail_fd >= 0) { - close(detail_fd); - } - - if (pid > 0) { - int status = 0; - while (waitpid(pid, &status, 0) < 0 && errno == EINTR) { - } - } - } -}; - -int ensure_dir(const char* path) { - struct stat st = {}; - - if (stat(path, &st) == 0) { - return S_ISDIR(st.st_mode) ? 0 : -1; - } - - return mkdir(path, 0755); -} - -void best_effort_rmdir(const char* path) { - if (rmdir(path) != 0 && errno != ENOENT && errno != ENOTEMPTY) { - ADD_FAILURE() << "rmdir failed for " << path << ": errno=" << errno << " (" - << strerror(errno) << ")"; - } -} - -std::string read_all_from_fd(int fd) { - std::string out; - char buf[512]; - ssize_t n = 0; - - while ((n = read(fd, buf, sizeof(buf))) > 0) { - out.append(buf, static_cast(n)); - } - - return out; -} - -bool read_text_file(const char* path, std::string* out) { - int fd = open(path, O_RDONLY); - if (fd < 0) { - return false; - } - - out->clear(); - char buf[1024]; - ssize_t n = 0; - while ((n = read(fd, buf, sizeof(buf))) > 0) { - out->append(buf, static_cast(n)); - } - - const int saved_errno = errno; - close(fd); - errno = saved_errno; - return n >= 0; -} - -void expect_contains(const char* path, const std::string& content, const char* needle) { - EXPECT_NE(std::string::npos, content.find(needle)) << path << " missing substring\nneedle=" - << needle << "\ncontent=" << content; -} - -void expect_not_contains(const char* path, const std::string& content, const char* needle) { - EXPECT_EQ(std::string::npos, content.find(needle)) - << path << " unexpectedly contains substring\nneedle=" << needle << "\ncontent=" - << content; -} - -[[noreturn]] void child_fail(int detail_fd, const char* step) { - dprintf(detail_fd, - "%s: errno=%d (%s)", - step, - errno, - errno == 0 ? "no error information" : strerror(errno)); - _exit(1); -} - -} // namespace - -TEST(ProcPidMounts, UsesTargetTaskRootAndMountNamespace) { - char base[256] = {}; - char rootfs[256] = {}; - char inside_name[64] = {}; - char inside[256] = {}; - char outside[256] = {}; - char proc_mounts_path[64] = {}; - char proc_mountinfo_path[64] = {}; - int ready_pipe[2] = {-1, -1}; - int quit_pipe[2] = {-1, -1}; - int detail_pipe[2] = {-1, -1}; - std::string proc_mounts; - std::string proc_mountinfo; - std::string self_mounts; - std::string self_mountinfo; - char inside_mounts_needle[96] = {}; - char inside_mountinfo_needle[96] = {}; - - ASSERT_EQ(0, ensure_dir("/tmp")) << "mkdir /tmp failed: errno=" << errno << " (" - << strerror(errno) << ")"; - - snprintf(base, sizeof(base), "/tmp/proc_pid_mounts_target_view_%d", getpid()); - snprintf(rootfs, sizeof(rootfs), "%s/rootfs", base); - snprintf(inside_name, sizeof(inside_name), "inside_%d", getpid()); - snprintf(inside, sizeof(inside), "%s/%s", rootfs, inside_name); - snprintf(outside, sizeof(outside), "%s/outside", base); - snprintf(inside_mounts_needle, sizeof(inside_mounts_needle), " /%s ramfs ", inside_name); - snprintf(inside_mountinfo_needle, sizeof(inside_mountinfo_needle), " /%s ", inside_name); - - ASSERT_EQ(0, ensure_dir(base)) << "mkdir base failed: errno=" << errno << " (" - << strerror(errno) << ")"; - ASSERT_EQ(0, ensure_dir(rootfs)) << "mkdir rootfs failed: errno=" << errno << " (" - << strerror(errno) << ")"; - ASSERT_EQ(0, ensure_dir(outside)) << "mkdir outside failed: errno=" << errno << " (" - << strerror(errno) << ")"; - - ASSERT_EQ(0, pipe(ready_pipe)) << "pipe ready failed: errno=" << errno << " (" - << strerror(errno) << ")"; - ASSERT_EQ(0, pipe(quit_pipe)) << "pipe quit failed: errno=" << errno << " (" - << strerror(errno) << ")"; - ASSERT_EQ(0, pipe(detail_pipe)) << "pipe detail failed: errno=" << errno << " (" - << strerror(errno) << ")"; - - const pid_t child = fork(); - ASSERT_GE(child, 0) << "fork failed: errno=" << errno << " (" << strerror(errno) << ")"; - - if (child == 0) { - close(ready_pipe[0]); - close(quit_pipe[1]); - close(detail_pipe[0]); - - if (unshare(CLONE_NEWNS) != 0) { - child_fail(detail_pipe[1], "unshare(CLONE_NEWNS)"); - } - if (mount("", "/", nullptr, MS_REC | MS_PRIVATE, nullptr) != 0) { - child_fail(detail_pipe[1], "mount(/, MS_PRIVATE)"); - } - if (mount("", rootfs, "ramfs", 0, nullptr) != 0) { - child_fail(detail_pipe[1], "mount(rootfs, ramfs)"); - } - if (ensure_dir(inside) != 0) { - child_fail(detail_pipe[1], "mkdir(inside)"); - } - if (mount("", inside, "ramfs", 0, nullptr) != 0) { - child_fail(detail_pipe[1], "mount(inside, ramfs)"); - } - if (mount("", outside, "ramfs", 0, nullptr) != 0) { - child_fail(detail_pipe[1], "mount(outside, ramfs)"); - } - if (chdir(rootfs) != 0) { - child_fail(detail_pipe[1], "chdir(rootfs)"); - } - if (chroot(rootfs) != 0) { - child_fail(detail_pipe[1], "chroot(rootfs)"); - } - if (chdir("/") != 0) { - child_fail(detail_pipe[1], "chdir(/)"); - } - - const char ready = 'R'; - if (write(ready_pipe[1], &ready, 1) != 1) { - child_fail(detail_pipe[1], "notify parent ready"); - } - - char quit = 0; - if (read(quit_pipe[0], &quit, 1) != 1) { - child_fail(detail_pipe[1], "wait parent quit"); - } - - close(ready_pipe[1]); - close(quit_pipe[0]); - close(detail_pipe[1]); - _exit(0); - } - - close(ready_pipe[1]); - close(quit_pipe[0]); - close(detail_pipe[1]); - - ChildProcessGuard guard; - guard.pid = child; - guard.quit_fd = quit_pipe[1]; - guard.detail_fd = detail_pipe[0]; - - char ready = 0; - ASSERT_EQ(1, read(ready_pipe[0], &ready, 1)) << "read child ready failed: errno=" << errno - << " (" << strerror(errno) << ")"; - EXPECT_EQ('R', ready); - close(ready_pipe[0]); - - snprintf(proc_mounts_path, sizeof(proc_mounts_path), "/proc/%d/mounts", child); - ASSERT_TRUE(read_text_file(proc_mounts_path, &proc_mounts)) - << "read " << proc_mounts_path << " failed: errno=" << errno << " (" << strerror(errno) - << ")"; - expect_contains(proc_mounts_path, proc_mounts, " / ramfs "); - expect_contains(proc_mounts_path, proc_mounts, inside_mounts_needle); - expect_not_contains(proc_mounts_path, proc_mounts, "/outside"); - - snprintf(proc_mountinfo_path, sizeof(proc_mountinfo_path), "/proc/%d/mountinfo", child); - ASSERT_TRUE(read_text_file(proc_mountinfo_path, &proc_mountinfo)) - << "read " << proc_mountinfo_path << " failed: errno=" << errno << " (" - << strerror(errno) << ")"; - expect_contains(proc_mountinfo_path, proc_mountinfo, inside_mountinfo_needle); - expect_not_contains(proc_mountinfo_path, proc_mountinfo, outside); - - ASSERT_TRUE(read_text_file("/proc/self/mounts", &self_mounts)) - << "read /proc/self/mounts failed: errno=" << errno << " (" << strerror(errno) << ")"; - expect_not_contains("/proc/self/mounts", self_mounts, inside_mountinfo_needle); - expect_not_contains("/proc/self/mounts", self_mounts, outside); - - ASSERT_TRUE(read_text_file("/proc/self/mountinfo", &self_mountinfo)) - << "read /proc/self/mountinfo failed: errno=" << errno << " (" << strerror(errno) - << ")"; - expect_not_contains("/proc/self/mountinfo", self_mountinfo, inside_mountinfo_needle); - expect_not_contains("/proc/self/mountinfo", self_mountinfo, outside); - - char quit = 'Q'; - ASSERT_EQ(1, write(guard.quit_fd, &quit, 1)) << "write child quit failed: errno=" << errno - << " (" << strerror(errno) << ")"; - close(guard.quit_fd); - guard.quit_fd = -1; - - int status = 0; - ASSERT_EQ(child, waitpid(child, &status, 0)) << "waitpid failed: errno=" << errno << " (" - << strerror(errno) << ")"; - - const std::string detail = read_all_from_fd(guard.detail_fd); - close(guard.detail_fd); - guard.detail_fd = -1; - - ASSERT_TRUE(WIFEXITED(status)) << "child terminated abnormally, status=0x" << std::hex - << status; - EXPECT_EQ(0, WEXITSTATUS(status)) << detail; - - guard.pid = -1; - - best_effort_rmdir(inside); - best_effort_rmdir(rootfs); - best_effort_rmdir(outside); - best_effort_rmdir(base); -} - -int main(int argc, char** argv) { - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/user/apps/tests/dunitest/whitelist.txt b/user/apps/tests/dunitest/whitelist.txt index 343006670e..6484b3ffa0 100644 --- a/user/apps/tests/dunitest/whitelist.txt +++ b/user/apps/tests/dunitest/whitelist.txt @@ -32,7 +32,7 @@ normal/socket_getsockopt_semantics fuse/fuse_core fuse/fuse_extended normal/test_procfs_link -normal/proc_pid_mounts_target_view +normal/proc_mount_exports normal/proc_pid_reuse_cache normal/proc_self_exec_cmdline normal/proc_thread_accounting diff --git a/user/apps/tests/syscall/gvisor/monitor_test_results.sh b/user/apps/tests/syscall/gvisor/monitor_test_results.sh index e7588f2583..2d484e56ba 100755 --- a/user/apps/tests/syscall/gvisor/monitor_test_results.sh +++ b/user/apps/tests/syscall/gvisor/monitor_test_results.sh @@ -18,7 +18,7 @@ SERIAL_FILE="serial_opt.txt" # 超时配置(秒) BOOT_TIMEOUT=300 # DragonOS开机超时(5分钟) TEST_START_TIMEOUT=600 # 测试程序启动超时(10分钟) -TEST_TIMEOUT=1800 # 整个测试超时(30分钟) +TEST_TIMEOUT=2100 # 整个测试超时(35分钟) IDLE_TIMEOUT=120 # 无输出超时(5分钟) SINGLE_TEST_TIMEOUT=60 # 单个测试用例超时(1分钟)