use crate::{flags::OpenFlags, syscalls, utils::FdExt};
use std::{
fs::File,
os::unix::io::{AsFd, AsRawFd},
path::{Path, PathBuf},
};
use anyhow::{bail, Context, Error};
use rustix::{
mount::{self as rustix_mount, MountFlags, MountPropagationFlags},
thread::{self as rustix_thread, LinkNameSpaceType, UnshareFlags},
};
#[derive(Debug, Clone)]
pub(crate) enum MountType {
Tmpfs,
Bind { src: PathBuf },
RebindWithFlags { flags: MountFlags },
}
pub(in crate::tests) const NOSYMFOLLOW: MountFlags = MountFlags::from_bits_retain(0x100);
fn are_vfs_flags(flags: MountFlags) -> bool {
flags
.difference(
MountFlags::RDONLY |
MountFlags::NOSUID | MountFlags::NODEV | MountFlags::NOEXEC | NOSYMFOLLOW |
MountFlags::NOATIME | MountFlags::NODIRATIME | MountFlags::RELATIME,
)
.is_empty()
}
pub(in crate::tests) fn mount(dst: impl AsRef<Path>, ty: MountType) -> Result<(), Error> {
let dst = dst.as_ref();
let dst_file = syscalls::openat(
syscalls::AT_FDCWD,
dst,
OpenFlags::O_NOFOLLOW | OpenFlags::O_PATH,
0,
)?;
let dst_path = format!("/proc/self/fd/{}", dst_file.as_raw_fd());
match ty {
MountType::Tmpfs => rustix_mount::mount("", &dst_path, "tmpfs", MountFlags::empty(), None)
.with_context(|| {
format!(
"mount tmpfs on {:?}",
dst_file
.as_unsafe_path_unchecked()
.unwrap_or(dst_path.into())
)
}),
MountType::Bind { src } => {
let src_file = syscalls::openat(
syscalls::AT_FDCWD,
src,
OpenFlags::O_NOFOLLOW | OpenFlags::O_PATH,
0,
)?;
let src_path = format!("/proc/self/fd/{}", src_file.as_raw_fd());
rustix_mount::mount_bind(&src_path, &dst_path).with_context(|| {
format!(
"bind-mount {:?} -> {:?}",
src_file
.as_unsafe_path_unchecked()
.unwrap_or(src_path.into()),
dst_file
.as_unsafe_path_unchecked()
.unwrap_or(dst_path.into())
)
})
}
MountType::RebindWithFlags { flags } => {
if !are_vfs_flags(flags) {
bail!("rebind-with-flags mount options {flags:?} contains non-vfsmount flags");
}
rustix_mount::mount_bind_recursive(&dst_path, &dst_path).with_context(|| {
format!(
"bind-mount {:?} to self",
dst_file
.as_unsafe_path_unchecked()
.unwrap_or(dst_path.clone().into())
)
})?;
let dst_file = syscalls::openat(
syscalls::AT_FDCWD,
dst,
OpenFlags::O_NOFOLLOW | OpenFlags::O_PATH,
0,
)?;
let dst_path = format!("/proc/self/fd/{}", dst_file.as_raw_fd());
rustix_mount::mount_remount(&dst_path, MountFlags::BIND | flags, "").with_context(
|| {
format!(
"vfs-remount {:?} with {flags:?}",
dst_file
.as_unsafe_path_unchecked()
.unwrap_or(dst_path.into())
)
},
)
}
}
}
pub(in crate::tests) fn in_mnt_ns<F, T>(func: F) -> Result<T, Error>
where
F: FnOnce() -> Result<T, Error>,
{
let old_ns = File::open("/proc/self/ns/mnt")?;
unsafe { rustix_thread::unshare_unsafe(UnshareFlags::FS | UnshareFlags::NEWNS) }
.expect("unable to create a mount namespace");
rustix_mount::mount_change(
"/",
MountPropagationFlags::DOWNSTREAM | MountPropagationFlags::REC,
)?;
let ret = func();
rustix_thread::move_into_link_name_space(old_ns.as_fd(), Some(LinkNameSpaceType::Mount))
.expect("unable to rejoin old namespace");
ret
}