Add `proc/self/mounts` and `proc/mounts`
This commit is contained in:
parent
ab4e0d9012
commit
b0407dd517
|
|
@ -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)?;
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
|
||||
|
|
|
|||
|
|
@ -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(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<dyn Inode>,
|
||||
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();
|
||||
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@ impl Path {
|
|||
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 }
|
||||
}
|
||||
|
||||
|
|
@ -87,6 +87,11 @@ impl Path {
|
|||
&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.
|
||||
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<dyn FileSystem>,
|
||||
flags: PerMountFlags,
|
||||
source: Option<String>,
|
||||
ctx: &Context,
|
||||
) -> Result<Arc<Mount>> {
|
||||
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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -160,6 +160,15 @@ pub struct Mount {
|
|||
mountpoint: RwLock<Option<Arc<Dentry>>>,
|
||||
/// The associated FS.
|
||||
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.
|
||||
parent: RwLock<Option<Weak<Mount>>>,
|
||||
/// Child mount nodes which are mounted on one dentry of self.
|
||||
|
|
@ -186,7 +195,8 @@ impl Mount {
|
|||
fs: Arc<dyn FileSystem>,
|
||||
mnt_ns: Weak<MountNamespace>,
|
||||
) -> 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.
|
||||
|
|
@ -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<dyn FileSystem>) -> Arc<Self> {
|
||||
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<Weak<Mount>>,
|
||||
mnt_ns: Weak<MountNamespace>,
|
||||
source: Option<String>,
|
||||
) -> Arc<Self> {
|
||||
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<Self>,
|
||||
fs: Arc<dyn FileSystem>,
|
||||
flags: PerMountFlags,
|
||||
mountpoint: &Arc<Dentry>,
|
||||
source: Option<String>,
|
||||
) -> Result<Arc<Self>> {
|
||||
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<Dentry> {
|
||||
pub(in crate::fs) fn root_dentry(&self) -> &Arc<Dentry> {
|
||||
&self.root_dentry
|
||||
}
|
||||
|
||||
/// 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()
|
||||
}
|
||||
|
||||
|
|
@ -481,7 +508,7 @@ impl Mount {
|
|||
}
|
||||
|
||||
/// 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()
|
||||
}
|
||||
|
||||
|
|
@ -491,11 +518,11 @@ impl Mount {
|
|||
}
|
||||
|
||||
/// Gets the associated FS.
|
||||
pub(super) fn fs(&self) -> &Arc<dyn FileSystem> {
|
||||
pub(in crate::fs) fn fs(&self) -> &Arc<dyn FileSystem> {
|
||||
&self.fs
|
||||
}
|
||||
|
||||
pub(super) fn flags(&self) -> PerMountFlags {
|
||||
pub(in crate::fs) fn flags(&self) -> PerMountFlags {
|
||||
self.flags.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<usize> {
|
||||
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<Arc<Mount>> {
|
||||
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()
|
||||
let is_root_mount = Arc::ptr_eq(&mount, &self.root.mount);
|
||||
|
||||
// Add the root mount only if `self` is at the mount root.
|
||||
if !is_root_mount || is_root_mount_root {
|
||||
visible.push(mount.clone());
|
||||
}
|
||||
} 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.
|
||||
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)?;
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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()))
|
||||
}
|
||||
}
|
||||
|
|
@ -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),
|
||||
];
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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: &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<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 {
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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<()>;
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
let fs_type = {
|
||||
let fs_type_cstr = 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");
|
||||
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(())
|
||||
}
|
||||
|
||||
/// 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<Arc<dyn FileSystem>> {
|
||||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue