Add global lock for mount tree topology
Add a `MountTopology` abstraction that protects mount tree topology via a global `RwMutex`. Write side is acquired in all `Path` mutation methods and in `Drop`, `pivot_root`. Read side is acquired in `new_clone`, `switch_to_mnt_ns` and `collect_visible_mounts` for consistent snapshots. Require topology operations to take a `MountTopology` reference so that callers must hold the corresponding read or write guard. Closes: #3392
This commit is contained in:
parent
cf9c093d01
commit
b577872004
|
|
@ -6,8 +6,8 @@ use core::time::Duration;
|
|||
|
||||
pub(in crate::fs) use dentry::Dentry;
|
||||
use inherit_methods_macro::inherit_methods;
|
||||
use mount::MountNsFileCopying;
|
||||
pub use mount::{Mount, MountPropType, PerMountFlags};
|
||||
use mount::{MountNsFileCopying, MountTopology};
|
||||
pub use mount_namespace::MountNamespace;
|
||||
pub use resolver::{
|
||||
AT_FDCWD, AbsPathResult, EmptyPathStr, FsPath, LookupResult, PathResolver, SplitPath,
|
||||
|
|
@ -194,8 +194,12 @@ impl Path {
|
|||
}
|
||||
|
||||
/// Finds the corresponding `Path` in the given mount namespace.
|
||||
fn find_corresponding_mount(&self, mnt_ns: &Arc<MountNamespace>) -> Option<Self> {
|
||||
let corresponding_mount = self.mount.find_corresponding_mount(mnt_ns)?;
|
||||
fn find_corresponding_mount(
|
||||
&self,
|
||||
mnt_ns: &Arc<MountNamespace>,
|
||||
topology: &MountTopology,
|
||||
) -> Option<Self> {
|
||||
let corresponding_mount = self.mount.find_corresponding_mount(mnt_ns, topology)?;
|
||||
let corresponding_path = Self::new(corresponding_mount, self.dentry.clone());
|
||||
|
||||
Some(corresponding_path)
|
||||
|
|
@ -207,7 +211,7 @@ impl Path {
|
|||
/// of the `root` path. The check traverses upwards from the current path,
|
||||
/// crossing mount point boundaries as necessary, until it either finds
|
||||
/// the `root` path or reaches the global root.
|
||||
fn is_reachable_from(&self, root: &Path) -> bool {
|
||||
fn is_reachable_from(&self, root: &Path, _topology: &MountTopology) -> bool {
|
||||
let mut owned;
|
||||
let mut current = self;
|
||||
|
||||
|
|
@ -363,7 +367,10 @@ 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, source)?;
|
||||
let mut topology_guard = MountTopology::write_lock();
|
||||
let child_mount =
|
||||
self.mount
|
||||
.do_mount(fs, flags, &self.dentry, source, &mut topology_guard)?;
|
||||
|
||||
Ok(child_mount)
|
||||
}
|
||||
|
|
@ -383,7 +390,7 @@ impl Path {
|
|||
return_errno_with_message!(Errno::EINVAL, "the path is not a mount root");
|
||||
}
|
||||
|
||||
let Some(mountpoint) = self.mount.mountpoint() else {
|
||||
let Some(_mountpoint) = self.mount.mountpoint() else {
|
||||
return_errno_with_message!(Errno::EINVAL, "the root mount cannot be unmounted");
|
||||
};
|
||||
|
||||
|
|
@ -395,8 +402,12 @@ impl Path {
|
|||
|
||||
self.mount.sync()?;
|
||||
|
||||
let mut topology_guard = MountTopology::write_lock();
|
||||
let Some(mountpoint) = self.mount.mountpoint() else {
|
||||
return_errno_with_message!(Errno::EINVAL, "the mount has been detached");
|
||||
};
|
||||
let parent_mount = self.mount.parent().unwrap().upgrade().unwrap();
|
||||
let child_mount = parent_mount.do_unmount(&mountpoint)?;
|
||||
let child_mount = parent_mount.do_unmount(&mountpoint, &mut topology_guard)?;
|
||||
|
||||
Ok(child_mount)
|
||||
}
|
||||
|
|
@ -428,7 +439,9 @@ impl Path {
|
|||
return_errno_with_message!(Errno::EINVAL, "the path is not in this mount namespace");
|
||||
}
|
||||
|
||||
self.mount.remount(mount_flags, fs_flags, data, ctx)
|
||||
let mut topology_guard = MountTopology::write_lock();
|
||||
self.mount
|
||||
.remount(mount_flags, fs_flags, data, ctx, &mut topology_guard)
|
||||
}
|
||||
|
||||
/// Creates a bind mount from the current path to the destination path.
|
||||
|
|
@ -476,14 +489,16 @@ impl Path {
|
|||
);
|
||||
}
|
||||
|
||||
let mut topology_guard = MountTopology::write_lock();
|
||||
let current_mnt_ns_weak = Arc::downgrade(current_mnt_ns);
|
||||
let new_mount = self.mount.clone_mount_tree(
|
||||
&self.dentry,
|
||||
¤t_mnt_ns_weak,
|
||||
recursive,
|
||||
MountNsFileCopying::Copy,
|
||||
&topology_guard,
|
||||
)?;
|
||||
new_mount.graft_mount_tree(dst_path);
|
||||
new_mount.graft_mount_tree(dst_path, &mut topology_guard);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
@ -523,10 +538,11 @@ impl Path {
|
|||
"the destination path is not in this mount namespace"
|
||||
);
|
||||
}
|
||||
current_mnt_ns.check_no_mnt_ns_loop_in_tree(self.mount_node())?;
|
||||
let mut topology_guard = MountTopology::write_lock();
|
||||
current_mnt_ns.check_no_mnt_ns_loop_in_tree(self.mount_node(), &topology_guard)?;
|
||||
if dst_path
|
||||
.mount_node()
|
||||
.is_equal_or_descendant_of(self.mount_node())
|
||||
.is_equal_or_descendant_of(self.mount_node(), &topology_guard)
|
||||
{
|
||||
// Reject moves that would place a mount beneath itself, because the mount tree
|
||||
// must remain acyclic.
|
||||
|
|
@ -536,7 +552,7 @@ impl Path {
|
|||
);
|
||||
}
|
||||
|
||||
self.mount.graft_mount_tree(dst_path);
|
||||
self.mount.graft_mount_tree(dst_path, &mut topology_guard);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -558,7 +574,9 @@ impl Path {
|
|||
return_errno_with_message!(Errno::EINVAL, "the path is not in this mount namespace");
|
||||
}
|
||||
|
||||
self.mount.set_propagation(prop, recursive);
|
||||
let mut topology_guard = MountTopology::write_lock();
|
||||
self.mount
|
||||
.set_propagation(prop, recursive, &mut topology_guard);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ use core::sync::atomic::{AtomicU32, Ordering};
|
|||
use atomic_integer_wrapper::define_atomic_version_of_integer_like_type;
|
||||
use hashbrown::HashMap;
|
||||
use id_alloc::IdAlloc;
|
||||
use ostd::sync::{RwMutexReadGuard, RwMutexWriteGuard};
|
||||
use spin::Once;
|
||||
|
||||
use super::try_get_mnt_ns_inode;
|
||||
|
|
@ -23,6 +24,35 @@ use crate::{
|
|||
prelude::*,
|
||||
};
|
||||
|
||||
/// Provides synchronized access to mount topology.
|
||||
pub(super) struct MountTopology {
|
||||
_private: (),
|
||||
}
|
||||
|
||||
fn global_mount_topology() -> &'static RwMutex<MountTopology> {
|
||||
static MOUNT_TOPOLOGY: RwMutex<MountTopology> = RwMutex::new(MountTopology { _private: () });
|
||||
&MOUNT_TOPOLOGY
|
||||
}
|
||||
|
||||
impl MountTopology {
|
||||
/// Acquires the write side of the mount topology lock.
|
||||
///
|
||||
/// Use this for operations that may change the mount topology,
|
||||
/// including the parent-child links, mountpoints, mount propagation state,
|
||||
/// or namespace-visible mount trees.
|
||||
pub(super) fn write_lock() -> RwMutexWriteGuard<'static, Self> {
|
||||
global_mount_topology().write()
|
||||
}
|
||||
|
||||
/// Acquires the read side of the mount topology lock.
|
||||
///
|
||||
/// Use this for operations that need a stable view of mount topology
|
||||
/// without changing it.
|
||||
pub(super) fn read_lock() -> RwMutexReadGuard<'static, Self> {
|
||||
global_mount_topology().read()
|
||||
}
|
||||
}
|
||||
|
||||
/// Controls how recursive mount-tree cloning handles mount-namespace files.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub(super) enum MountNsFileCopying {
|
||||
|
|
@ -293,6 +323,7 @@ impl Mount {
|
|||
flags: PerMountFlags,
|
||||
mountpoint: &Arc<Dentry>,
|
||||
source: Option<String>,
|
||||
_topology: &mut MountTopology,
|
||||
) -> Result<Arc<Self>> {
|
||||
if mountpoint.type_() != InodeType::Dir {
|
||||
return_errno!(Errno::ENOTDIR);
|
||||
|
|
@ -315,14 +346,18 @@ impl Mount {
|
|||
/// Unmounts a child mount node from the mountpoint and returns it.
|
||||
///
|
||||
/// The mountpoint should belong to this mount node, or an error is returned.
|
||||
pub(super) fn do_unmount(&self, mountpoint: &Dentry) -> Result<Arc<Self>> {
|
||||
pub(super) fn do_unmount(
|
||||
&self,
|
||||
mountpoint: &Dentry,
|
||||
topology: &mut MountTopology,
|
||||
) -> Result<Arc<Self>> {
|
||||
let child_mount = self
|
||||
.children
|
||||
.write()
|
||||
.remove(&mountpoint.key())
|
||||
.ok_or_else(|| Error::with_message(Errno::ENOENT, "can not find child mount"))?;
|
||||
|
||||
child_mount.clear_mountpoint();
|
||||
child_mount.clear_topology_link(topology);
|
||||
|
||||
Ok(child_mount)
|
||||
}
|
||||
|
|
@ -374,6 +409,7 @@ impl Mount {
|
|||
new_ns: &Weak<MountNamespace>,
|
||||
recursive: bool,
|
||||
mnt_ns_file_copying: MountNsFileCopying,
|
||||
_topology: &MountTopology,
|
||||
) -> Result<Arc<Self>> {
|
||||
let new_root_mount = self.clone_mount(root_dentry, new_ns)?;
|
||||
if !recursive {
|
||||
|
|
@ -414,7 +450,12 @@ impl Mount {
|
|||
}
|
||||
|
||||
/// Sets the propagation type of this mount.
|
||||
pub(super) fn set_propagation(&self, prop: MountPropType, recursive: bool) {
|
||||
pub(super) fn set_propagation(
|
||||
&self,
|
||||
prop: MountPropType,
|
||||
recursive: bool,
|
||||
_topology: &mut MountTopology,
|
||||
) {
|
||||
*self.propagation.write() = prop;
|
||||
if !recursive {
|
||||
return;
|
||||
|
|
@ -428,7 +469,7 @@ impl Mount {
|
|||
}
|
||||
|
||||
/// Detaches the mount node from the parent mount node.
|
||||
pub(super) fn detach_from_parent(&self) {
|
||||
pub(super) fn detach_from_parent(&self, topology: &mut MountTopology) {
|
||||
if let Some(parent) = self.parent() {
|
||||
let parent = parent.upgrade().unwrap();
|
||||
let child = parent
|
||||
|
|
@ -437,13 +478,24 @@ impl Mount {
|
|||
.remove(&self.mountpoint().unwrap().key());
|
||||
|
||||
if let Some(child) = child {
|
||||
child.clear_mountpoint();
|
||||
child.clear_topology_link(topology);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Clears this mount node's topology link.
|
||||
///
|
||||
/// The parent pointer and mountpoint describe the same topology edge, so
|
||||
/// they must be cleared together while holding the mount topology lock.
|
||||
///
|
||||
/// This only mutates this mount node's own link state.
|
||||
pub(super) fn clear_topology_link(&self, _topology: &mut MountTopology) {
|
||||
self.set_parent(None);
|
||||
self.clear_mountpoint();
|
||||
}
|
||||
|
||||
/// Attaches the mount node to the mountpoint.
|
||||
fn attach_to_path(&self, target_path: &Path) {
|
||||
fn attach_to_path(&self, target_path: &Path, _topology: &mut MountTopology) {
|
||||
let key = target_path.dentry.key();
|
||||
target_path
|
||||
.mount_node()
|
||||
|
|
@ -455,9 +507,9 @@ impl Mount {
|
|||
}
|
||||
|
||||
/// Grafts the mount node tree to the mountpoint.
|
||||
pub(super) fn graft_mount_tree(&self, target_path: &Path) {
|
||||
self.detach_from_parent();
|
||||
self.attach_to_path(target_path);
|
||||
pub(super) fn graft_mount_tree(&self, target_path: &Path, topology: &mut MountTopology) {
|
||||
self.detach_from_parent(topology);
|
||||
self.attach_to_path(target_path, topology);
|
||||
}
|
||||
|
||||
/// Gets a child mount node from the mountpoint if any.
|
||||
|
|
@ -476,7 +528,7 @@ impl Mount {
|
|||
}
|
||||
|
||||
/// Sets the mountpoint.
|
||||
pub(super) fn set_mountpoint(&self, dentry: &Arc<Dentry>) {
|
||||
fn set_mountpoint(&self, dentry: &Arc<Dentry>) {
|
||||
let mut mountpoint = self.mountpoint.write();
|
||||
if let Some(mountpoint) = mountpoint.as_deref() {
|
||||
mountpoint.dec_mount_count();
|
||||
|
|
@ -487,7 +539,7 @@ impl Mount {
|
|||
}
|
||||
|
||||
/// Clears the mountpoint.
|
||||
pub(super) fn clear_mountpoint(&self) {
|
||||
fn clear_mountpoint(&self) {
|
||||
let mut mountpoint = self.mountpoint.write();
|
||||
if let Some(mountpoint) = mountpoint.as_deref() {
|
||||
mountpoint.dec_mount_count();
|
||||
|
|
@ -508,6 +560,7 @@ impl Mount {
|
|||
fs_flags: Option<FsFlags>,
|
||||
data: Option<CString>,
|
||||
ctx: &Context,
|
||||
_topology: &mut MountTopology,
|
||||
) -> Result<()> {
|
||||
// TODO: This lock is a workaround to guarantee the atomicity of remount operation.
|
||||
// We need to re-design the lock mechanism of `Mount` and file system in the future.
|
||||
|
|
@ -547,7 +600,11 @@ impl Mount {
|
|||
}
|
||||
|
||||
/// Returns whether `self` is `ancestor` or a descendant of it in the mount tree.
|
||||
pub(super) fn is_equal_or_descendant_of(&self, ancestor: &Arc<Self>) -> bool {
|
||||
pub(super) fn is_equal_or_descendant_of(
|
||||
&self,
|
||||
ancestor: &Arc<Self>,
|
||||
_topology: &MountTopology,
|
||||
) -> bool {
|
||||
let mut current = self.this();
|
||||
loop {
|
||||
if Arc::ptr_eq(¤t, ancestor) {
|
||||
|
|
@ -580,7 +637,7 @@ impl Mount {
|
|||
///
|
||||
/// In some cases we may need to reset the parent of
|
||||
/// the created Mount, such as move mount.
|
||||
pub(super) fn set_parent(&self, mount: Option<&Arc<Mount>>) {
|
||||
fn set_parent(&self, mount: Option<&Arc<Mount>>) {
|
||||
let mut parent = self.parent.write();
|
||||
*parent = mount.map(Arc::downgrade);
|
||||
}
|
||||
|
|
@ -589,6 +646,7 @@ impl Mount {
|
|||
pub(super) fn find_corresponding_mount(
|
||||
&self,
|
||||
mnt_ns: &Arc<MountNamespace>,
|
||||
_topology: &MountTopology,
|
||||
) -> Option<Arc<Self>> {
|
||||
// Collect the ancestors from self to the root mount (The root mount is not included).
|
||||
let mut ancestors = VecDeque::new();
|
||||
|
|
|
|||
|
|
@ -4,7 +4,10 @@ use alloc::sync::UniqueArc;
|
|||
|
||||
use spin::Once;
|
||||
|
||||
use super::{mount::MountNsFileCopying, try_get_mnt_ns_inode};
|
||||
use super::{
|
||||
mount::{MountNsFileCopying, MountTopology},
|
||||
try_get_mnt_ns_inode,
|
||||
};
|
||||
use crate::{
|
||||
fs::{
|
||||
fs_impls::ramfs::RamFs,
|
||||
|
|
@ -123,6 +126,8 @@ impl MountNamespace {
|
|||
CapSet::SYS_ADMIN,
|
||||
))?;
|
||||
|
||||
let topology_guard = MountTopology::read_lock();
|
||||
|
||||
let root_mount = self.root();
|
||||
Self::new_with_root(owner, |weak_ns| {
|
||||
root_mount.clone_mount_tree(
|
||||
|
|
@ -130,6 +135,7 @@ impl MountNamespace {
|
|||
weak_ns,
|
||||
true,
|
||||
MountNsFileCopying::Skip,
|
||||
&topology_guard,
|
||||
)
|
||||
})
|
||||
}
|
||||
|
|
@ -174,7 +180,11 @@ impl MountNamespace {
|
|||
|
||||
/// Ensures that importing the mount subtree rooted at `root_mount` into this
|
||||
/// mount namespace would not form a mount-namespace loop.
|
||||
pub(super) fn check_no_mnt_ns_loop_in_tree(&self, root_mount: &Arc<Mount>) -> Result<()> {
|
||||
pub(super) fn check_no_mnt_ns_loop_in_tree(
|
||||
&self,
|
||||
root_mount: &Arc<Mount>,
|
||||
_topology: &MountTopology,
|
||||
) -> Result<()> {
|
||||
let mut worklist = VecDeque::new();
|
||||
worklist.push_back(root_mount.clone());
|
||||
|
||||
|
|
@ -208,13 +218,15 @@ impl Drop for MountNamespace {
|
|||
// and thus the subsequent cleanup logic can be skipped.
|
||||
return;
|
||||
};
|
||||
|
||||
let mut topology_guard = MountTopology::write_lock();
|
||||
|
||||
let mut worklist = VecDeque::new();
|
||||
worklist.push_back(root.clone());
|
||||
while let Some(current_mount) = worklist.pop_front() {
|
||||
let mut children = current_mount.children.write();
|
||||
for (_, child) in children.drain() {
|
||||
child.set_parent(None);
|
||||
child.clear_mountpoint();
|
||||
child.clear_topology_link(&mut topology_guard);
|
||||
worklist.push_back(child);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ use alloc::str;
|
|||
|
||||
use ostd::task::Task;
|
||||
|
||||
use super::{Mount, Path};
|
||||
use super::{Mount, Path, mount::MountTopology};
|
||||
use crate::{
|
||||
fs::{
|
||||
file::{
|
||||
|
|
@ -334,18 +334,26 @@ impl PathResolver {
|
|||
return Ok(());
|
||||
}
|
||||
|
||||
let new_root = self.root.find_corresponding_mount(mnt_ns).ok_or_else(|| {
|
||||
Error::with_message(
|
||||
Errno::EINVAL,
|
||||
"the root directory does not exist in the target mount namespace",
|
||||
)
|
||||
})?;
|
||||
let new_cwd = self.cwd.find_corresponding_mount(mnt_ns).ok_or_else(|| {
|
||||
Error::with_message(
|
||||
Errno::EINVAL,
|
||||
"the current working directory does not exist in the target mount namespace",
|
||||
)
|
||||
})?;
|
||||
let topology_guard = MountTopology::read_lock();
|
||||
|
||||
let new_root = self
|
||||
.root
|
||||
.find_corresponding_mount(mnt_ns, &topology_guard)
|
||||
.ok_or_else(|| {
|
||||
Error::with_message(
|
||||
Errno::EINVAL,
|
||||
"the root directory does not exist in the target mount namespace",
|
||||
)
|
||||
})?;
|
||||
let new_cwd = self
|
||||
.cwd
|
||||
.find_corresponding_mount(mnt_ns, &topology_guard)
|
||||
.ok_or_else(|| {
|
||||
Error::with_message(
|
||||
Errno::EINVAL,
|
||||
"the current working directory does not exist in the target mount namespace",
|
||||
)
|
||||
})?;
|
||||
|
||||
self.root = new_root;
|
||||
self.cwd = new_cwd;
|
||||
|
|
@ -411,13 +419,15 @@ impl PathResolver {
|
|||
"`new_root` or the current root is on the rootfs mount"
|
||||
);
|
||||
}
|
||||
if !put_old_path.is_reachable_from(&new_root_path) {
|
||||
let mut topology_guard = MountTopology::write_lock();
|
||||
|
||||
if !put_old_path.is_reachable_from(&new_root_path, &topology_guard) {
|
||||
return_errno_with_message!(
|
||||
Errno::EINVAL,
|
||||
"`put_old` is not at or underneath `new_root`"
|
||||
);
|
||||
}
|
||||
if !new_root_path.is_reachable_from(&self.root) {
|
||||
if !new_root_path.is_reachable_from(&self.root, &topology_guard) {
|
||||
return_errno_with_message!(
|
||||
Errno::EINVAL,
|
||||
"`new_root` is not at or underneath the current root"
|
||||
|
|
@ -430,8 +440,15 @@ impl PathResolver {
|
|||
Path::new(parent_mount, mountpoint)
|
||||
};
|
||||
|
||||
self.root.mount.graft_mount_tree(&put_old_path);
|
||||
new_root_path.mount.graft_mount_tree(&parent_path);
|
||||
self.root
|
||||
.mount
|
||||
.graft_mount_tree(&put_old_path, &mut topology_guard);
|
||||
new_root_path
|
||||
.mount
|
||||
.graft_mount_tree(&parent_path, &mut topology_guard);
|
||||
|
||||
// Release the mount topology lock before taking other threads' resolver locks.
|
||||
drop(topology_guard);
|
||||
|
||||
// TODO: This method should only iterate threads in the current PID namespace instead of
|
||||
// the whole PID table.
|
||||
|
|
@ -501,6 +518,7 @@ impl PathResolver {
|
|||
///
|
||||
/// The mounts are collected in depth-first order.
|
||||
pub(in crate::fs) fn collect_visible_mounts(&self) -> Vec<Arc<Mount>> {
|
||||
let _topology_guard = MountTopology::read_lock();
|
||||
let mut visible = Vec::new();
|
||||
let mut stack = vec![self.root.mount.clone()];
|
||||
let is_root_mount_root = self.root.is_mount_root();
|
||||
|
|
|
|||
Loading…
Reference in New Issue