Skip to content

Commit 6c6d313

Browse files
authored
fix(namespace): implement propagated unmount transactions (#2121)
* fix(namespace): implement propagated unmount transactions Resolve propagated unmount targets by exact parent and mountpoint identity, process the complete local source subtree, and compute Linux-compatible mark, remove, restore, and lazy-retain decisions before mutation. Commit mount-edge and propagation-graph changes under the topology lock with prepared capacity and exactly-once lifecycle cleanup. Preserve root stack ordering, locked root restoration, and detached-connected locked descendants. Add object-level topology coverage and DragonOS dunitest regressions for private children, complete subtrees, blockers, root covers, selected toppers, locked descendants, and propagation chains. Fixes: #2100 Signed-off-by: longjin <longjin@dragonos.org> * fix(namespace): unmount visible propagated shadow Map Linux's mount-hash head lookup to DragonOS's visible stack topper. DragonOS stores direct mounts oldest-to-newest, so selecting the first vector entry could detach a hidden lower mount instead of the propagated mount that is currently visible. Use lookup_top consistently during target preparation and final validation, remove the misleading lookup_first helper, and add a regression test for a direct [lower, top] shadow stack. Signed-off-by: longjin <longjin@dragonos.org> * test(namespace): cover tucked shadow restoration Document the Linux 6.6 distinction between a normal nested overmount and the flat-source race that tucks an older propagated copy below the new copy root. Verify that propagated lazy unmount removes the new copy while restoring the tucked lower copy with exact edge, backlink, and lifecycle invariants. Also express the propagation closure scans with iterators so the repository-wide make fmt clippy gate passes without changing behavior. Signed-off-by: longjin <longjin@dragonos.org> --------- Signed-off-by: longjin <longjin@dragonos.org>
1 parent d544044 commit 6c6d313

6 files changed

Lines changed: 1170 additions & 97 deletions

File tree

kernel/src/filesystem/vfs/mount/mod.rs

Lines changed: 181 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@ use crate::{
2727
propagation::{
2828
abort_mount_propagation, commit_mount_propagation_locked, detach_mount_propagation,
2929
ensure_subtree_shared, inherit_bind_mount_propagation,
30-
prepare_mount_propagation_locked, propagate_umount, propagation_umount_busy,
31-
MountPropagation,
30+
prepare_mount_propagation_locked, propagate_umount_sources,
31+
propagation_umount_busy, MountPropagation,
3232
},
3333
},
3434
ProcessManager,
@@ -403,6 +403,7 @@ struct MountLifecycle {
403403
state: MountLifecycleState,
404404
external_pins: usize,
405405
construction_reserved: bool,
406+
propagation_attached: bool,
406407
detached_component: Option<Arc<DetachedMountComponent>>,
407408
}
408409

@@ -1115,6 +1116,7 @@ impl MountFS {
11151116
state: MountLifecycleState::Constructing,
11161117
external_pins: 0,
11171118
construction_reserved: state_init.construction_reserved,
1119+
propagation_attached: true,
11181120
detached_component: None,
11191121
}),
11201122
});
@@ -1159,6 +1161,7 @@ impl MountFS {
11591161
state: MountLifecycleState::Constructing,
11601162
external_pins: 0,
11611163
construction_reserved: true,
1164+
propagation_attached: true,
11621165
detached_component: None,
11631166
}),
11641167
});
@@ -1380,6 +1383,133 @@ impl MountFS {
13801383
}
13811384
}
13821385

1386+
/// Remove one exact mount edge while moving every mount stacked on the
1387+
/// removed mount's root back to the removed edge in chronological order.
1388+
///
1389+
/// This is the `umount_list()` restoration operation used by propagated
1390+
/// umount. Each tuple identifies whether that root child is restored to the
1391+
/// original parent (`true`) or retained below a lazy-detached parent
1392+
/// (`false`). Commit capacity is reserved during preflight.
1393+
pub(crate) fn detach_exact_restoring_root_children(
1394+
&self,
1395+
mount_fs: &Arc<MountFS>,
1396+
root_children: Vec<(Arc<MountFS>, bool)>,
1397+
reservation: &MountEdgeReservation,
1398+
) -> Result<Arc<MountFS>, SystemError> {
1399+
let original_mountpoint = mount_fs.self_mountpoint().ok_or(SystemError::EINVAL)?;
1400+
if !Arc::ptr_eq(&original_mountpoint.mount_fs, &self.self_ref())
1401+
|| !Arc::ptr_eq(&reservation.parent, &self.self_ref())
1402+
|| !Arc::ptr_eq(reservation.mountpoint(), &original_mountpoint)
1403+
|| Arc::ptr_eq(&self.self_ref(), mount_fs)
1404+
{
1405+
return Err(SystemError::EINVAL);
1406+
}
1407+
let root_mountpoint = mount_fs.mountpoint_root_inode();
1408+
let original_dentry = original_mountpoint.shared_dentry();
1409+
let root_dentry = root_mountpoint.shared_dentry();
1410+
1411+
with_dentry_mount_gates(Some(&original_dentry), Some(&root_dentry), || {
1412+
fn replace_edge(
1413+
parent_mountpoints: &mut HashMap<DentryId, Vec<Arc<MountFS>>>,
1414+
child_mountpoints: &mut HashMap<DentryId, Vec<Arc<MountFS>>>,
1415+
original_mountpoint: &Arc<MountFSInode>,
1416+
root_mountpoint: &Arc<MountFSInode>,
1417+
mount_fs: &Arc<MountFS>,
1418+
root_children: &[(Arc<MountFS>, bool)],
1419+
) -> Result<(), SystemError> {
1420+
let parent_stack = parent_mountpoints
1421+
.get(&original_mountpoint.dentry_id())
1422+
.ok_or(SystemError::ENOENT)?;
1423+
let parent_index = parent_stack
1424+
.iter()
1425+
.position(|child| Arc::ptr_eq(child, mount_fs))
1426+
.ok_or(SystemError::ENOENT)?;
1427+
let actual_root_children = child_mountpoints
1428+
.get(&root_mountpoint.dentry_id())
1429+
.map(Vec::as_slice)
1430+
.unwrap_or_default();
1431+
if actual_root_children.len() != root_children.len()
1432+
|| actual_root_children
1433+
.iter()
1434+
.zip(root_children)
1435+
.any(|(actual, (expected, _))| !Arc::ptr_eq(actual, expected))
1436+
{
1437+
return Err(SystemError::EBUSY);
1438+
}
1439+
1440+
let parent_stack = parent_mountpoints
1441+
.get_mut(&original_mountpoint.dentry_id())
1442+
.expect("validated propagated-umount parent stack");
1443+
parent_stack.remove(parent_index);
1444+
let mut offset = 0;
1445+
for (child, restore) in root_children {
1446+
if !restore {
1447+
continue;
1448+
}
1449+
child.relocate_mountpoint(Some(original_mountpoint.clone()));
1450+
child.tucked_under.store(false, Ordering::Release);
1451+
parent_stack.insert(parent_index + offset, child.clone());
1452+
offset += 1;
1453+
}
1454+
let remove_root_key = if root_children.is_empty() {
1455+
false
1456+
} else {
1457+
let root_stack = child_mountpoints
1458+
.get_mut(&root_mountpoint.dentry_id())
1459+
.expect("validated propagated-umount root stack");
1460+
let mut index = 0;
1461+
root_stack.retain(|_| {
1462+
let retain = !root_children[index].1;
1463+
index += 1;
1464+
retain
1465+
});
1466+
root_stack.is_empty()
1467+
};
1468+
if remove_root_key {
1469+
child_mountpoints.remove(&root_mountpoint.dentry_id());
1470+
}
1471+
Ok(())
1472+
}
1473+
1474+
if self.mount_id.data() < mount_fs.mount_id.data() {
1475+
let mut parent_mountpoints = self.mountpoints.lock();
1476+
let mut child_mountpoints = mount_fs.mountpoints.lock();
1477+
replace_edge(
1478+
&mut parent_mountpoints,
1479+
&mut child_mountpoints,
1480+
&original_mountpoint,
1481+
&root_mountpoint,
1482+
mount_fs,
1483+
&root_children,
1484+
)?;
1485+
} else {
1486+
let mut child_mountpoints = mount_fs.mountpoints.lock();
1487+
let mut parent_mountpoints = self.mountpoints.lock();
1488+
replace_edge(
1489+
&mut parent_mountpoints,
1490+
&mut child_mountpoints,
1491+
&original_mountpoint,
1492+
&root_mountpoint,
1493+
mount_fs,
1494+
&root_children,
1495+
)?;
1496+
}
1497+
1498+
let restored_count = root_children.iter().filter(|(_, restore)| *restore).count();
1499+
root_dentry
1500+
.mount_edges
1501+
.fetch_sub(restored_count, Ordering::Release);
1502+
if restored_count == 0 {
1503+
original_dentry.mount_edges.fetch_sub(1, Ordering::Release);
1504+
} else if restored_count > 1 {
1505+
original_dentry
1506+
.mount_edges
1507+
.fetch_add(restored_count - 1, Ordering::Release);
1508+
}
1509+
Ok(mount_fs.clone())
1510+
})
1511+
}
1512+
13831513
fn restore_exact_cover(
13841514
&self,
13851515
mount_fs: &Arc<MountFS>,
@@ -1755,22 +1885,29 @@ impl MountFS {
17551885
pub(crate) fn deactivate(&self) {
17561886
let (should_remove, release_construction, should_leave_propagation) = {
17571887
let mut lifecycle = self.lifecycle.lock();
1758-
match lifecycle.state {
1888+
let should_leave_propagation = lifecycle.propagation_attached;
1889+
lifecycle.propagation_attached = false;
1890+
let (should_remove, release_construction) = match lifecycle.state {
17591891
MountLifecycleState::Constructing => {
17601892
lifecycle.state = MountLifecycleState::Detached;
17611893
let reserved = lifecycle.construction_reserved;
17621894
lifecycle.construction_reserved = false;
1763-
(false, reserved, true)
1895+
(false, reserved)
17641896
}
17651897
MountLifecycleState::Live
17661898
| MountLifecycleState::Detaching
17671899
| MountLifecycleState::DetachedConnected => {
17681900
lifecycle.state = MountLifecycleState::Detached;
17691901
lifecycle.detached_component = None;
1770-
(true, false, true)
1902+
(true, false)
17711903
}
1772-
MountLifecycleState::Detached => (false, false, false),
1773-
}
1904+
MountLifecycleState::Detached => (false, false),
1905+
};
1906+
(
1907+
should_remove,
1908+
release_construction,
1909+
should_leave_propagation,
1910+
)
17741911
};
17751912
if should_leave_propagation {
17761913
detach_mount_propagation(&self.self_ref());
@@ -1783,6 +1920,22 @@ impl MountFS {
17831920
}
17841921
}
17851922

1923+
/// Record that a prepared batch graph commit already removed this mount's
1924+
/// propagation relationships. Later lifecycle teardown must not repeat it.
1925+
pub(crate) fn mark_propagation_detached(&self) {
1926+
self.lifecycle.lock().propagation_attached = false;
1927+
}
1928+
1929+
fn detach_propagation_once(&self) {
1930+
let should_detach = {
1931+
let mut lifecycle = self.lifecycle.lock();
1932+
core::mem::replace(&mut lifecycle.propagation_attached, false)
1933+
};
1934+
if should_detach {
1935+
detach_mount_propagation(&self.self_ref());
1936+
}
1937+
}
1938+
17861939
pub(crate) fn has_external_pins(&self) -> bool {
17871940
self.lifecycle.lock().external_pins != 0
17881941
}
@@ -1895,7 +2048,7 @@ impl MountFS {
18952048
namespace.remove_mount_exact(mount);
18962049
}
18972050
mount.clear_namespace();
1898-
detach_mount_propagation(mount);
2051+
mount.detach_propagation_once();
18992052
}
19002053

19012054
let mut component_roots = vec![root.clone()];
@@ -2111,7 +2264,17 @@ impl MountFS {
21112264
}
21122265

21132266
let root_mountpoint = root.self_mountpoint().ok_or(SystemError::EINVAL)?;
2114-
root.commit_umount_at(&root_mountpoint, lazy)?;
2267+
if let Err(error) = propagate_umount_sources(&mounts, lazy) {
2268+
for mount in &mounts {
2269+
let mut lifecycle = mount.lifecycle.lock();
2270+
if lifecycle.state == MountLifecycleState::Detaching {
2271+
lifecycle.state = MountLifecycleState::Live;
2272+
}
2273+
}
2274+
return Err(error);
2275+
}
2276+
root.commit_umount_at(&root_mountpoint, lazy)
2277+
.expect("prepared local umount edge commit cannot fail");
21152278
// Final filesystem shutdown may sleep and may itself need pathname
21162279
// operations. Never wait while holding the topology/admission lock.
21172280
drop(_topology);
@@ -2127,6 +2290,7 @@ impl MountFS {
21272290
/// pre-detach sync; final shared-superblock shutdown remains deferred until
21282291
/// every mount and external pin is gone.
21292292
pub fn umount_with_mode(&self, lazy: bool) -> Result<Arc<MountFS>, SystemError> {
2293+
let _topology = MOUNT_LIFECYCLE_LOCK.lock();
21302294
let mountpoint = self.self_mountpoint().ok_or(SystemError::EINVAL)?;
21312295

21322296
if !lazy && propagation_umount_busy(&mountpoint.mount_fs, &mountpoint) {
@@ -2143,8 +2307,14 @@ impl MountFS {
21432307
}
21442308
lifecycle.state = MountLifecycleState::Detaching;
21452309
}
2146-
2147-
self.commit_umount_at(&mountpoint, lazy)
2310+
if let Err(error) = propagate_umount_sources(core::slice::from_ref(&self.self_ref()), lazy)
2311+
{
2312+
self.lifecycle.lock().state = MountLifecycleState::Live;
2313+
return Err(error);
2314+
}
2315+
Ok(self
2316+
.commit_umount_at(&mountpoint, lazy)
2317+
.expect("prepared local umount edge commit cannot fail"))
21482318
}
21492319

21502320
fn commit_umount_at(
@@ -2155,12 +2325,6 @@ impl MountFS {
21552325
let parent = mountpoint.mount_fs();
21562326
let this_mount = self.self_ref();
21572327

2158-
// Propagation performs a full preflight and commits only peer/slave
2159-
// copies. Keep the local edge intact until that transaction succeeds,
2160-
// so failure needs no local topology rollback.
2161-
if parent.propagation().is_shared() {
2162-
propagate_umount(&parent, mountpoint, &this_mount, lazy)?;
2163-
}
21642328
let result = parent.detach_exact(&this_mount);
21652329

21662330
if result.is_ok() {

0 commit comments

Comments
 (0)