181 lines
5.9 KiB
Rust
181 lines
5.9 KiB
Rust
|
|
// 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<dyn Inode>) -> Arc<dyn Inode> {
|
||
|
|
ProcDirBuilder::new(
|
||
|
|
Self { dir: dir.clone() },
|
||
|
|
// Reference: <https://elixir.bootlin.com/linux/v6.18/source/fs/proc/base.c#L3321>
|
||
|
|
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<dyn Inode>) -> Arc<dyn Inode>)] = &[
|
||
|
|
("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<dyn Inode>,
|
||
|
|
) -> Option<Result<Arc<dyn Inode>>> {
|
||
|
|
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<Self>, name: &str) -> Result<Arc<dyn Inode>> {
|
||
|
|
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<Self>,
|
||
|
|
) -> RwMutexUpgradeableGuard<'a, SlotVec<(String, Arc<dyn Inode>)>> {
|
||
|
|
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::<NsSymlink<UserNamespace>>().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::<NsSymlink<UtsNamespace>>() {
|
||
|
|
return &sym.inner().ns_path == ns_proxy.uts_ns().path();
|
||
|
|
}
|
||
|
|
|
||
|
|
if let Some(sym) = child.downcast_ref::<NsSymlink<MountNamespace>>() {
|
||
|
|
return &sym.inner().ns_path == ns_proxy.mnt_ns().path();
|
||
|
|
}
|
||
|
|
|
||
|
|
// TODO: Support additional namespace types.
|
||
|
|
false
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
type NsSymlink<T> = ProcSym<NsSymOps<T>>;
|
||
|
|
|
||
|
|
/// Represents the inode at `/proc/[pid]/task/[tid]/ns/<type>` (and also `/proc/[pid]/ns/<type>`).
|
||
|
|
pub struct NsSymOps<T: NsCommonOps> {
|
||
|
|
ns_path: Path,
|
||
|
|
phantom: PhantomData<T>,
|
||
|
|
}
|
||
|
|
|
||
|
|
impl<T: NsCommonOps> NsSymOps<T> {
|
||
|
|
/// Creates a new symlink inode pointing to the given namespace.
|
||
|
|
fn new_inode(ns: &Arc<T>, parent: Weak<dyn Inode>) -> Arc<dyn Inode> {
|
||
|
|
ProcSymBuilder::new(
|
||
|
|
Self {
|
||
|
|
ns_path: ns.path().clone(),
|
||
|
|
phantom: PhantomData,
|
||
|
|
},
|
||
|
|
mkmod!(a + rwx),
|
||
|
|
)
|
||
|
|
.parent(parent)
|
||
|
|
.build()
|
||
|
|
.unwrap()
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
impl<T: NsCommonOps> SymOps for NsSymOps<T> {
|
||
|
|
fn read_link(&self) -> Result<SymbolicLink> {
|
||
|
|
Ok(SymbolicLink::Path(self.ns_path.clone()))
|
||
|
|
}
|
||
|
|
}
|