Add `proc/self/mounts` and `proc/mounts`

This commit is contained in:
Xinyi Yu 2026-02-02 03:35:02 +00:00 committed by Tate, Hongliang Tian
parent ab4e0d9012
commit b0407dd517
15 changed files with 415 additions and 151 deletions

View File

@ -36,7 +36,12 @@ pub fn init_in_first_process(ctx: &Context) -> Result<()> {
// Mount devtmpfs. // Mount devtmpfs.
let dev_path = path_resolver.lookup(&FsPath::try_from("/dev")?)?; 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()?; tty::init_in_first_process()?;
pty::init_in_first_process(&path_resolver, ctx)?; pty::init_in_first_process(&path_resolver, ctx)?;

View File

@ -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")?)?; let dev = path_resolver.lookup(&FsPath::try_from("/dev")?)?;
// Create the "pts" directory and mount devpts on it. // 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_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)); DEV_PTS.call_once(|| Path::new_fs_root(devpts_mount));

View File

@ -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. // Create the "shm" directory under "/dev" and mount a ramfs on it.
let shm_path = let shm_path =
dev_path.new_fs_child("shm", InodeType::Dir, chmod!(InodeMode::S_ISVTX, a+rwx))?; 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\""); log::debug!("Mount RamFs at \"/dev/shm\"");
Ok(()) Ok(())
} }

View File

@ -18,7 +18,7 @@ use crate::{
}; };
/// A `Dentry` represents a cached filesystem node in the VFS tree. /// A `Dentry` represents a cached filesystem node in the VFS tree.
pub(super) struct Dentry { pub(in crate::fs) struct Dentry {
inode: Arc<dyn Inode>, inode: Arc<dyn Inode>,
type_: InodeType, type_: InodeType,
name_and_parent: NameAndParent, name_and_parent: NameAndParent,
@ -203,7 +203,7 @@ impl Dentry {
} }
/// Gets the absolute path name of this `Dentry` within the filesystem. /// 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 path_name = self.name().to_string();
let mut current_dir = self.this(); let mut current_dir = self.this();

View File

@ -78,7 +78,7 @@ impl Path {
Self::new(mount, dentry) Self::new(mount, dentry)
} }
fn new(mount: Arc<Mount>, dentry: Arc<Dentry>) -> Self { pub(in crate::fs) fn new(mount: Arc<Mount>, dentry: Arc<Dentry>) -> Self {
Self { mount, dentry } Self { mount, dentry }
} }
@ -87,6 +87,11 @@ impl Path {
&self.mount &self.mount
} }
/// Gets the dentry of current `Path`.
pub(in crate::fs) fn dentry(&self) -> &Arc<Dentry> {
&self.dentry
}
/// Returns true if the current `Path` is the root of its mount. /// Returns true if the current `Path` is the root of its mount.
pub fn is_mount_root(&self) -> bool { pub fn is_mount_root(&self) -> bool {
Arc::ptr_eq(&self.dentry, self.mount.root_dentry()) Arc::ptr_eq(&self.dentry, self.mount.root_dentry())
@ -203,6 +208,7 @@ impl Path {
&self, &self,
fs: Arc<dyn FileSystem>, fs: Arc<dyn FileSystem>,
flags: PerMountFlags, flags: PerMountFlags,
source: Option<String>,
ctx: &Context, ctx: &Context,
) -> Result<Arc<Mount>> { ) -> Result<Arc<Mount>> {
if self.type_() != InodeType::Dir { 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"); 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) Ok(child_mount)
} }

View File

@ -160,6 +160,15 @@ pub struct Mount {
mountpoint: RwLock<Option<Arc<Dentry>>>, mountpoint: RwLock<Option<Arc<Dentry>>>,
/// The associated FS. /// The associated FS.
fs: Arc<dyn FileSystem>, fs: Arc<dyn FileSystem>,
/// 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:
/// <https://github.com/asterinas/asterinas/pull/2929#discussion_r2729739818>.
///
/// Reference: <https://elixir.bootlin.com/linux/v6.17/source/fs/mount.h#L68>
source: Option<String>,
/// The parent mount node. /// The parent mount node.
parent: RwLock<Option<Weak<Mount>>>, parent: RwLock<Option<Weak<Mount>>>,
/// Child mount nodes which are mounted on one dentry of self. /// Child mount nodes which are mounted on one dentry of self.
@ -186,7 +195,8 @@ impl Mount {
fs: Arc<dyn FileSystem>, fs: Arc<dyn FileSystem>,
mnt_ns: Weak<MountNamespace>, mnt_ns: Weak<MountNamespace>,
) -> Arc<Self> { ) -> Arc<Self> {
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. /// 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 /// This pseudo mount is not mounted on other mount nodes, has no parent, and does not
/// belong to any mount namespace. /// belong to any mount namespace.
pub(in crate::fs) fn new_pseudo(fs: Arc<dyn FileSystem>) -> Arc<Self> { pub(in crate::fs) fn new_pseudo(fs: Arc<dyn FileSystem>) -> Arc<Self> {
Self::new(fs, PerMountFlags::KERNMOUNT, None, Weak::new()) Self::new(fs, PerMountFlags::KERNMOUNT, None, Weak::new(), None)
} }
/// The internal constructor. /// The internal constructor.
@ -210,6 +220,7 @@ impl Mount {
flags: PerMountFlags, flags: PerMountFlags,
parent_mount: Option<Weak<Mount>>, parent_mount: Option<Weak<Mount>>,
mnt_ns: Weak<MountNamespace>, mnt_ns: Weak<MountNamespace>,
source: Option<String>,
) -> Arc<Self> { ) -> Arc<Self> {
let id = ID_ALLOCATOR.get().unwrap().lock().alloc().unwrap(); let id = ID_ALLOCATOR.get().unwrap().lock().alloc().unwrap();
@ -221,6 +232,7 @@ impl Mount {
children: RwLock::new(HashMap::new()), children: RwLock::new(HashMap::new()),
propagation: RwLock::new(MountPropType::default()), propagation: RwLock::new(MountPropType::default()),
fs, fs,
source,
mnt_ns, mnt_ns,
flags: AtomicPerMountFlags::new(flags), flags: AtomicPerMountFlags::new(flags),
this: weak_self.clone(), this: weak_self.clone(),
@ -232,6 +244,11 @@ impl Mount {
self.id 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. /// 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 /// 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 /// 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. /// 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. /// Return the mounted child mount.
pub(super) fn do_mount( pub(super) fn do_mount(
self: &Arc<Self>, self: &Arc<Self>,
fs: Arc<dyn FileSystem>, fs: Arc<dyn FileSystem>,
flags: PerMountFlags, flags: PerMountFlags,
mountpoint: &Arc<Dentry>, mountpoint: &Arc<Dentry>,
source: Option<String>,
) -> Result<Arc<Self>> { ) -> Result<Arc<Self>> {
if mountpoint.type_() != InodeType::Dir { if mountpoint.type_() != InodeType::Dir {
return_errno!(Errno::ENOTDIR); return_errno!(Errno::ENOTDIR);
} }
let key = mountpoint.key(); 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()); self.children.write().insert(key, child_mount.clone());
child_mount.set_mountpoint(mountpoint); child_mount.set_mountpoint(mountpoint);
@ -296,6 +322,7 @@ impl Mount {
children: RwLock::new(HashMap::new()), children: RwLock::new(HashMap::new()),
propagation: RwLock::new(MountPropType::default()), propagation: RwLock::new(MountPropType::default()),
fs: self.fs.clone(), fs: self.fs.clone(),
source: self.source.clone(),
mnt_ns: new_ns.cloned().unwrap_or_else(|| self.mnt_ns.clone()), mnt_ns: new_ns.cloned().unwrap_or_else(|| self.mnt_ns.clone()),
flags: AtomicPerMountFlags::new(self.flags.load(Ordering::Relaxed)), flags: AtomicPerMountFlags::new(self.flags.load(Ordering::Relaxed)),
this: weak_self.clone(), this: weak_self.clone(),
@ -405,12 +432,12 @@ impl Mount {
} }
/// Gets the root `Dentry` of this mount node. /// Gets the root `Dentry` of this mount node.
pub(super) fn root_dentry(&self) -> &Arc<Dentry> { pub(in crate::fs) fn root_dentry(&self) -> &Arc<Dentry> {
&self.root_dentry &self.root_dentry
} }
/// Gets the mountpoint `Dentry` of this mount node if any. /// Gets the mountpoint `Dentry` of this mount node if any.
pub(super) fn mountpoint(&self) -> Option<Arc<Dentry>> { pub(in crate::fs) fn mountpoint(&self) -> Option<Arc<Dentry>> {
self.mountpoint.read().clone() self.mountpoint.read().clone()
} }
@ -481,7 +508,7 @@ impl Mount {
} }
/// Gets the parent mount node if any. /// Gets the parent mount node if any.
pub(super) fn parent(&self) -> Option<Weak<Self>> { pub(in crate::fs) fn parent(&self) -> Option<Weak<Self>> {
self.parent.read().as_ref().cloned() self.parent.read().as_ref().cloned()
} }
@ -491,11 +518,11 @@ impl Mount {
} }
/// Gets the associated FS. /// Gets the associated FS.
pub(super) fn fs(&self) -> &Arc<dyn FileSystem> { pub(in crate::fs) fn fs(&self) -> &Arc<dyn FileSystem> {
&self.fs &self.fs
} }
pub(super) fn flags(&self) -> PerMountFlags { pub(in crate::fs) fn flags(&self) -> PerMountFlags {
self.flags.load(Ordering::Relaxed) self.flags.load(Ordering::Relaxed)
} }

View File

@ -2,15 +2,14 @@
use alloc::str; use alloc::str;
use aster_util::printer::VmPrinter;
use ostd::task::Task; use ostd::task::Task;
use super::Path; use super::{Mount, Path};
use crate::{ use crate::{
fs::{ fs::{
file_table::{FileDesc, get_file_fast}, file_table::{FileDesc, get_file_fast},
path::{MountNamespace, PerMountFlags}, path::MountNamespace,
utils::{FsFlags, InodeType, NAME_MAX, PATH_MAX, Permission, SYMLINKS_MAX, SymbolicLink}, utils::{InodeType, NAME_MAX, PATH_MAX, Permission, SYMLINKS_MAX, SymbolicLink},
}, },
prelude::*, prelude::*,
process::posix_thread::AsThreadLocal, process::posix_thread::AsThreadLocal,
@ -253,122 +252,43 @@ impl AbsPathResult {
// Mount info reading implementation // Mount info reading implementation
impl PathResolver { 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: /// 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 /// 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. /// 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 /// 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. /// mounts that are mounted under the resolver's root directory.
pub fn read_mount_info(&self, offset: usize, writer: &mut VmWriter) -> Result<usize> { ///
let mut printer = VmPrinter::new_skip(writer, offset); /// The mounts are collected in depth-first order.
pub(in crate::fs) fn collect_visible_mounts(&self) -> Vec<Arc<Mount>> {
let mut stack = Vec::new(); let mut visible = Vec::new();
if self.root.is_mount_root() { let mut stack = vec![self.root.mount.clone()];
stack.push(self.root.mount.clone()); let is_root_mount_root = self.root.is_mount_root();
} 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());
}
}
}
while let Some(mount) = stack.pop() { while let Some(mount) = stack.pop() {
let mount_id = mount.id(); let is_root_mount = Arc::ptr_eq(&mount, &self.root.mount);
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();
// The following fields are dummy for now. // Add the root mount only if `self` is at the mount root.
let major = 0; if !is_root_mount || is_root_mount_root {
let minor = 0; visible.push(mount.clone());
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)?;
let children = mount.children.read(); let children = mount.children.read();
for child_mount in children.values() { 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()); stack.push(child_mount.clone());
} }
} }
Ok(printer.bytes_written()) visible
}
}
/// 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,
)
} }
} }

View File

@ -11,6 +11,7 @@ use self::{
cpuinfo::CpuInfoFileOps, cpuinfo::CpuInfoFileOps,
loadavg::LoadAvgFileOps, loadavg::LoadAvgFileOps,
meminfo::MemInfoFileOps, meminfo::MemInfoFileOps,
mounts::MountsSymOps,
pid::PidDirOps, pid::PidDirOps,
self_::SelfSymOps, self_::SelfSymOps,
sys::SysDirOps, sys::SysDirOps,
@ -41,6 +42,7 @@ mod cpuinfo;
mod filesystems; mod filesystems;
mod loadavg; mod loadavg;
mod meminfo; mod meminfo;
mod mounts;
mod pid; mod pid;
mod self_; mod self_;
mod stat; mod stat;
@ -159,6 +161,7 @@ impl RootDirOps {
("filesystems", FileSystemsFileOps::new_inode), ("filesystems", FileSystemsFileOps::new_inode),
("loadavg", LoadAvgFileOps::new_inode), ("loadavg", LoadAvgFileOps::new_inode),
("meminfo", MemInfoFileOps::new_inode), ("meminfo", MemInfoFileOps::new_inode),
("mounts", MountsSymOps::new_inode),
("self", SelfSymOps::new_inode), ("self", SelfSymOps::new_inode),
("stat", StatFileOps::new_inode), ("stat", StatFileOps::new_inode),
("sys", SysDirOps::new_inode), ("sys", SysDirOps::new_inode),

View File

@ -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<dyn Inode>) -> Arc<dyn Inode> {
// Reference:
// <https://elixir.bootlin.com/linux/v6.16.5/source/fs/proc/root.c#L291>
// <https://elixir.bootlin.com/linux/v6.16.5/source/fs/proc/generic.c#L466>
ProcSymBuilder::new(Self, mkmod!(a+rwx))
.parent(parent)
.build()
.unwrap()
}
}
impl SymOps for MountsSymOps {
fn read_link(&self) -> Result<SymbolicLink> {
Ok(SymbolicLink::Plain("self/mounts".to_string()))
}
}

View File

@ -12,8 +12,8 @@ use crate::{
cgroup::CgroupFileOps, cmdline::CmdlineFileOps, comm::CommFileOps, cgroup::CgroupFileOps, cmdline::CmdlineFileOps, comm::CommFileOps,
environ::EnvironFileOps, exe::ExeSymOps, fd::FdDirOps, gid_map::GidMapFileOps, environ::EnvironFileOps, exe::ExeSymOps, fd::FdDirOps, gid_map::GidMapFileOps,
maps::MapsFileOps, mem::MemFileOps, mountinfo::MountInfoFileOps, maps::MapsFileOps, mem::MemFileOps, mountinfo::MountInfoFileOps,
oom_score_adj::OomScoreAdjFileOps, stat::StatFileOps, status::StatusFileOps, mounts::MountsFileOps, oom_score_adj::OomScoreAdjFileOps, stat::StatFileOps,
uid_map::UidMapFileOps, status::StatusFileOps, uid_map::UidMapFileOps,
}, },
template::{ template::{
DirOps, ProcDir, ProcDirBuilder, lookup_child_from_table, DirOps, ProcDir, ProcDirBuilder, lookup_child_from_table,
@ -37,6 +37,7 @@ mod gid_map;
mod maps; mod maps;
mod mem; mod mem;
mod mountinfo; mod mountinfo;
mod mounts;
mod oom_score_adj; mod oom_score_adj;
mod stat; mod stat;
mod status; mod status;
@ -115,6 +116,7 @@ impl TidDirOps {
("status", StatusFileOps::new_inode), ("status", StatusFileOps::new_inode),
("uid_map", UidMapFileOps::new_inode), ("uid_map", UidMapFileOps::new_inode),
("maps", MapsFileOps::new_inode), ("maps", MapsFileOps::new_inode),
("mounts", MountsFileOps::new_inode),
]; ];
} }

View File

@ -1,15 +1,84 @@
// SPDX-License-Identifier: MPL-2.0 // SPDX-License-Identifier: MPL-2.0
use aster_util::printer::VmPrinter;
use super::TidDirOps; use super::TidDirOps;
use crate::{ use crate::{
fs::{ fs::{
path::{Mount, Path, PathResolver, PerMountFlags},
procfs::template::{FileOps, ProcFileBuilder}, procfs::template::{FileOps, ProcFileBuilder},
utils::{Inode, mkmod}, utils::{FsFlags, Inode, mkmod},
}, },
prelude::*, prelude::*,
process::posix_thread::AsPosixThread, 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: &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`). /// Represents the inode at `/proc/[pid]/task/[tid]/mountinfo` (and also `/proc/[pid]/mountinfo`).
pub struct MountInfoFileOps(TidDirOps); pub struct MountInfoFileOps(TidDirOps);
@ -21,6 +90,62 @@ impl MountInfoFileOps {
.build() .build()
.unwrap() .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<usize> {
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 { impl FileOps for MountInfoFileOps {
@ -30,7 +155,6 @@ impl FileOps for MountInfoFileOps {
let fs = posix_thread.read_fs(); let fs = posix_thread.read_fs();
let path_resolver = fs.resolver().read(); let path_resolver = fs.resolver().read();
self.read_mount_info(&path_resolver, offset, writer)
path_resolver.read_mount_info(offset, writer)
} }
} }

View File

@ -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<dyn Inode>) -> Arc<dyn Inode> {
// Reference: <https://elixir.bootlin.com/linux/v6.16.5/source/fs/proc/base.c#L3351>
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<usize> {
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: <https://elixir.bootlin.com/linux/v6.16.5/source/fs/proc_namespace.c#L130>.
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<usize> {
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)
}
}

View File

@ -150,6 +150,11 @@ pub trait FileSystem: Any + Sync + Send {
/// Gets the name of this FS type such as `"ext4"` or `"sysfs"`. /// Gets the name of this FS type such as `"ext4"` or `"sysfs"`.
fn name(&self) -> &'static str; 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. /// Syncs the file system.
fn sync(&self) -> Result<()>; fn sync(&self) -> Result<()>;

View File

@ -6,7 +6,7 @@ use super::SyscallReturn;
use crate::{ use crate::{
fs::{ fs::{
path::{AT_FDCWD, FsPath, MountPropType, Path, PerMountFlags}, path::{AT_FDCWD, FsPath, MountPropType, Path, PerMountFlags},
registry::FsProperties, registry::{FsProperties, FsType},
utils::{FileSystem, FsFlags, InodeType}, utils::{FileSystem, FsFlags, InodeType},
}, },
prelude::*, prelude::*,
@ -192,22 +192,44 @@ fn do_new_mount(
return_errno_with_message!(Errno::ENOTDIR, "mountpoint must be directory"); return_errno_with_message!(Errno::ENOTDIR, "mountpoint must be directory");
}; };
let fs_type = ctx let fs_type = {
.user_space() let fs_type_cstr = ctx
.read_cstring(fs_type_addr, MAX_FILENAME_LEN)?; .user_space()
if fs_type.is_empty() { .read_cstring(fs_type_addr, MAX_FILENAME_LEN)?;
return_errno_with_message!(Errno::EINVAL, "fs_type is empty"); if fs_type_cstr.is_empty() {
} return_errno_with_message!(Errno::EINVAL, "empty file system type");
let fs = get_fs(src_name_addr, flags, fs_type, data_addr, ctx)?; }
target_path.mount(fs, flags.into(), ctx)?;
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(()) Ok(())
} }
/// Gets the filesystem by fs_type and devname. /// Gets the filesystem by fs_type and dev_name.
fn get_fs( fn open_fs(
src_name_addr: Vaddr, dev_name: Option<&str>,
flags: MountFlags, flags: MountFlags,
fs_type: CString, fs_type: &dyn FsType,
data_addr: Vaddr, data_addr: Vaddr,
ctx: &Context, ctx: &Context,
) -> Result<Arc<dyn FileSystem>> { ) -> Result<Arc<dyn FileSystem>> {
@ -218,18 +240,10 @@ fn get_fs(
Some(user_space.read_cstring(data_addr, MAX_FILENAME_LEN)?) 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 disk = if fs_type.properties().contains(FsProperties::NEED_DISK) {
let devname = user_space.read_cstring(src_name_addr, MAX_FILENAME_LEN)?; let dev_name = dev_name
let path = devname.to_string_lossy(); .ok_or_else(|| Error::with_message(Errno::EINVAL, "the source is not specified"))?;
let fs_path = FsPath::from_fd_and_path(AT_FDCWD, path.as_ref())?; let fs_path = FsPath::from_fd_and_path(AT_FDCWD, dev_name)?;
let path = ctx let path = ctx
.thread_local .thread_local
.borrow_fs() .borrow_fs()

View File

@ -7,7 +7,6 @@ ProcCpuinfo.RequiredFieldsArePresent
ProcCpuinfo.DeniesWriteNonRoot ProcCpuinfo.DeniesWriteNonRoot
ProcFilesystems.OverflowID ProcFilesystems.OverflowID
ProcFilesystems.PresenceOfShmMaxMniAll ProcFilesystems.PresenceOfShmMaxMniAll
ProcMounts.IsSymlink
ProcPid.AccessDeny ProcPid.AccessDeny
ProcPidCwd.Subprocess ProcPidCwd.Subprocess
ProcPidRoot.Subprocess ProcPidRoot.Subprocess
@ -31,8 +30,6 @@ ProcSelfFdInfo.Flags
# TODO: Mappings created with only `PROT_WRITE` should be shown as `-w-`. # TODO: Mappings created with only `PROT_WRITE` should be shown as `-w-`.
ProcSelfMaps.Map2 ProcSelfMaps.Map2
ProcSelfMaps.MapUnmap ProcSelfMaps.MapUnmap
ProcSelfMounts.ContainsProcfsEntry
ProcSelfMounts.RequiredFieldsArePresent
ProcSelfRoot.IsRoot ProcSelfRoot.IsRoot
ProcSelfStat.PopulateWriteRSS ProcSelfStat.PopulateWriteRSS
ProcSysKernelHostname.Exists ProcSysKernelHostname.Exists