Skip to content

Commit f62b190

Browse files
authored
fix(namespace): make mount propagation transactional (#2116)
* fix(namespace): make mount propagation transactional Prepare every propagation clone, peer/slave registration, and mount edge capacity before publishing topology changes. Preserve the prepared root wrapper and exact covering mount so tuck-under rollback restores stacks, backlinks, flags, and edge counts without allocating. Resolve skipped slave layers through exact mount and peer-group identities to match Linux propagation semantics. Split move propagation into prepare, commit, and abort phases, retain the old edge for allocation-free rollback, and reserve private destination edges before detaching the source. Add kernel coverage for skipped peer/slave sources, move prepare abort, and exact tuck-under restoration. Extend the move dunitest to verify that propagated moves retain their complete child mount subtree. Signed-off-by: longjin <longjin@dragonos.org> * fix(namespace): track latest propagated peer source Update each peer-group source after a successful propagation clone so slaves of uncovered peers inherit from the nearest materialized peer, matching Linux propagate_one() last_source semantics. Add a regression test for the skipped-peer topology and apply the repository formatter required by the architecture format checks. Signed-off-by: longjin <longjin@dragonos.org> * fix(vfs): satisfy mount clippy checks Remove two redundant references in the prepared cover attach and rollback paths so the x86_64 CI lint gate accepts the transactional mount implementation. Signed-off-by: longjin <longjin@dragonos.org> --------- Signed-off-by: longjin <longjin@dragonos.org>
1 parent 24da4c5 commit f62b190

8 files changed

Lines changed: 737 additions & 120 deletions

File tree

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

Lines changed: 187 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ use crate::{
2828
abort_mount_propagation, commit_mount_propagation_locked, detach_mount_propagation,
2929
ensure_subtree_shared, inherit_bind_mount_propagation,
3030
prepare_mount_propagation_locked, propagate_umount, propagation_umount_busy,
31-
register_peer, register_slave_with_master, MountPropagation,
31+
MountPropagation,
3232
},
3333
},
3434
ProcessManager,
@@ -362,6 +362,33 @@ pub struct MountFS {
362362
lifecycle: Mutex<MountLifecycle>,
363363
}
364364

365+
/// Capacity reserved for one future mount edge. Creating an empty map entry is
366+
/// topology-neutral; if prepare aborts before the slot is consumed, Drop
367+
/// removes that entry so failed events leave no mountpoint residue.
368+
pub(crate) struct MountEdgeReservation {
369+
parent: Arc<MountFS>,
370+
mountpoint: Arc<MountFSInode>,
371+
}
372+
373+
impl Drop for MountEdgeReservation {
374+
fn drop(&mut self) {
375+
let mut mountpoints = self.parent.mountpoints.lock();
376+
let dentry_id = self.mountpoint.dentry.id;
377+
if mountpoints
378+
.get(&dentry_id)
379+
.is_some_and(|stack| stack.is_empty())
380+
{
381+
mountpoints.remove(&dentry_id);
382+
}
383+
}
384+
}
385+
386+
impl MountEdgeReservation {
387+
pub(crate) fn mountpoint(&self) -> &Arc<MountFSInode> {
388+
&self.mountpoint
389+
}
390+
}
391+
365392
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
366393
enum MountLifecycleState {
367394
Constructing,
@@ -1248,8 +1275,20 @@ impl MountFS {
12481275
&self,
12491276
mountpoint: &Arc<MountFSInode>,
12501277
mount_fs: Arc<MountFS>,
1278+
) -> Result<(), SystemError> {
1279+
let cover_mountpoint = mount_fs.mountpoint_root_inode();
1280+
self.attach_beneath_prepared(mountpoint, mount_fs, &cover_mountpoint)
1281+
}
1282+
1283+
pub(crate) fn attach_beneath_prepared(
1284+
&self,
1285+
mountpoint: &Arc<MountFSInode>,
1286+
mount_fs: Arc<MountFS>,
1287+
cover_mountpoint: &Arc<MountFSInode>,
12511288
) -> Result<(), SystemError> {
12521289
if !Arc::ptr_eq(&mountpoint.mount_fs, &self.self_ref())
1290+
|| !Arc::ptr_eq(&cover_mountpoint.mount_fs, &mount_fs)
1291+
|| cover_mountpoint.dentry.id != mount_fs.root_dentry.id
12531292
|| mount_fs
12541293
.self_mountpoint()
12551294
.as_ref()
@@ -1276,9 +1315,8 @@ impl MountFS {
12761315

12771316
// Linux mnt_set_mountpoint_beneath(): the propagated mount takes the
12781317
// original edge and the previous topper is reparented onto its root.
1279-
let cover_mountpoint = mount_fs.mountpoint_root_inode();
12801318
covered.relocate_mountpoint(Some(cover_mountpoint.clone()));
1281-
if let Err(error) = mount_fs.attach_top(&cover_mountpoint, covered.clone()) {
1319+
if let Err(error) = mount_fs.attach_top(cover_mountpoint, covered.clone()) {
12821320
covered.relocate_mountpoint(Some(mountpoint.clone()));
12831321
let mut mountpoints = self.mountpoints.lock();
12841322
let stack = mountpoints
@@ -1300,28 +1338,127 @@ impl MountFS {
13001338
mount_fs: &Arc<MountFS>,
13011339
) -> Result<Arc<MountFS>, SystemError> {
13021340
let cover_mountpoint = mount_fs.mountpoint_root_inode();
1303-
let Some(covered) = mount_fs
1304-
.children_at(&cover_mountpoint)
1305-
.into_iter()
1306-
.find(|child| child.tucked_under.load(Ordering::Acquire))
1307-
else {
1341+
let covered = mount_fs
1342+
.mountpoints
1343+
.lock()
1344+
.get(&cover_mountpoint.dentry.id)
1345+
.and_then(|stack| {
1346+
stack
1347+
.iter()
1348+
.find(|child| child.tucked_under.load(Ordering::Acquire))
1349+
.cloned()
1350+
});
1351+
let Some(covered) = covered else {
13081352
return self.detach_exact(mount_fs);
13091353
};
1354+
self.restore_exact_cover(mount_fs, &cover_mountpoint, &covered)
1355+
}
1356+
1357+
/// Transaction rollback variant using the exact topper and root wrapper
1358+
/// captured during prepare. It performs no discovery or allocation.
1359+
pub(crate) fn detach_exact_restoring_prepared_cover(
1360+
&self,
1361+
mount_fs: &Arc<MountFS>,
1362+
cover_mountpoint: Option<&Arc<MountFSInode>>,
1363+
covered: Option<&Arc<MountFS>>,
1364+
) -> Result<Arc<MountFS>, SystemError> {
1365+
match (cover_mountpoint, covered) {
1366+
(None, None) => self.detach_exact(mount_fs),
1367+
(Some(cover_mountpoint), Some(covered)) => {
1368+
if !covered.tucked_under.load(Ordering::Acquire)
1369+
|| !mount_fs
1370+
.mountpoints
1371+
.lock()
1372+
.get(&cover_mountpoint.dentry.id)
1373+
.is_some_and(|stack| stack.iter().any(|child| Arc::ptr_eq(child, covered)))
1374+
{
1375+
return Err(SystemError::ENOENT);
1376+
}
1377+
self.restore_exact_cover(mount_fs, cover_mountpoint, covered)
1378+
}
1379+
_ => Err(SystemError::EINVAL),
1380+
}
1381+
}
1382+
1383+
fn restore_exact_cover(
1384+
&self,
1385+
mount_fs: &Arc<MountFS>,
1386+
cover_mountpoint: &Arc<MountFSInode>,
1387+
covered: &Arc<MountFS>,
1388+
) -> Result<Arc<MountFS>, SystemError> {
13101389
let original_mountpoint = mount_fs.self_mountpoint().ok_or(SystemError::EINVAL)?;
1311-
mount_fs.detach_exact(&covered)?;
1390+
// Preserve the clone-root Vec until restoration completes. This is
1391+
// the rollback counterpart of the prepare-time cover reservation.
1392+
let _cover_reservation = mount_fs.reserve_mount_edge(cover_mountpoint, 0)?;
1393+
mount_fs.detach_exact_keep_slot(covered)?;
13121394
covered.relocate_mountpoint(Some(original_mountpoint.clone()));
1313-
let removed = self.detach_exact(mount_fs)?;
1314-
if let Err(error) = self.attach_top(&original_mountpoint, covered.clone()) {
1315-
// All objects remain owned; reconstruct the tuck-under topology.
1316-
self.attach_top(&original_mountpoint, mount_fs.clone())?;
1317-
covered.relocate_mountpoint(Some(cover_mountpoint.clone()));
1318-
mount_fs.attach_top(&cover_mountpoint, covered)?;
1319-
return Err(error);
1320-
}
1395+
let removed = match self.replace_exact_edge(mount_fs, covered.clone()) {
1396+
Ok(removed) => removed,
1397+
Err(error) => {
1398+
// All objects remain owned; reconstruct the tuck-under
1399+
// topology without allocating from the reserved cover slot.
1400+
covered.relocate_mountpoint(Some(cover_mountpoint.clone()));
1401+
mount_fs.attach_top(cover_mountpoint, covered.clone())?;
1402+
covered.restore_tucked_under(true);
1403+
return Err(error);
1404+
}
1405+
};
13211406
covered.tucked_under.store(false, Ordering::Release);
13221407
Ok(removed)
13231408
}
13241409

1410+
/// Replace one exact edge in place, preserving the parent stack's key,
1411+
/// capacity, ordering and mount-edge count.
1412+
fn replace_exact_edge(
1413+
&self,
1414+
old: &Arc<MountFS>,
1415+
replacement: Arc<MountFS>,
1416+
) -> Result<Arc<MountFS>, SystemError> {
1417+
let mountpoint = old.self_mountpoint().ok_or(SystemError::EINVAL)?;
1418+
if !Arc::ptr_eq(&mountpoint.mount_fs, &self.self_ref())
1419+
|| replacement
1420+
.self_mountpoint()
1421+
.as_ref()
1422+
.is_none_or(|replacement_mp| !Arc::ptr_eq(replacement_mp, &mountpoint))
1423+
{
1424+
return Err(SystemError::EINVAL);
1425+
}
1426+
let _gate = mountpoint.dentry.mount_gate.lock();
1427+
let mut mountpoints = self.mountpoints.lock();
1428+
let stack = mountpoints
1429+
.get_mut(&mountpoint.dentry.id)
1430+
.ok_or(SystemError::ENOENT)?;
1431+
let index = stack
1432+
.iter()
1433+
.position(|child| Arc::ptr_eq(child, old))
1434+
.ok_or(SystemError::ENOENT)?;
1435+
Ok(core::mem::replace(&mut stack[index], replacement))
1436+
}
1437+
1438+
pub(crate) fn detach_exact_keep_slot(
1439+
&self,
1440+
mount_fs: &Arc<MountFS>,
1441+
) -> Result<Arc<MountFS>, SystemError> {
1442+
let mountpoint = mount_fs.self_mountpoint().ok_or(SystemError::EINVAL)?;
1443+
if !Arc::ptr_eq(&mountpoint.mount_fs, &self.self_ref()) {
1444+
return Err(SystemError::EINVAL);
1445+
}
1446+
let _gate = mountpoint.dentry.mount_gate.lock();
1447+
let key = mountpoint.dentry.id;
1448+
let mut mountpoints = self.mountpoints.lock();
1449+
let stack = mountpoints.get_mut(&key).ok_or(SystemError::ENOENT)?;
1450+
let index = stack
1451+
.iter()
1452+
.position(|child| Arc::ptr_eq(child, mount_fs))
1453+
.ok_or(SystemError::ENOENT)?;
1454+
let removed = stack.remove(index);
1455+
mountpoint
1456+
.dentry
1457+
.mount_edges
1458+
.fetch_sub(1, Ordering::Release);
1459+
Ok(removed)
1460+
}
1461+
13251462
pub fn lookup_top(&self, mountpoint: &Arc<MountFSInode>) -> Option<Arc<MountFS>> {
13261463
self.mountpoints
13271464
.lock()
@@ -1377,6 +1514,39 @@ impl MountFS {
13771514
self.propagation.clone()
13781515
}
13791516

1517+
/// Reserve the HashMap key and stack capacity needed by a future exact
1518+
/// edge publication. Callers hold `MOUNT_LIFECYCLE_LOCK`, so no topology
1519+
/// writer can consume the reserved slot before commit.
1520+
pub(crate) fn reserve_mount_edge(
1521+
&self,
1522+
mountpoint: &Arc<MountFSInode>,
1523+
additional: usize,
1524+
) -> Result<MountEdgeReservation, SystemError> {
1525+
if !Arc::ptr_eq(&mountpoint.mount_fs, &self.self_ref()) {
1526+
return Err(SystemError::EINVAL);
1527+
}
1528+
let key = mountpoint.dentry.id;
1529+
let mut mountpoints = self.mountpoints.lock();
1530+
if let Some(stack) = mountpoints.get_mut(&key) {
1531+
stack
1532+
.try_reserve(additional)
1533+
.map_err(|_| SystemError::ENOMEM)?;
1534+
} else {
1535+
mountpoints
1536+
.try_reserve(1)
1537+
.map_err(|_| SystemError::ENOMEM)?;
1538+
let mut stack = Vec::new();
1539+
stack
1540+
.try_reserve(additional)
1541+
.map_err(|_| SystemError::ENOMEM)?;
1542+
mountpoints.insert(key, stack);
1543+
}
1544+
Ok(MountEdgeReservation {
1545+
parent: self.self_ref(),
1546+
mountpoint: mountpoint.clone(),
1547+
})
1548+
}
1549+
13801550
/// Get the mount ID
13811551
pub fn mount_id(&self) -> MountId {
13821552
self.mount_id
@@ -2559,17 +2729,6 @@ impl MountFSInode {
25592729
return Err(error);
25602730
}
25612731

2562-
let mut pending = vec![new_mount_fs.clone()];
2563-
while let Some(mount) = pending.pop() {
2564-
pending.extend(mount.mount_children());
2565-
let propagation = mount.propagation();
2566-
if propagation.is_shared() {
2567-
register_peer(propagation.peer_group_id(), &mount);
2568-
}
2569-
if propagation.is_slave() {
2570-
register_slave_with_master(&mount);
2571-
}
2572-
}
25732732
Ok(())
25742733
}
25752734

kernel/src/process/namespace/mnt.rs

Lines changed: 42 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@ use system_error::SystemError;
1515
use super::{
1616
nsproxy::NsCommon,
1717
propagation::{
18-
propagate_moved_tree_locked, register_peer, register_slave_with_master, MountPropagation,
18+
abort_moved_tree_propagation_locked, commit_moved_tree_propagation_locked,
19+
prepare_moved_tree_propagation_locked, register_peer, register_slave_with_master,
20+
MountPropagation,
1921
},
2022
user_namespace::UserNamespace,
2123
NamespaceOps,
@@ -280,7 +282,40 @@ impl MntNamespace {
280282
}
281283
}
282284

283-
old_parent.detach_exact(source_mfs)?;
285+
// Keep the old stack allocation alive until the move either commits
286+
// or restores this exact edge. Successful moves drop the token and
287+
// remove the now-empty key; rollback reuses the original Vec.
288+
let _old_edge_reservation = old_parent.reserve_mount_edge(&old_mountpoint, 0)?;
289+
290+
// Match Linux attach_recursive_mnt(MNT_TREE_MOVE): allocate group IDs
291+
// and clone every propagation target while the source still occupies
292+
// its old edge. Resource failure therefore cannot expose a transient
293+
// move and needs no topology rollback.
294+
let prepared_propagation = if target_parent.propagation().is_shared() {
295+
Some(prepare_moved_tree_propagation_locked(
296+
&target_parent,
297+
source_mfs,
298+
target_mountpoint,
299+
)?)
300+
} else {
301+
None
302+
};
303+
// Shared destinations reserve this edge as part of propagation
304+
// prepare. A private destination still needs the same guarantee:
305+
// attaching the moved root after detaching its old edge must not be
306+
// the first operation that tries to grow the target stack.
307+
let _private_target_reservation = if prepared_propagation.is_none() {
308+
Some(target_parent.reserve_mount_edge(target_mountpoint, 1)?)
309+
} else {
310+
None
311+
};
312+
313+
if let Err(error) = old_parent.detach_exact_keep_slot(source_mfs) {
314+
if let Some(prepared) = prepared_propagation {
315+
abort_moved_tree_propagation_locked(prepared);
316+
}
317+
return Err(error);
318+
}
284319
source_mfs.relocate_mountpoint(Some(target_mountpoint.clone()));
285320
if let Err(error) = target_parent.attach_new_top(target_mountpoint, source_mfs.clone()) {
286321
source_mfs.relocate_mountpoint(Some(old_mountpoint));
@@ -293,13 +328,14 @@ impl MntNamespace {
293328
source_mfs.clone(),
294329
)
295330
.expect("move rollback must restore the exact detached edge");
331+
if let Some(prepared) = prepared_propagation {
332+
abort_moved_tree_propagation_locked(prepared);
333+
}
296334
return Err(error);
297335
}
298336

299-
if target_parent.propagation().is_shared() {
300-
if let Err(error) =
301-
propagate_moved_tree_locked(&target_parent, source_mfs, target_mountpoint)
302-
{
337+
if let Some(prepared) = prepared_propagation {
338+
if let Err(error) = commit_moved_tree_propagation_locked(prepared) {
303339
target_parent
304340
.detach_exact(source_mfs)
305341
.expect("failed move propagation must detach the target edge");

0 commit comments

Comments
 (0)