Support nsfs

This commit is contained in:
Jianfeng Jiang 2026-02-11 11:18:48 +00:00
parent 78f098fe88
commit 9c023db7b5
7 changed files with 678 additions and 12 deletions

View File

@ -5,6 +5,7 @@ use spin::Once;
use crate::{
fs::{
path::{Mount, Path, PathResolver},
pseudofs::{NsCommonOps, NsFs, NsType},
ramfs::RamFs,
},
prelude::*,
@ -22,6 +23,8 @@ pub struct MountNamespace {
root: Arc<Mount>,
/// The user namespace that owns this mount namespace.
owner: Arc<UserNamespace>,
/// The path in nsfs.
path: Path,
}
impl MountNamespace {
@ -36,7 +39,8 @@ impl MountNamespace {
Arc::new_cyclic(|weak_self| {
let root = Mount::new_root(rootfs, weak_self.clone());
MountNamespace { root, owner }
let path = NsFs::new_path(weak_self.clone());
MountNamespace { root, owner, path }
})
})
}
@ -73,10 +77,11 @@ impl MountNamespace {
let new_mnt_ns = Arc::new_cyclic(|weak_self| {
let new_root =
root_mount.clone_mount_tree(root_mount.root_dentry(), Some(weak_self), true);
let path = NsFs::new_path(weak_self.clone());
MountNamespace {
root: new_root,
owner,
path,
}
});
@ -134,3 +139,26 @@ impl Drop for MountNamespace {
}
}
}
impl NsCommonOps for MountNamespace {
const TYPE: NsType = NsType::Mnt;
fn get_owner_user_ns(&self) -> Result<&Arc<UserNamespace>> {
Ok(&self.owner)
}
fn get_parent(&self) -> Result<Arc<Self>> {
return_errno_with_message!(
Errno::EINVAL,
"mnt namespace does not have parent namespace"
);
}
fn path(&self) -> &Path {
&self.path
}
fn as_any(&self) -> &dyn Any {
self
}
}

View File

@ -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,
mounts::MountsFileOps, oom_score_adj::OomScoreAdjFileOps, stat::StatFileOps,
status::StatusFileOps, uid_map::UidMapFileOps,
mounts::MountsFileOps, ns::NsDirOps, oom_score_adj::OomScoreAdjFileOps,
stat::StatFileOps, status::StatusFileOps, uid_map::UidMapFileOps,
},
template::{
DirOps, ProcDir, ProcDirBuilder, lookup_child_from_table,
@ -38,6 +38,7 @@ mod maps;
mod mem;
mod mountinfo;
mod mounts;
mod ns;
mod oom_score_adj;
mod stat;
mod status;
@ -111,6 +112,7 @@ impl TidDirOps {
("gid_map", GidMapFileOps::new_inode),
("mem", MemFileOps::new_inode),
("mountinfo", MountInfoFileOps::new_inode),
("ns", NsDirOps::new_inode),
("oom_score_adj", OomScoreAdjFileOps::new_inode),
("stat", StatFileOps::new_inode),
("status", StatusFileOps::new_inode),

View File

@ -0,0 +1,180 @@
// 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()))
}
}

View File

@ -6,6 +6,7 @@ use core::{
};
pub use anon_inode_fs::AnonInodeFs;
pub use nsfs::{NsCommonOps, NsFile, NsFs, NsType};
pub use pidfdfs::PidfdFs;
pub(super) use pipefs::PipeFs;
use pipefs::PipeFsType;
@ -28,6 +29,7 @@ use crate::{
};
mod anon_inode_fs;
mod nsfs;
mod pidfdfs;
mod pipefs;
mod sockfs;
@ -122,6 +124,7 @@ pub enum PseudoInodeType {
Socket,
AnonInode,
Pidfd,
Ns,
}
impl From<PseudoInodeType> for InodeType {
@ -132,6 +135,7 @@ impl From<PseudoInodeType> for InodeType {
PseudoInodeType::Socket => InodeType::Socket,
PseudoInodeType::AnonInode => InodeType::Unknown,
PseudoInodeType::Pidfd => InodeType::Unknown,
PseudoInodeType::Ns => InodeType::File,
}
}
}

View File

@ -0,0 +1,365 @@
// SPDX-License-Identifier: MPL-2.0
use alloc::format;
use core::time::Duration;
use inherit_methods_macro::inherit_methods;
use ostd::task::Task;
use spin::Once;
use crate::{
events::IoEvents,
fs::{
file_table::{FdFlags, FileDesc},
inode_handle::{FileIo, InodeHandle},
path::{Mount, Path},
pseudofs::{PseudoFs, PseudoInode, PseudoInodeType},
utils::{
AccessMode, Extension, FileSystem, Inode, InodeIo, InodeMode, InodeType, Metadata,
StatusFlags, mkmod,
},
},
prelude::*,
process::{
CloneFlags, Gid, Uid, UserNamespace,
signal::{PollHandle, Pollable},
},
util::ioctl::{RawIoctl, dispatch_ioctl},
};
/// A pseudo filesystem for namespace files.
pub struct NsFs {
_private: (),
}
impl NsFs {
/// Returns the singleton instance of the ns filesystem.
pub fn singleton() -> &'static Arc<PseudoFs> {
static NSFS: Once<Arc<PseudoFs>> = Once::new();
PseudoFs::singleton(&NSFS, "nsfs", NSFS_MAGIC)
}
/// Creates a pseudo [`Path`] for a namespace file.
pub fn new_path<T: NsCommonOps>(ns: Weak<T>) -> Path {
let ns_inode = {
let ino = Self::singleton().alloc_id();
let fs = Arc::downgrade(Self::singleton());
Arc::new(NsInode::new(ino, Uid::new_root(), Gid::new_root(), ns, fs))
};
Path::new_pseudo(Self::mount_node().clone(), ns_inode, |inode| {
let inode = inode.downcast_ref::<NsInode<T>>().unwrap();
inode.name().to_string()
})
}
/// Returns the pseudo mount node of the ns filesystem.
pub fn mount_node() -> &'static Arc<Mount> {
static NSFS_MOUNT: Once<Arc<Mount>> = Once::new();
NSFS_MOUNT.call_once(|| Mount::new_pseudo(Self::singleton().clone()))
}
}
/// An inode representing a namespace entry in [`NsFs`].
struct NsInode<T: NsCommonOps> {
common: PseudoInode,
ns: Weak<T>,
name: String,
}
impl<T: NsCommonOps> NsInode<T> {
fn new(ino: u64, uid: Uid, gid: Gid, ns: Weak<T>, fs: Weak<PseudoFs>) -> Self {
let mode = mkmod!(a + r);
let common = PseudoInode::new(ino, PseudoInodeType::Ns, mode, uid, gid, fs);
let name = format!("{}:[{}]", T::NAME, ino);
Self { common, ns, name }
}
fn name(&self) -> &str {
&self.name
}
}
#[inherit_methods(from = "self.common")]
impl<T: NsCommonOps> Inode for NsInode<T> {
fn size(&self) -> usize;
fn resize(&self, _new_size: usize) -> Result<()>;
fn metadata(&self) -> Metadata;
fn extension(&self) -> &Extension;
fn ino(&self) -> u64;
fn type_(&self) -> InodeType;
fn mode(&self) -> Result<InodeMode>;
fn set_mode(&self, mode: InodeMode) -> Result<()>;
fn owner(&self) -> Result<Uid>;
fn set_owner(&self, uid: Uid) -> Result<()>;
fn group(&self) -> Result<Gid>;
fn set_group(&self, gid: Gid) -> Result<()>;
fn atime(&self) -> Duration;
fn set_atime(&self, time: Duration);
fn mtime(&self) -> Duration;
fn set_mtime(&self, time: Duration);
fn ctime(&self) -> Duration;
fn set_ctime(&self, time: Duration);
fn fs(&self) -> Arc<dyn FileSystem>;
fn open(
&self,
access_mode: AccessMode,
_status_flags: StatusFlags,
) -> Option<Result<Box<dyn FileIo>>> {
// FIXME: This may not be the most appropriate place to check the access mode,
// but the check must not be bypassed even if the current process has the
// CAP_DAC_OVERRIDE capability. It is hard to find a better place for it,
// and an extra check here does no harm.
if access_mode.is_writable() {
return Some(Err(Error::with_message(
Errno::EPERM,
"ns files cannot be opened as writable",
)));
}
let ns = self
.ns
.upgrade()
.ok_or_else(|| Error::with_message(Errno::EPERM, "the namespace no longer exists"));
Some(ns.map(|ns| Box::new(NsFile { ns }) as Box<dyn FileIo>))
}
}
#[inherit_methods(from = "self.common")]
impl<T: NsCommonOps> InodeIo for NsInode<T> {
fn read_at(
&self,
_offset: usize,
_writer: &mut VmWriter,
_status: StatusFlags,
) -> Result<usize>;
fn write_at(
&self,
_offset: usize,
_reader: &mut VmReader,
_status: StatusFlags,
) -> Result<usize>;
}
/// A file handle referencing a live namespace.
pub struct NsFile<T: NsCommonOps> {
ns: Arc<T>,
}
impl<T: NsCommonOps> NsFile<T> {
/// Returns a reference to the underlying namespace.
pub fn ns(&self) -> &Arc<T> {
&self.ns
}
}
impl<T: NsCommonOps> FileIo for NsFile<T> {
fn check_seekable(&self) -> Result<()> {
return_errno_with_message!(Errno::ESPIPE, "ns files is not seekable");
}
fn is_offset_aware(&self) -> bool {
false
}
fn ioctl(&self, raw_ioctl: RawIoctl) -> Result<i32> {
use ioctl_defs::*;
dispatch_ioctl!(match raw_ioctl {
_cmd @ GetUserNs => {
let user_ns = self.ns.get_owner_user_ns()?;
let current = current!();
let current_user_ns = current.user_ns().lock();
if !current_user_ns.is_same_or_ancestor_of(user_ns) {
return_errno_with_message!(
Errno::EPERM,
"the owner user namespace is not an ancestor of the current namespace"
);
}
open_ns_as_file(user_ns.as_ref())
}
_cmd @ GetParent => {
let parent = self.ns.get_parent()?;
open_ns_as_file(parent.as_ref())
}
_cmd @ GetType => {
let clone_flags = CloneFlags::from(T::TYPE);
Ok(clone_flags.bits().cast_signed())
}
cmd @ GetOwnerUid => {
let user_ns = self
.ns
.as_any()
.downcast_ref::<UserNamespace>()
.ok_or_else(|| {
Error::with_message(
Errno::EINVAL,
"the ns file does not correspond to a user namespace",
)
})?;
let uid = user_ns.get_owner_uid()?;
cmd.write(&uid.into())?;
Ok(0)
}
// TODO: Support additional iotcl commands
_ => return_errno_with_message!(Errno::ENOTTY, "unsupported ioctl command"),
})
}
fn as_any(&self) -> &dyn Any {
self
}
}
impl<T: NsCommonOps> Pollable for NsFile<T> {
fn poll(&self, mask: IoEvents, _poller: Option<&mut PollHandle>) -> IoEvents {
(IoEvents::IN | IoEvents::OUT | IoEvents::RDNORM) & mask
}
}
impl<T: NsCommonOps> InodeIo for NsFile<T> {
fn read_at(
&self,
_offset: usize,
_writer: &mut VmWriter,
_status_flags: StatusFlags,
) -> Result<usize> {
return_errno_with_message!(Errno::EINVAL, "ns files do not support read_at");
}
fn write_at(
&self,
_offset: usize,
_reader: &mut VmReader,
_status_flags: StatusFlags,
) -> Result<usize> {
return_errno_with_message!(Errno::EINVAL, "ns files do not support write_at");
}
}
/// Opens a namespace as a file and returns the file descriptor.
fn open_ns_as_file<T: NsCommonOps>(ns: &T) -> Result<FileDesc> {
let path = ns.path();
let inode_handle = InodeHandle::new(path.clone(), AccessMode::O_RDONLY, StatusFlags::empty())?;
let current_task = Task::current().unwrap();
let thread_local = current_task.as_thread_local().unwrap();
let mut file_table_ref = thread_local.borrow_file_table_mut();
let mut file_table = file_table_ref.unwrap().write();
let fd = file_table.insert(Arc::new(inode_handle), FdFlags::CLOEXEC);
Ok(fd)
}
/// Common operations shared by all namespace types.
///
/// Implementors represent a specific kind of namespace (e.g., UTS, mount, user)
/// and must provide the associated metadata and traversal methods required by
/// [`NsFs`] and [`NsFile`].
pub trait NsCommonOps: Any + Send + Sync + 'static {
/// The human-readable name of this namespace kind (derived from [`Self::TYPE`]).
const NAME: &str = Self::TYPE.as_str();
/// The [`NsType`] discriminant for this namespace kind.
const TYPE: NsType;
/// Returns the owner user namespace.
fn get_owner_user_ns(&self) -> Result<&Arc<UserNamespace>>;
/// Returns the parent namespace, if one exists.
fn get_parent(&self) -> Result<Arc<Self>>;
/// Returns the pseudo filesystem [`Path`] associated with this namespace.
fn path(&self) -> &Path;
fn as_any(&self) -> &dyn Any;
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum NsType {
Uts,
User,
Mnt,
#[expect(unused)]
Pid,
#[expect(unused)]
Time,
#[expect(unused)]
Cgroup,
#[expect(unused)]
Ipc,
#[expect(unused)]
Net,
}
impl NsType {
const fn as_str(&self) -> &'static str {
match self {
NsType::Uts => "uts",
NsType::User => "user",
NsType::Mnt => "mnt",
NsType::Pid => "pid",
NsType::Time => "time",
NsType::Cgroup => "cgroup",
NsType::Ipc => "ipc",
NsType::Net => "net",
}
}
}
impl From<NsType> for CloneFlags {
fn from(value: NsType) -> Self {
match value {
NsType::Uts => CloneFlags::CLONE_NEWUTS,
NsType::User => CloneFlags::CLONE_NEWUSER,
NsType::Mnt => CloneFlags::CLONE_NEWNS,
NsType::Pid => CloneFlags::CLONE_NEWPID,
NsType::Time => CloneFlags::CLONE_NEWTIME,
NsType::Cgroup => CloneFlags::CLONE_NEWCGROUP,
NsType::Ipc => CloneFlags::CLONE_NEWIPC,
NsType::Net => CloneFlags::CLONE_NEWNET,
}
}
}
// Reference: <https://elixir.bootlin.com/linux/v6.16.5/source/include/uapi/linux/magic.h#L95>
const NSFS_MAGIC: u64 = 0x6e736673;
mod ioctl_defs {
use crate::util::ioctl::{InData, NoData, OutData, ioc};
// Legacy encoding ioctl commands
/// Returns a file descriptor of the owner user namespace.
pub type GetUserNs = ioc!(NS_GET_USERNS, 0xb701, NoData);
/// Returns a file descriptor of the parent namespace.
pub type GetParent = ioc!(NS_GET_PARENT, 0xb702, NoData);
/// Gets the type of the namespace (e.g., user, pid, mnt, etc.).
pub type GetType = ioc!(NS_GET_NSTYPE, 0xb703, NoData);
/// Gets the user ID of the namespace owner.
///
/// Only user namespace supports this operation.
pub type GetOwnerUid = ioc!(NS_GET_OWNER_ID, 0xb704, OutData<u32>);
// Modern encoding ioctl commands
#[expect(unused)]
/// Gets the ID of the mount namespace.
pub type GetMntNsId = ioc!(NS_GET_MNTNS_ID, 0xb7, 0x5, OutData<u64>);
/// Translates thread ID from the target PID namespace into the caller's PID namespace.
#[expect(unused)]
pub type GetTidFromPidNs = ioc!(NS_GET_PID_FROM_PIDNS, 0xb7, 0x6, InData<i32>);
/// Translates process ID from the target PID namespace into the caller's PID namespace.
#[expect(unused)]
pub type GetPidFromPidNs = ioc!(NS_GET_TGID_FROM_PIDNS, 0xb7, 0x7, InData<i32>);
/// Translates thread ID from the caller's PID namespace into the target PID namespace.
#[expect(unused)]
pub type GetTidInPidNs = ioc!(NS_GET_PID_IN_PIDNS, 0xb7, 0x8, InData<i32>);
/// Translates process ID from the caller's PID namespace into the target PID namespace.
#[expect(unused)]
pub type GetPidInPidNs = ioc!(NS_GET_TGID_IN_PIDNS, 0xb7, 0x9, InData<i32>);
}

View File

@ -4,6 +4,10 @@ use ostd::{const_assert, sync::RwMutexReadGuard};
use spin::Once;
use crate::{
fs::{
path::Path,
pseudofs::{NsCommonOps, NsFs, NsType},
},
prelude::*,
process::{UserNamespace, credentials::capabilities::CapSet, posix_thread::PosixThread},
util::padded,
@ -13,6 +17,7 @@ use crate::{
pub struct UtsNamespace {
uts_name: RwMutex<UtsName>,
owner: Arc<UserNamespace>,
path: Path,
}
impl UtsNamespace {
@ -33,11 +38,18 @@ impl UtsNamespace {
};
let owner = UserNamespace::get_init_singleton().clone();
Self::new(uts_name, owner)
})
}
Arc::new(Self {
fn new(uts_name: UtsName, owner: Arc<UserNamespace>) -> Arc<Self> {
Arc::new_cyclic(|weak_self| {
let path = NsFs::new_path(weak_self.clone());
Self {
uts_name: RwMutex::new(uts_name),
owner,
})
path,
}
})
}
@ -48,10 +60,7 @@ impl UtsNamespace {
posix_thread: &PosixThread,
) -> Result<Arc<Self>> {
owner.check_cap(CapSet::SYS_ADMIN, posix_thread)?;
Ok(Arc::new(Self {
uts_name: RwMutex::new(*self.uts_name.read()),
owner,
}))
Ok(Self::new(*self.uts_name.read(), owner))
}
/// Returns the owner user namespace of the namespace.
@ -195,3 +204,26 @@ impl UtsName {
}
};
}
impl NsCommonOps for UtsNamespace {
const TYPE: NsType = NsType::Uts;
fn get_owner_user_ns(&self) -> Result<&Arc<UserNamespace>> {
Ok(&self.owner)
}
fn get_parent(&self) -> Result<Arc<Self>> {
return_errno_with_message!(
Errno::EINVAL,
"UTS namespace does not have parent namespace"
);
}
fn path(&self) -> &Path {
&self.path
}
fn as_any(&self) -> &dyn Any {
self
}
}

View File

@ -3,13 +3,18 @@
use spin::Once;
use crate::{
fs::{
path::Path,
pseudofs::{NsCommonOps, NsFs, NsType},
},
prelude::*,
process::{credentials::capabilities::CapSet, posix_thread::PosixThread},
process::{Uid, credentials::capabilities::CapSet, posix_thread::PosixThread},
};
/// The user namespace.
pub struct UserNamespace {
_private: (),
path: Path,
}
impl UserNamespace {
@ -17,7 +22,14 @@ impl UserNamespace {
pub fn get_init_singleton() -> &'static Arc<UserNamespace> {
static INIT: Once<Arc<UserNamespace>> = Once::new();
INIT.call_once(|| Arc::new(UserNamespace { _private: () }))
INIT.call_once(Self::new)
}
fn new() -> Arc<Self> {
Arc::new_cyclic(|weak_self| {
let path = NsFs::new_path(weak_self.clone());
Self { _private: (), path }
})
}
/// Checks whether the thread has the required capability in this user namespace.
@ -37,4 +49,47 @@ impl UserNamespace {
"the thread does not have the required capability"
)
}
/// Returns the owner UID of the user namespace.
pub fn get_owner_uid(&self) -> Result<Uid> {
// FIXME: The owner of the user namespace is not yet tracked.
// Return the correct user ID once ownership tracking is implemented.
Ok(Uid::new_root())
}
/// Returns whether this namespace is the same as, or an ancestor of, the other namespace.
pub fn is_same_or_ancestor_of(self: &Arc<Self>, other: &Arc<Self>) -> bool {
// FIXME: Creating new user namespaces is not yet supported,
// so we simply check pointer equality.
// Once user namespace creation is implemented,
// this should walk up the ancestor chain to verify
// whether `self` is an ancestor of `other`.
Arc::ptr_eq(self, other)
}
}
impl NsCommonOps for UserNamespace {
const TYPE: NsType = NsType::User;
fn get_owner_user_ns(&self) -> Result<&Arc<UserNamespace>> {
return_errno_with_message!(
Errno::EPERM,
"a user namespace does not have an owner user namespace"
);
}
fn get_parent(&self) -> Result<Arc<Self>> {
return_errno_with_message!(
Errno::EPERM,
"getting the parent of a user namespace is not supported"
);
}
fn path(&self) -> &Path {
&self.path
}
fn as_any(&self) -> &dyn Any {
self
}
}