// SPDX-License-Identifier: MPL-2.0 use core::marker::PhantomData; use aster_util::slot_vec::SlotVec; use ostd::sync::RwMutexUpgradeableGuard; use crate::{ fs::{ path::{MountNamespace, Path}, procfs::{ DirOps, ProcDir, ProcDirBuilder, ProcSymBuilder, SymOps, pid::TidDirOps, template::ProcSym, }, pseudofs::NsCommonOps, utils::{DirEntryVecExt, Inode, SymbolicLink, mkmod}, }, net::uts_ns::UtsNamespace, prelude::*, process::{NsProxy, UserNamespace, posix_thread::AsPosixThread}, }; /// Represents the inode at `/proc/[pid]/task/[tid]/ns` (and also `/proc/[pid]/ns`). pub(super) struct NsDirOps { dir: TidDirOps, } impl NsDirOps { /// Creates a new directory inode for the `ns` directory. pub fn new_inode(dir: &TidDirOps, parent: Weak) -> Arc { ProcDirBuilder::new( Self { dir: dir.clone() }, // Reference: mkmod!(u + r, a + x), ) .parent(parent) .build() .unwrap() } } impl NsDirOps { /// The set of namespace entries that depend on the thread's [`NsProxy`]. #[expect(clippy::type_complexity)] const NS_PROXY_ENTRIES: &[(&str, fn(&NsProxy, Weak) -> Arc)] = &[ ("uts", |proxy, parent| { NsSymOps::new_inode(proxy.uts_ns(), parent) }), ("mnt", |proxy, parent| { NsSymOps::new_inode(proxy.mnt_ns(), parent) }), ]; /// Looks up a namespace symlink backed by the thread's [`NsProxy`]. fn lookup_ns_proxy_child( &self, name: &str, parent: Weak, ) -> Option>> { let constructor = Self::NS_PROXY_ENTRIES .iter() .find(|(entry_name, _)| *entry_name == name) .map(|(_, ctor)| ctor)?; let thread = self.dir.thread(); let ns_proxy = thread.as_posix_thread().unwrap().ns_proxy().lock(); let Some(ns_proxy) = ns_proxy.as_ref() else { return Some(Err(Error::with_message( Errno::ENOENT, "the thread's namespace proxy no longer exists", ))); }; Some(Ok(constructor(ns_proxy, parent))) } } impl DirOps for NsDirOps { fn lookup_child(&self, dir: &ProcDir, name: &str) -> Result> { let inode = if let Some(result) = self.lookup_ns_proxy_child(name, dir.this_weak().clone()) { result? } else if name == "user" { let user_ns = self.dir.process_ref.user_ns().lock(); NsSymOps::new_inode(&*user_ns, dir.this_weak().clone()) } else { return_errno_with_message!(Errno::ENOENT, "the file does not exist"); }; let mut cached_children = dir.cached_children().write(); cached_children.remove_entry_by_name(name); cached_children.put((name.to_string(), inode.clone())); Ok(inode) } fn populate_children<'a>( &self, dir: &'a ProcDir, ) -> RwMutexUpgradeableGuard<'a, SlotVec<(String, Arc)>> { let mut cached_children = dir.cached_children().write(); let thread = self.dir.thread(); let ns_proxy = thread.as_posix_thread().unwrap().ns_proxy().lock(); // Refresh NsProxy-backed entries: remove stale ones and re-add if the proxy is alive. for &(name, constructor) in Self::NS_PROXY_ENTRIES { cached_children.remove_entry_by_name(name); if let Some(ns_proxy) = ns_proxy.as_ref() { let inode = constructor(ns_proxy, dir.this_weak().clone()); cached_children.put((name.to_string(), inode)); } } // The user namespace never changes, so only insert if absent. cached_children.put_entry_if_not_found("user", || { let user_ns = self.dir.process_ref.user_ns().lock(); NsSymOps::new_inode(&*user_ns, dir.this_weak().clone()) }); cached_children.downgrade() } fn validate_child(&self, child: &dyn Inode) -> bool { // The user namespace of a thread/process never changes, // so a user-ns symlink is always valid. if child.downcast_ref::>().is_some() { return true; } // Checks whether `child` still matches the corresponding namespace // in the thread's current `NsProxy`. let thread = self.dir.thread(); let ns_proxy = thread.as_posix_thread().unwrap().ns_proxy().lock(); let Some(ns_proxy) = ns_proxy.as_ref() else { return false; }; if let Some(sym) = child.downcast_ref::>() { return &sym.inner().ns_path == ns_proxy.uts_ns().path(); } if let Some(sym) = child.downcast_ref::>() { return &sym.inner().ns_path == ns_proxy.mnt_ns().path(); } // TODO: Support additional namespace types. false } } type NsSymlink = ProcSym>; /// Represents the inode at `/proc/[pid]/task/[tid]/ns/` (and also `/proc/[pid]/ns/`). pub struct NsSymOps { ns_path: Path, phantom: PhantomData, } impl NsSymOps { /// Creates a new symlink inode pointing to the given namespace. fn new_inode(ns: &Arc, parent: Weak) -> Arc { ProcSymBuilder::new( Self { ns_path: ns.path().clone(), phantom: PhantomData, }, mkmod!(a + rwx), ) .parent(parent) .build() .unwrap() } } impl SymOps for NsSymOps { fn read_link(&self) -> Result { Ok(SymbolicLink::Path(self.ns_path.clone())) } }