From b0407dd5173552b59cf08f84a5e44914b60e67bd Mon Sep 17 00:00:00 2001 From: Xinyi Yu <1809327837@qq.com> Date: Mon, 2 Feb 2026 03:35:02 +0000 Subject: [PATCH] Add `proc/self/mounts` and `proc/mounts` --- kernel/src/device/mod.rs | 7 +- kernel/src/device/pty/mod.rs | 7 +- kernel/src/device/shm.rs | 7 +- kernel/src/fs/path/dentry.rs | 4 +- kernel/src/fs/path/mod.rs | 10 +- kernel/src/fs/path/mount.rs | 43 ++++-- kernel/src/fs/path/resolver.rs | 128 ++++------------- kernel/src/fs/procfs/mod.rs | 3 + kernel/src/fs/procfs/mounts.rs | 30 ++++ kernel/src/fs/procfs/pid/task/mod.rs | 6 +- kernel/src/fs/procfs/pid/task/mountinfo.rs | 130 +++++++++++++++++- kernel/src/fs/procfs/pid/task/mounts.rs | 121 ++++++++++++++++ kernel/src/fs/utils/fs.rs | 5 + kernel/src/syscall/mount.rs | 62 +++++---- .../src/syscall/gvisor/blocklists/proc_test | 3 - 15 files changed, 415 insertions(+), 151 deletions(-) create mode 100644 kernel/src/fs/procfs/mounts.rs create mode 100644 kernel/src/fs/procfs/pid/task/mounts.rs diff --git a/kernel/src/device/mod.rs b/kernel/src/device/mod.rs index 49ca6670d..414757524 100644 --- a/kernel/src/device/mod.rs +++ b/kernel/src/device/mod.rs @@ -36,7 +36,12 @@ pub fn init_in_first_process(ctx: &Context) -> Result<()> { // Mount devtmpfs. let dev_path = path_resolver.lookup(&FsPath::try_from("/dev")?)?; - dev_path.mount(RamFs::new(), PerMountFlags::default(), ctx)?; + dev_path.mount( + RamFs::new(), + PerMountFlags::default(), + Some("ramfs".to_string()), + ctx, + )?; tty::init_in_first_process()?; pty::init_in_first_process(&path_resolver, ctx)?; diff --git a/kernel/src/device/pty/mod.rs b/kernel/src/device/pty/mod.rs index dc4e88074..ed188584b 100644 --- a/kernel/src/device/pty/mod.rs +++ b/kernel/src/device/pty/mod.rs @@ -24,7 +24,12 @@ pub fn init_in_first_process(path_resolver: &PathResolver, ctx: &Context) -> Res let dev = path_resolver.lookup(&FsPath::try_from("/dev")?)?; // Create the "pts" directory and mount devpts on it. let devpts_path = dev.new_fs_child("pts", InodeType::Dir, mkmod!(a+rx, u+w))?; - let devpts_mount = devpts_path.mount(DevPts::new(), PerMountFlags::default(), ctx)?; + let devpts_mount = devpts_path.mount( + DevPts::new(), + PerMountFlags::default(), + Some("devpts".to_string()), + ctx, + )?; DEV_PTS.call_once(|| Path::new_fs_root(devpts_mount)); diff --git a/kernel/src/device/shm.rs b/kernel/src/device/shm.rs index 8bd9a1125..6bc6c01e5 100644 --- a/kernel/src/device/shm.rs +++ b/kernel/src/device/shm.rs @@ -18,7 +18,12 @@ pub fn init_in_first_process(path_resolver: &PathResolver, ctx: &Context) -> Res // Create the "shm" directory under "/dev" and mount a ramfs on it. let shm_path = dev_path.new_fs_child("shm", InodeType::Dir, chmod!(InodeMode::S_ISVTX, a+rwx))?; - shm_path.mount(RamFs::new(), PerMountFlags::default(), ctx)?; + shm_path.mount( + RamFs::new(), + PerMountFlags::default(), + Some("tmpfs".to_string()), + ctx, + )?; log::debug!("Mount RamFs at \"/dev/shm\""); Ok(()) } diff --git a/kernel/src/fs/path/dentry.rs b/kernel/src/fs/path/dentry.rs index 6e039903e..66d5cc265 100644 --- a/kernel/src/fs/path/dentry.rs +++ b/kernel/src/fs/path/dentry.rs @@ -18,7 +18,7 @@ use crate::{ }; /// A `Dentry` represents a cached filesystem node in the VFS tree. -pub(super) struct Dentry { +pub(in crate::fs) struct Dentry { inode: Arc, type_: InodeType, name_and_parent: NameAndParent, @@ -203,7 +203,7 @@ impl Dentry { } /// Gets the absolute path name of this `Dentry` within the filesystem. - pub(super) fn path_name(&self) -> String { + pub(in crate::fs) fn path_name(&self) -> String { let mut path_name = self.name().to_string(); let mut current_dir = self.this(); diff --git a/kernel/src/fs/path/mod.rs b/kernel/src/fs/path/mod.rs index abfe97d31..ea28eb5c0 100644 --- a/kernel/src/fs/path/mod.rs +++ b/kernel/src/fs/path/mod.rs @@ -78,7 +78,7 @@ impl Path { Self::new(mount, dentry) } - fn new(mount: Arc, dentry: Arc) -> Self { + pub(in crate::fs) fn new(mount: Arc, dentry: Arc) -> Self { Self { mount, dentry } } @@ -87,6 +87,11 @@ impl Path { &self.mount } + /// Gets the dentry of current `Path`. + pub(in crate::fs) fn dentry(&self) -> &Arc { + &self.dentry + } + /// Returns true if the current `Path` is the root of its mount. pub fn is_mount_root(&self) -> bool { Arc::ptr_eq(&self.dentry, self.mount.root_dentry()) @@ -203,6 +208,7 @@ impl Path { &self, fs: Arc, flags: PerMountFlags, + source: Option, ctx: &Context, ) -> Result> { if self.type_() != InodeType::Dir { @@ -222,7 +228,7 @@ impl Path { return_errno_with_message!(Errno::EINVAL, "the path is not in this mount namespace"); } - let child_mount = self.mount.do_mount(fs, flags, &self.dentry)?; + let child_mount = self.mount.do_mount(fs, flags, &self.dentry, source)?; Ok(child_mount) } diff --git a/kernel/src/fs/path/mount.rs b/kernel/src/fs/path/mount.rs index f276b5eca..73e10d7c7 100644 --- a/kernel/src/fs/path/mount.rs +++ b/kernel/src/fs/path/mount.rs @@ -160,6 +160,15 @@ pub struct Mount { mountpoint: RwLock>>, /// The associated FS. fs: Arc, + /// The mount source (e.g., a device path like "/dev/vda" or a filesystem name like "proc"). + /// + /// The source is stored in `Mount` instead of requiring each filesystem to provide it. + /// If a filesystem does not provide a source, it falls back to the value stored in `Mount`. + /// This behavior aligns with that of Linux. Concrete examples can be found here: + /// . + /// + /// Reference: + source: Option, /// The parent mount node. parent: RwLock>>, /// Child mount nodes which are mounted on one dentry of self. @@ -186,7 +195,8 @@ impl Mount { fs: Arc, mnt_ns: Weak, ) -> Arc { - Self::new(fs, PerMountFlags::default(), None, mnt_ns) + let source = fs.name().to_string(); + Self::new(fs, PerMountFlags::default(), None, mnt_ns, Some(source)) } /// Creates a pseudo mount node with an associated FS. @@ -194,7 +204,7 @@ impl Mount { /// This pseudo mount is not mounted on other mount nodes, has no parent, and does not /// belong to any mount namespace. pub(in crate::fs) fn new_pseudo(fs: Arc) -> Arc { - Self::new(fs, PerMountFlags::KERNMOUNT, None, Weak::new()) + Self::new(fs, PerMountFlags::KERNMOUNT, None, Weak::new(), None) } /// The internal constructor. @@ -210,6 +220,7 @@ impl Mount { flags: PerMountFlags, parent_mount: Option>, mnt_ns: Weak, + source: Option, ) -> Arc { let id = ID_ALLOCATOR.get().unwrap().lock().alloc().unwrap(); @@ -221,6 +232,7 @@ impl Mount { children: RwLock::new(HashMap::new()), propagation: RwLock::new(MountPropType::default()), fs, + source, mnt_ns, flags: AtomicPerMountFlags::new(flags), this: weak_self.clone(), @@ -232,6 +244,11 @@ impl Mount { self.id } + /// Returns the mount source. + pub(in crate::fs) fn source(&self) -> Option<&str> { + self.fs.source().or(self.source.as_deref()) + } + /// Mounts a fs on the mountpoint, it will create a new child mount node. /// /// If the given mountpoint has already been mounted, then its mounted child mount @@ -242,19 +259,28 @@ impl Mount { /// It is allowed to mount a fs even if the fs has been provided to another /// mountpoint. It is the fs's responsibility to ensure the data consistency. /// + /// If the source is provided by user, it will be recorded in the new mount. + /// /// Return the mounted child mount. pub(super) fn do_mount( self: &Arc, fs: Arc, flags: PerMountFlags, mountpoint: &Arc, + source: Option, ) -> Result> { if mountpoint.type_() != InodeType::Dir { return_errno!(Errno::ENOTDIR); } let key = mountpoint.key(); - let child_mount = Self::new(fs, flags, Some(Arc::downgrade(self)), self.mnt_ns.clone()); + let child_mount = Self::new( + fs, + flags, + Some(Arc::downgrade(self)), + self.mnt_ns.clone(), + source, + ); self.children.write().insert(key, child_mount.clone()); child_mount.set_mountpoint(mountpoint); @@ -296,6 +322,7 @@ impl Mount { children: RwLock::new(HashMap::new()), propagation: RwLock::new(MountPropType::default()), fs: self.fs.clone(), + source: self.source.clone(), mnt_ns: new_ns.cloned().unwrap_or_else(|| self.mnt_ns.clone()), flags: AtomicPerMountFlags::new(self.flags.load(Ordering::Relaxed)), this: weak_self.clone(), @@ -405,12 +432,12 @@ impl Mount { } /// Gets the root `Dentry` of this mount node. - pub(super) fn root_dentry(&self) -> &Arc { + pub(in crate::fs) fn root_dentry(&self) -> &Arc { &self.root_dentry } /// Gets the mountpoint `Dentry` of this mount node if any. - pub(super) fn mountpoint(&self) -> Option> { + pub(in crate::fs) fn mountpoint(&self) -> Option> { self.mountpoint.read().clone() } @@ -481,7 +508,7 @@ impl Mount { } /// Gets the parent mount node if any. - pub(super) fn parent(&self) -> Option> { + pub(in crate::fs) fn parent(&self) -> Option> { self.parent.read().as_ref().cloned() } @@ -491,11 +518,11 @@ impl Mount { } /// Gets the associated FS. - pub(super) fn fs(&self) -> &Arc { + pub(in crate::fs) fn fs(&self) -> &Arc { &self.fs } - pub(super) fn flags(&self) -> PerMountFlags { + pub(in crate::fs) fn flags(&self) -> PerMountFlags { self.flags.load(Ordering::Relaxed) } diff --git a/kernel/src/fs/path/resolver.rs b/kernel/src/fs/path/resolver.rs index 2e4afb189..c36ef2096 100644 --- a/kernel/src/fs/path/resolver.rs +++ b/kernel/src/fs/path/resolver.rs @@ -2,15 +2,14 @@ use alloc::str; -use aster_util::printer::VmPrinter; use ostd::task::Task; -use super::Path; +use super::{Mount, Path}; use crate::{ fs::{ file_table::{FileDesc, get_file_fast}, - path::{MountNamespace, PerMountFlags}, - utils::{FsFlags, InodeType, NAME_MAX, PATH_MAX, Permission, SYMLINKS_MAX, SymbolicLink}, + path::MountNamespace, + utils::{InodeType, NAME_MAX, PATH_MAX, Permission, SYMLINKS_MAX, SymbolicLink}, }, prelude::*, process::posix_thread::AsThreadLocal, @@ -253,122 +252,43 @@ impl AbsPathResult { // Mount info reading implementation impl PathResolver { - /// Reads the information of the mounts visible to this resolver. + /// Collects the mounts visible to this resolver. /// /// Here, the visible mounts are defined as follows: /// 1. If the resolver's root is a mount point, the visible mounts are the mount of the /// resolver's root directory and all of its descendant mounts in the mount tree. /// 2. If the resolver's root is not a mount point, the visible mounts are all descendant /// mounts that are mounted under the resolver's root directory. - pub fn read_mount_info(&self, offset: usize, writer: &mut VmWriter) -> Result { - let mut printer = VmPrinter::new_skip(writer, offset); - - let mut stack = Vec::new(); - if self.root.is_mount_root() { - stack.push(self.root.mount.clone()); - } else { - // The root is not a mount root, so we need to find the visible child mounts. - let children = self.root.mount.children.read(); - for child_mount in children.values() { - if child_mount - .mountpoint() - .is_some_and(|dentry| dentry.is_equal_or_descendant_of(&self.root.dentry)) - { - stack.push(child_mount.clone()); - } - } - } + /// + /// The mounts are collected in depth-first order. + pub(in crate::fs) fn collect_visible_mounts(&self) -> Vec> { + let mut visible = Vec::new(); + let mut stack = vec![self.root.mount.clone()]; + let is_root_mount_root = self.root.is_mount_root(); while let Some(mount) = stack.pop() { - let mount_id = mount.id(); - let parent = mount.parent().and_then(|parent| parent.upgrade()); - let parent_id = parent.as_ref().map_or(mount_id, |p| p.id()); - let root = mount.root_dentry().path_name(); - let mount_point = if let Some(parent) = parent { - if let Some(mount_point_dentry) = mount.mountpoint() { - self.make_abs_path(&Path::new(parent, mount_point_dentry)) - .into_string() - } else { - "".to_string() - } - } else { - // No parent means it's the root of the namespace. - "/".to_string() - }; - let mount_flags = mount.flags(); - let fs_type = mount.fs().name(); - let fs_flags = mount.fs().flags(); + let is_root_mount = Arc::ptr_eq(&mount, &self.root.mount); - // The following fields are dummy for now. - let major = 0; - let minor = 0; - let source = "none"; - - let entry = MountInfoEntry { - mount_id, - parent_id, - major, - minor, - root: &root, - mount_point: &mount_point, - mount_flags, - fs_type, - source, - fs_flags, - }; - - writeln!(printer, "{}", entry)?; + // Add the root mount only if `self` is at the mount root. + if !is_root_mount || is_root_mount_root { + visible.push(mount.clone()); + } let children = mount.children.read(); for child_mount in children.values() { + if is_root_mount && !is_root_mount_root { + let Some(mountpoint) = child_mount.mountpoint() else { + continue; + }; + if !mountpoint.is_equal_or_descendant_of(&self.root.dentry) { + continue; + } + } stack.push(child_mount.clone()); } } - Ok(printer.bytes_written()) - } -} - -/// A single entry in the mountinfo file. -struct MountInfoEntry<'a> { - /// A unique ID for the mount (but not guaranteed to be unique across reboots). - mount_id: usize, - /// The ID of the parent mount (or self if it has no parent). - parent_id: usize, - /// The major device ID of the filesystem. - major: u32, - /// The minor device ID of the filesystem. - minor: u32, - /// The root of the mount within the filesystem. - root: &'a str, - /// The mount point relative to the process's root directory. - mount_point: &'a str, - /// Per-mount flags. - mount_flags: PerMountFlags, - /// The type of the filesystem in the form "type[.subtype]". - fs_type: &'a str, - /// Filesystem-specific information or "none". - source: &'a str, - /// Per-filesystem flags. - fs_flags: FsFlags, -} - -impl core::fmt::Display for MountInfoEntry<'_> { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - write!( - f, - "{} {} {}:{} {} {} {} - {} {} {}", - self.mount_id, - self.parent_id, - self.major, - self.minor, - &self.root, - &self.mount_point, - &self.mount_flags, - &self.fs_type, - &self.source, - &self.fs_flags, - ) + visible } } diff --git a/kernel/src/fs/procfs/mod.rs b/kernel/src/fs/procfs/mod.rs index 690ffb14c..2351e68ce 100644 --- a/kernel/src/fs/procfs/mod.rs +++ b/kernel/src/fs/procfs/mod.rs @@ -11,6 +11,7 @@ use self::{ cpuinfo::CpuInfoFileOps, loadavg::LoadAvgFileOps, meminfo::MemInfoFileOps, + mounts::MountsSymOps, pid::PidDirOps, self_::SelfSymOps, sys::SysDirOps, @@ -41,6 +42,7 @@ mod cpuinfo; mod filesystems; mod loadavg; mod meminfo; +mod mounts; mod pid; mod self_; mod stat; @@ -159,6 +161,7 @@ impl RootDirOps { ("filesystems", FileSystemsFileOps::new_inode), ("loadavg", LoadAvgFileOps::new_inode), ("meminfo", MemInfoFileOps::new_inode), + ("mounts", MountsSymOps::new_inode), ("self", SelfSymOps::new_inode), ("stat", StatFileOps::new_inode), ("sys", SysDirOps::new_inode), diff --git a/kernel/src/fs/procfs/mounts.rs b/kernel/src/fs/procfs/mounts.rs new file mode 100644 index 000000000..bedb485dc --- /dev/null +++ b/kernel/src/fs/procfs/mounts.rs @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: MPL-2.0 + +use crate::{ + fs::{ + procfs::{ProcSymBuilder, SymOps}, + utils::{Inode, SymbolicLink, mkmod}, + }, + prelude::*, +}; + +/// Represents the inode at `/proc/mounts`. +pub struct MountsSymOps; + +impl MountsSymOps { + pub fn new_inode(parent: Weak) -> Arc { + // Reference: + // + // + ProcSymBuilder::new(Self, mkmod!(a+rwx)) + .parent(parent) + .build() + .unwrap() + } +} + +impl SymOps for MountsSymOps { + fn read_link(&self) -> Result { + Ok(SymbolicLink::Plain("self/mounts".to_string())) + } +} diff --git a/kernel/src/fs/procfs/pid/task/mod.rs b/kernel/src/fs/procfs/pid/task/mod.rs index 4750341fa..54cbbfdd2 100644 --- a/kernel/src/fs/procfs/pid/task/mod.rs +++ b/kernel/src/fs/procfs/pid/task/mod.rs @@ -12,8 +12,8 @@ use crate::{ cgroup::CgroupFileOps, cmdline::CmdlineFileOps, comm::CommFileOps, environ::EnvironFileOps, exe::ExeSymOps, fd::FdDirOps, gid_map::GidMapFileOps, maps::MapsFileOps, mem::MemFileOps, mountinfo::MountInfoFileOps, - oom_score_adj::OomScoreAdjFileOps, stat::StatFileOps, status::StatusFileOps, - uid_map::UidMapFileOps, + mounts::MountsFileOps, oom_score_adj::OomScoreAdjFileOps, stat::StatFileOps, + status::StatusFileOps, uid_map::UidMapFileOps, }, template::{ DirOps, ProcDir, ProcDirBuilder, lookup_child_from_table, @@ -37,6 +37,7 @@ mod gid_map; mod maps; mod mem; mod mountinfo; +mod mounts; mod oom_score_adj; mod stat; mod status; @@ -115,6 +116,7 @@ impl TidDirOps { ("status", StatusFileOps::new_inode), ("uid_map", UidMapFileOps::new_inode), ("maps", MapsFileOps::new_inode), + ("mounts", MountsFileOps::new_inode), ]; } diff --git a/kernel/src/fs/procfs/pid/task/mountinfo.rs b/kernel/src/fs/procfs/pid/task/mountinfo.rs index d48eaa3c2..c14ea4afa 100644 --- a/kernel/src/fs/procfs/pid/task/mountinfo.rs +++ b/kernel/src/fs/procfs/pid/task/mountinfo.rs @@ -1,15 +1,84 @@ // SPDX-License-Identifier: MPL-2.0 +use aster_util::printer::VmPrinter; + use super::TidDirOps; use crate::{ fs::{ + path::{Mount, Path, PathResolver, PerMountFlags}, procfs::template::{FileOps, ProcFileBuilder}, - utils::{Inode, mkmod}, + utils::{FsFlags, Inode, mkmod}, }, prelude::*, process::posix_thread::AsPosixThread, }; +/// A helper function to create the mount point path for a given mount (used by `mounts` and `mountinfo`). +pub(super) fn make_mount_point_path( + is_resolver_root_mount: bool, + parent: Option<&Arc>, + mount: &Mount, + path_resolver: &PathResolver, +) -> String { + if is_resolver_root_mount { + "/".to_string() + } else if let Some(parent) = parent { + if let Some(mount_point_dentry) = mount.mountpoint() { + path_resolver + .make_abs_path(&Path::new(parent.clone(), mount_point_dentry)) + .into_string() + } else { + "".to_string() + } + } else { + // No parent means it's the root of the namespace. + "/".to_string() + } +} + +/// A single entry in the mountinfo file. +struct MountInfoEntry<'a> { + /// A unique ID for the mount (but not guaranteed to be unique across reboots). + mount_id: usize, + /// The ID of the parent mount (or self if it has no parent). + parent_id: usize, + /// The major device ID of the filesystem. + major: u32, + /// The minor device ID of the filesystem. + minor: u32, + /// The root of the mount within the filesystem. + root: &'a str, + /// The mount point relative to the process's root directory. + mount_point: &'a str, + /// Per-mount flags. + mount_flags: PerMountFlags, + /// The type of the filesystem in the form "type[.subtype]". + fs_type: &'a str, + /// Filesystem-specific information or "none". + source: &'a str, + /// Per-filesystem flags. + fs_flags: FsFlags, +} + +impl core::fmt::Display for MountInfoEntry<'_> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!( + f, + "{} {} {}:{} {} {} {} - {} {} {}", + self.mount_id, + self.parent_id, + self.major, + self.minor, + &self.root, + &self.mount_point, + &self.mount_flags, + &self.fs_type, + &self.source, + &self.fs_flags, + ) + } +} + /// Represents the inode at `/proc/[pid]/task/[tid]/mountinfo` (and also `/proc/[pid]/mountinfo`). pub struct MountInfoFileOps(TidDirOps); @@ -21,6 +90,62 @@ impl MountInfoFileOps { .build() .unwrap() } + + /// Reads mount information for `/proc/[pid]/mountinfo`. + /// + /// Provides detailed mount information including mount IDs, parent relationships, + /// and device numbers. + fn read_mount_info( + &self, + path_resolver: &PathResolver, + offset: usize, + writer: &mut VmWriter, + ) -> Result { + let mut printer = VmPrinter::new_skip(writer, offset); + + for mount in path_resolver.collect_visible_mounts() { + let mount_id = mount.id(); + let parent = mount.parent().and_then(|parent| parent.upgrade()); + let parent_id = parent.as_ref().map_or(mount_id, |p| p.id()); + let is_resolver_root_mount = Arc::ptr_eq(&mount, path_resolver.root().mount_node()); + let root = if is_resolver_root_mount { + path_resolver.root().dentry().path_name() + } else { + mount.root_dentry().path_name() + }; + let mount_point = make_mount_point_path( + is_resolver_root_mount, + parent.as_ref(), + mount.as_ref(), + path_resolver, + ); + let mount_flags = mount.flags(); + let fs_type = mount.fs().name(); + let source = mount.source().unwrap_or("none"); + let fs_flags = mount.fs().flags(); + + // The following fields are dummy for now. + let major = 0; + let minor = 0; + + let entry = MountInfoEntry { + mount_id, + parent_id, + major, + minor, + root: &root, + mount_point: &mount_point, + mount_flags, + fs_type, + source, + fs_flags, + }; + + writeln!(printer, "{}", entry)?; + } + + Ok(printer.bytes_written()) + } } impl FileOps for MountInfoFileOps { @@ -30,7 +155,6 @@ impl FileOps for MountInfoFileOps { let fs = posix_thread.read_fs(); let path_resolver = fs.resolver().read(); - - path_resolver.read_mount_info(offset, writer) + self.read_mount_info(&path_resolver, offset, writer) } } diff --git a/kernel/src/fs/procfs/pid/task/mounts.rs b/kernel/src/fs/procfs/pid/task/mounts.rs new file mode 100644 index 000000000..6347d46d6 --- /dev/null +++ b/kernel/src/fs/procfs/pid/task/mounts.rs @@ -0,0 +1,121 @@ +// SPDX-License-Identifier: MPL-2.0 + +use aster_util::printer::VmPrinter; + +use super::TidDirOps; +use crate::{ + fs::{ + path::{PathResolver, PerMountFlags}, + procfs::{ + pid::task::mountinfo::make_mount_point_path, + template::{FileOps, ProcFileBuilder}, + }, + utils::{Inode, mkmod}, + }, + prelude::*, + process::posix_thread::AsPosixThread, +}; + +/// A single entry in the mounts file. +struct MountEntry<'a> { + /// Filesystem-specific information or "none". + source: &'a str, + /// Mount point relative to the process's root directory. + mount_point: &'a str, + /// The type of the filesystem in the form "type[.subtype]". + fs_type: &'a str, + /// Per-mount flags. + mount_flags: PerMountFlags, + /// The dump field is used by the dump(8) program to determine which + /// filesystems need to be dumped. + dump: u32, + /// The fsck(8) program uses this field. + pass: u32, +} + +impl core::fmt::Display for MountEntry<'_> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!( + f, + "{} {} {} {} {} {}", + &self.source, + &self.mount_point, + &self.fs_type, + &self.mount_flags, + &self.dump, + &self.pass, + ) + } +} + +/// Represents the inode at `/proc/[pid]/task/[tid]/mounts` (and also `/proc/[pid]/mounts`). +pub struct MountsFileOps(TidDirOps); + +impl MountsFileOps { + pub fn new_inode(dir: &TidDirOps, parent: Weak) -> Arc { + // Reference: + ProcFileBuilder::new(Self(dir.clone()), mkmod!(a+r)) + .parent(parent) + .build() + .unwrap() + } + + /// Reads mount information for `/proc/[pid]/mounts` and `/proc/mounts`. + /// + /// Provides a simplified view of mounted filesystems in the traditional + /// `/etc/fstab` format. + fn read_mounts( + &self, + path_resolver: &PathResolver, + offset: usize, + writer: &mut VmWriter, + ) -> Result { + let mut printer = VmPrinter::new_skip(writer, offset); + + for mount in path_resolver.collect_visible_mounts() { + let parent = mount.parent().and_then(|parent| parent.upgrade()); + let is_resolver_root_mount = Arc::ptr_eq(&mount, path_resolver.root().mount_node()); + let mount_point = make_mount_point_path( + is_resolver_root_mount, + parent.as_ref(), + mount.as_ref(), + path_resolver, + ); + let mount_flags = mount.flags(); + let fs_type = mount.fs().name(); + let source = mount.source().unwrap_or("none"); + + // The dump and pass fields are hardcoded to 0, because the kernel considers them + // userspace policy (managed by /etc/fstab) and does not store them in the VFS layer. + // This behavior is consistent with Linux. + // + // Reference: . + let dump = 0; + let pass = 0; + + let entry = MountEntry { + source, + mount_point: &mount_point, + fs_type, + mount_flags, + dump, + pass, + }; + + writeln!(printer, "{}", entry)?; + } + + Ok(printer.bytes_written()) + } +} + +impl FileOps for MountsFileOps { + fn read_at(&self, offset: usize, writer: &mut VmWriter) -> Result { + let thread = self.0.thread(); + let posix_thread = thread.as_posix_thread().unwrap(); + + let fs = posix_thread.read_fs(); + let path_resolver = fs.resolver().read(); + self.read_mounts(&path_resolver, offset, writer) + } +} diff --git a/kernel/src/fs/utils/fs.rs b/kernel/src/fs/utils/fs.rs index 7eeac3cd6..11b7701e4 100644 --- a/kernel/src/fs/utils/fs.rs +++ b/kernel/src/fs/utils/fs.rs @@ -150,6 +150,11 @@ pub trait FileSystem: Any + Sync + Send { /// Gets the name of this FS type such as `"ext4"` or `"sysfs"`. fn name(&self) -> &'static str; + /// Gets the source of this file system, e.g., the device name or user-provided source string. + fn source(&self) -> Option<&str> { + None + } + /// Syncs the file system. fn sync(&self) -> Result<()>; diff --git a/kernel/src/syscall/mount.rs b/kernel/src/syscall/mount.rs index 67d2aa0b1..fd1caceb0 100644 --- a/kernel/src/syscall/mount.rs +++ b/kernel/src/syscall/mount.rs @@ -6,7 +6,7 @@ use super::SyscallReturn; use crate::{ fs::{ path::{AT_FDCWD, FsPath, MountPropType, Path, PerMountFlags}, - registry::FsProperties, + registry::{FsProperties, FsType}, utils::{FileSystem, FsFlags, InodeType}, }, prelude::*, @@ -192,22 +192,44 @@ fn do_new_mount( return_errno_with_message!(Errno::ENOTDIR, "mountpoint must be directory"); }; - let fs_type = ctx - .user_space() - .read_cstring(fs_type_addr, MAX_FILENAME_LEN)?; - if fs_type.is_empty() { - return_errno_with_message!(Errno::EINVAL, "fs_type is empty"); - } - let fs = get_fs(src_name_addr, flags, fs_type, data_addr, ctx)?; - target_path.mount(fs, flags.into(), ctx)?; + let fs_type = { + let fs_type_cstr = ctx + .user_space() + .read_cstring(fs_type_addr, MAX_FILENAME_LEN)?; + if fs_type_cstr.is_empty() { + return_errno_with_message!(Errno::EINVAL, "empty file system type"); + } + + let fs_type_str = fs_type_cstr + .to_str() + .map_err(|_| Error::with_message(Errno::ENODEV, "invalid file system type"))?; + crate::fs::registry::look_up(fs_type_str).ok_or(Error::with_message( + Errno::ENODEV, + "the filesystem is not configured in the kernel", + ))? + }; + + let source = if src_name_addr == 0 { + None + } else { + let source = ctx + .user_space() + .read_cstring(src_name_addr, MAX_FILENAME_LEN)? + .to_string_lossy() + .into_owned(); + Some(source) + }; + + let fs = open_fs(source.as_deref(), flags, fs_type, data_addr, ctx)?; + target_path.mount(fs, flags.into(), source, ctx)?; Ok(()) } -/// Gets the filesystem by fs_type and devname. -fn get_fs( - src_name_addr: Vaddr, +/// Gets the filesystem by fs_type and dev_name. +fn open_fs( + dev_name: Option<&str>, flags: MountFlags, - fs_type: CString, + fs_type: &dyn FsType, data_addr: Vaddr, ctx: &Context, ) -> Result> { @@ -218,18 +240,10 @@ fn get_fs( Some(user_space.read_cstring(data_addr, MAX_FILENAME_LEN)?) }; - let fs_type = fs_type - .to_str() - .map_err(|_| Error::with_message(Errno::ENODEV, "invalid file system type"))?; - let fs_type = crate::fs::registry::look_up(fs_type).ok_or(Error::with_message( - Errno::ENODEV, - "the filesystem is not configured in the kernel", - ))?; - let disk = if fs_type.properties().contains(FsProperties::NEED_DISK) { - let devname = user_space.read_cstring(src_name_addr, MAX_FILENAME_LEN)?; - let path = devname.to_string_lossy(); - let fs_path = FsPath::from_fd_and_path(AT_FDCWD, path.as_ref())?; + let dev_name = dev_name + .ok_or_else(|| Error::with_message(Errno::EINVAL, "the source is not specified"))?; + let fs_path = FsPath::from_fd_and_path(AT_FDCWD, dev_name)?; let path = ctx .thread_local .borrow_fs() diff --git a/test/initramfs/src/syscall/gvisor/blocklists/proc_test b/test/initramfs/src/syscall/gvisor/blocklists/proc_test index 0e072e159..a1bc2d5f1 100644 --- a/test/initramfs/src/syscall/gvisor/blocklists/proc_test +++ b/test/initramfs/src/syscall/gvisor/blocklists/proc_test @@ -7,7 +7,6 @@ ProcCpuinfo.RequiredFieldsArePresent ProcCpuinfo.DeniesWriteNonRoot ProcFilesystems.OverflowID ProcFilesystems.PresenceOfShmMaxMniAll -ProcMounts.IsSymlink ProcPid.AccessDeny ProcPidCwd.Subprocess ProcPidRoot.Subprocess @@ -31,8 +30,6 @@ ProcSelfFdInfo.Flags # TODO: Mappings created with only `PROT_WRITE` should be shown as `-w-`. ProcSelfMaps.Map2 ProcSelfMaps.MapUnmap -ProcSelfMounts.ContainsProcfsEntry -ProcSelfMounts.RequiredFieldsArePresent ProcSelfRoot.IsRoot ProcSelfStat.PopulateWriteRSS ProcSysKernelHostname.Exists