Skip to content

Commit 7f63180

Browse files
committed
env: use syscalls when mounting and unmounting live layer bind mounts
1 parent 51492d6 commit 7f63180

File tree

1 file changed

+198
-85
lines changed

1 file changed

+198
-85
lines changed

crates/spfs/src/env.rs

+198-85
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ use linux_syscall::{
1616
SYS_fsopen,
1717
SYS_mount_setattr,
1818
SYS_move_mount,
19+
SYS_open_tree,
1920
};
2021

2122
use super::runtime;
@@ -36,6 +37,8 @@ const FSMOUNT_CLOEXEC: u32 = 0x00000001;
3637
const MOUNT_ATTR_RDONLY: u32 = 0x00000001;
3738
const MOUNT_ATTR_SIZE_VER0: u32 = 32;
3839
const MOVE_MOUNT_F_EMPTY_PATH: u32 = 0x00000004;
40+
const OPEN_TREE_CLONE: u32 = 1;
41+
const OPEN_TREE_CLOEXEC: u32 = 0o2000000;
3942

4043
// Linux fcntl constants from /usr/include/fcntl.h.
4144
const AT_EMPTY_PATH: u32 = 0x1000;
@@ -467,87 +470,6 @@ where
467470
rt.ensure_required_directories().await
468471
}
469472

470-
async fn mount_live_layers(&self, rt: &runtime::Runtime) -> Result<()> {
471-
// Mounts the bind mounts from the any live layers in the runtime the top of paths
472-
// inside /spfs
473-
//
474-
// It requires the mount destinations to exist under
475-
// /spfs/. If they do not, the mount commands will error. The
476-
// mount destinations are either provided by one of the layers
477-
// in the runtime, or by an earlier call to
478-
// ensure_extra_bind_mount_locations_exist() made in
479-
// initialize_runtime()
480-
let live_layers = rt.live_layers();
481-
if !live_layers.is_empty() {
482-
tracing::debug!("mounting the extra bind mounts over the {SPFS_DIR} filesystem ...");
483-
let mount = super::resolve::which("mount").unwrap_or_else(|| "/usr/bin/mount".into());
484-
485-
for layer in live_layers {
486-
let injection_mounts = layer.bind_mounts();
487-
488-
for extra_mount in injection_mounts {
489-
let dest = if extra_mount.dest.starts_with(SPFS_DIR_PREFIX) {
490-
PathBuf::from(extra_mount.dest.clone())
491-
} else {
492-
PathBuf::from(SPFS_DIR).join(extra_mount.dest.clone())
493-
};
494-
495-
let mut cmd = tokio::process::Command::new(mount.clone());
496-
cmd.arg("--bind");
497-
cmd.arg(extra_mount.src.to_string_lossy().into_owned());
498-
cmd.arg(dest);
499-
tracing::debug!("About to run: {cmd:?}");
500-
501-
match cmd.status().await {
502-
Err(err) => {
503-
return Err(Error::process_spawn_error("mount".to_owned(), err, None))
504-
}
505-
Ok(status) => match status.code() {
506-
Some(0) => (),
507-
_ => {
508-
return Err(format!(
509-
"Failed to inject bind mount into the {SPFS_DIR} filesystem using: {cmd:?}"
510-
).into())
511-
}
512-
},
513-
}
514-
}
515-
}
516-
}
517-
Ok(())
518-
}
519-
520-
async fn unmount_live_layers(&self, rt: &runtime::Runtime) -> Result<()> {
521-
// Unmount the bind mounted items from the live layers
522-
let live_layers = rt.live_layers();
523-
if !live_layers.is_empty() {
524-
tracing::debug!("unmounting the extra bind mounts from the {SPFS_DIR} filesystem ...");
525-
let umount =
526-
super::resolve::which("umount").unwrap_or_else(|| "/usr/bin/umount".into());
527-
528-
for layer in live_layers {
529-
let injection_mounts = layer.bind_mounts();
530-
531-
for extra_mount in injection_mounts {
532-
let mut cmd = tokio::process::Command::new(umount.clone());
533-
cmd.arg(PathBuf::from(SPFS_DIR).join(extra_mount.dest.clone()));
534-
tracing::debug!("About to run: {cmd:?}");
535-
536-
match cmd.status().await {
537-
Err(err) => {
538-
return Err(Error::process_spawn_error("umount".to_owned(), err, None))
539-
}
540-
Ok(status) => match status.code() {
541-
Some(0) => (),
542-
_ => return Err(format!("Failed to unmount a bind mount injected into the {SPFS_DIR} filesystem using: {cmd:?}").into()),
543-
},
544-
}
545-
}
546-
}
547-
}
548-
Ok(())
549-
}
550-
551473
/// Mounts an overlayfs built up from the given list of rendered
552474
/// layered directories (layer_dirs).
553475
///
@@ -569,7 +491,7 @@ where
569491
} else {
570492
mount_overlayfs_command(rt, layer_dirs).await?;
571493
}
572-
self.mount_live_layers(rt).await
494+
mount_live_layers(rt).await
573495
}
574496

575497
#[cfg(feature = "fuse-backend")]
@@ -580,7 +502,7 @@ where
580502
#[cfg(feature = "fuse-backend")]
581503
pub(crate) async fn mount_env_fuse(&self, rt: &runtime::Runtime) -> Result<()> {
582504
self.mount_fuse_onto(rt, SPFS_DIR).await?;
583-
self.mount_live_layers(rt).await
505+
mount_live_layers(rt).await
584506
}
585507

586508
#[cfg(feature = "fuse-backend")]
@@ -900,7 +822,7 @@ where
900822
// Unmount any extra paths mounted in the depths of
901823
// the fuse-only backend before fuse itself is
902824
// unmounted to avoid issue with lazy unmounting.
903-
self.unmount_live_layers(rt).await?;
825+
unmount_live_layers(rt).await?;
904826
std::path::Path::new(SPFS_DIR)
905827
}
906828
runtime::MountBackend::OverlayFsWithRenders | runtime::MountBackend::WinFsp => {
@@ -1229,7 +1151,6 @@ pub fn option_to_string(option: &fuser::MountOption) -> String {
12291151
}
12301152
}
12311153

1232-
12331154
/// Mount overlayfs layers using the mount command.
12341155
async fn mount_overlayfs_command<P: AsRef<Path>>(
12351156
rt: &runtime::Runtime,
@@ -1537,6 +1458,198 @@ fn mount_overlayfs_syscalls<P: AsRef<Path>>(rt: &runtime::Runtime, layer_dirs: &
15371458
Ok(())
15381459
}
15391460

1461+
/// Mounts the bind mounts from the any live layers in the runtime the top of paths inside /spfs.
1462+
async fn mount_live_layers(rt: &runtime::Runtime) -> Result<()> {
1463+
// This requires the mount destinations to exist under
1464+
// /spfs/. If they do not, the mount commands will error. The
1465+
// mount destinations are either provided by one of the layers
1466+
// in the runtime, or by an earlier call to
1467+
// ensure_extra_bind_mount_locations_exist() made in
1468+
// initialize_runtime()
1469+
let live_layers = rt.live_layers();
1470+
if !live_layers.is_empty() {
1471+
let spfs_config = crate::Config::current()?;
1472+
if spfs_config.filesystem.use_mount_syscalls {
1473+
mount_live_layers_syscalls(live_layers)?;
1474+
} else {
1475+
mount_live_layers_command(live_layers).await?;
1476+
}
1477+
}
1478+
1479+
Ok(())
1480+
}
1481+
1482+
/// Bind-mount live layers using the "mount" command.
1483+
async fn mount_live_layers_command(live_layers: &Vec<runtime::LiveLayer>) -> Result<()> {
1484+
tracing::debug!(
1485+
"mounting extra bind mounts over the {SPFS_DIR} filesystem using the mount command"
1486+
);
1487+
let mount = super::resolve::which("mount").unwrap_or_else(|| "/usr/bin/mount".into());
1488+
1489+
for layer in live_layers {
1490+
let injection_mounts = layer.bind_mounts();
1491+
1492+
for extra_mount in injection_mounts {
1493+
let dest = if extra_mount.dest.starts_with(SPFS_DIR_PREFIX) {
1494+
PathBuf::from(extra_mount.dest.clone())
1495+
} else {
1496+
PathBuf::from(SPFS_DIR).join(extra_mount.dest.clone())
1497+
};
1498+
1499+
let mut cmd = tokio::process::Command::new(mount.clone());
1500+
cmd.arg("--bind");
1501+
cmd.arg(extra_mount.src.to_string_lossy().into_owned());
1502+
cmd.arg(dest);
1503+
tracing::debug!("About to run: {cmd:?}");
1504+
1505+
match cmd.status().await {
1506+
Err(err) => return Err(Error::process_spawn_error("mount".to_owned(), err, None)),
1507+
Ok(status) => match status.code() {
1508+
Some(0) => (),
1509+
_ => {
1510+
return Err(format!(
1511+
"Failed to inject bind mount into the {SPFS_DIR} filesystem using: {cmd:?}"
1512+
)
1513+
.into())
1514+
}
1515+
},
1516+
}
1517+
}
1518+
}
1519+
1520+
Ok(())
1521+
}
1522+
1523+
/// Bind-mount live layers using mount syscalls.
1524+
fn mount_live_layers_syscalls(live_layers: &Vec<runtime::LiveLayer>) -> Result<()> {
1525+
tracing::debug!(
1526+
"mounting extra bind mounts over the {SPFS_DIR} filesystem using mount syscalls"
1527+
);
1528+
1529+
for layer in live_layers {
1530+
let injection_mounts = layer.bind_mounts();
1531+
1532+
for extra_mount in injection_mounts {
1533+
let Ok(src) = extra_mount.src.canonicalize() else {
1534+
return Err(format!("unable to canonicalize {:?}", extra_mount.src).into());
1535+
};
1536+
let dest = if extra_mount.dest.starts_with(SPFS_DIR_PREFIX) {
1537+
PathBuf::from(extra_mount.dest.clone())
1538+
} else {
1539+
PathBuf::from(SPFS_DIR).join(extra_mount.dest.clone())
1540+
};
1541+
let src_path = CString::new(src.to_string_lossy().as_ref())
1542+
.expect("allocate CString for live layer bind source");
1543+
let dest_path = CString::new(dest.to_string_lossy().as_ref())
1544+
.expect("allocate CString for live layer destination");
1545+
1546+
// mount_fd = open_tree(AT_FDCWD, "<src>", OPEN_TREE_CLONE|OPEN_TREE_CLOEXEC)
1547+
let mount_fd = unsafe {
1548+
syscall!(
1549+
SYS_open_tree,
1550+
AT_FDCWD,
1551+
src_path.as_ptr(),
1552+
OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC
1553+
)
1554+
}
1555+
.as_u64_unchecked() as i32;
1556+
if mount_fd < 0 {
1557+
return Err(format!("mount_live_layers_syscalls::SYS_open_tree(AT_FDCWD, {:?}, OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC) error: {}",
1558+
src_path, mount_fd).into());
1559+
}
1560+
1561+
// move_mount(mount_fd, "", AT_FDCWD, "<dest>", MOVE_MOUNT_F_EMPTY_PATH)
1562+
let rc = unsafe {
1563+
syscall!(
1564+
SYS_move_mount,
1565+
mount_fd,
1566+
c"".as_ptr(),
1567+
AT_FDCWD,
1568+
dest_path.as_ptr(),
1569+
MOVE_MOUNT_F_EMPTY_PATH
1570+
)
1571+
}
1572+
.as_u64_unchecked() as i32;
1573+
if rc != 0 {
1574+
return Err(format!(
1575+
"mount_overlayfs_syscalls::SYS_move_mount({}, \"\", AT_FDCWD, {:?}, MOVE_MOUNT_F_EMPTY_PATH) error: {}",
1576+
mount_fd, dest_path, rc
1577+
)
1578+
.into());
1579+
}
1580+
nix::unistd::close(mount_fd as std::ffi::c_int)?;
1581+
}
1582+
}
1583+
1584+
Ok(())
1585+
}
1586+
1587+
/// Unmount the bind mounted items from the live layers
1588+
async fn unmount_live_layers(rt: &runtime::Runtime) -> Result<()> {
1589+
let live_layers = rt.live_layers();
1590+
if !live_layers.is_empty() {
1591+
let spfs_config = crate::Config::current()?;
1592+
if spfs_config.filesystem.use_mount_syscalls {
1593+
unmount_live_layers_syscalls(live_layers)?;
1594+
} else {
1595+
unmount_live_layers_command(live_layers).await?;
1596+
}
1597+
}
1598+
1599+
Ok(())
1600+
}
1601+
1602+
/// Unmount live layers using the "umount" command.
1603+
async fn unmount_live_layers_command(live_layers: &Vec<runtime::LiveLayer>) -> Result<()> {
1604+
tracing::debug!(
1605+
"unmounting the extra bind mounts from the {SPFS_DIR} filesystem using the umount command ..."
1606+
);
1607+
let umount = super::resolve::which("umount").unwrap_or_else(|| "/usr/bin/umount".into());
1608+
for layer in live_layers {
1609+
let injection_mounts = layer.bind_mounts();
1610+
for extra_mount in injection_mounts {
1611+
let mut cmd = tokio::process::Command::new(umount.clone());
1612+
cmd.arg(PathBuf::from(SPFS_DIR).join(extra_mount.dest.clone()));
1613+
tracing::debug!("About to run: {cmd:?}");
1614+
match cmd.status().await {
1615+
Err(err) => {
1616+
return Err(Error::process_spawn_error("umount".to_owned(), err, None))
1617+
}
1618+
Ok(status) => match status.code() {
1619+
Some(0) => (),
1620+
_ => return Err(format!("Failed to unmount a bind mount injected into the {SPFS_DIR} filesystem using: {cmd:?}").into()),
1621+
},
1622+
}
1623+
}
1624+
}
1625+
1626+
Ok(())
1627+
}
1628+
1629+
/// Unmount live layers using umount syscalls.
1630+
fn unmount_live_layers_syscalls(live_layers: &Vec<runtime::LiveLayer>) -> Result<()> {
1631+
tracing::debug!(
1632+
"unmounting the extra bind mounts from the {SPFS_DIR} filesystem using syscalls ..."
1633+
);
1634+
for layer in live_layers {
1635+
let injection_mounts = layer.bind_mounts();
1636+
for extra_mount in injection_mounts {
1637+
let dest = if extra_mount.dest.starts_with(SPFS_DIR_PREFIX) {
1638+
PathBuf::from(extra_mount.dest.clone())
1639+
} else {
1640+
PathBuf::from(SPFS_DIR).join(extra_mount.dest.clone())
1641+
};
1642+
let flags = nix::mount::MntFlags::empty();
1643+
let result = nix::mount::umount2(&dest, flags);
1644+
if let Err(err) = result {
1645+
return Err(Error::wrap_nix(err, format!("Failed to unmount {dest:?}")));
1646+
}
1647+
}
1648+
}
1649+
1650+
Ok(())
1651+
}
1652+
15401653
/// Prevent a structure from being [`Send`].
15411654
struct NotSendMarker(std::marker::PhantomData<*mut u8>);
15421655

0 commit comments

Comments
 (0)