use std::{
ffi::CString,
os::{fd::AsFd, unix::ffi::OsStrExt},
};
use nix::{errno::Errno, fcntl::AtFlags, mount::MsFlags, NixPath};
use crate::{
error,
fd::AT_BADFD,
info,
mount::api::{
fsconfig, fsmount, fsopen, mount_setattr, move_mount, open_tree, FsConfigCmd, FsMountFlags,
FsOpenFlags, MountAttr, MountAttrFlags, MoveMountFlags, OpenTreeFlags, AT_RECURSIVE,
},
retry::retry_on_eintr,
};
pub fn mount_fs<Fd, P>(
fsname: &P,
dst: Fd,
flags: MountAttrFlags,
opts: Option<&str>,
) -> Result<(), Errno>
where
Fd: AsFd,
P: ?Sized + NixPath + OsStrExt,
{
let ctx = retry_on_eintr(|| fsopen(fsname, FsOpenFlags::FSOPEN_CLOEXEC))?;
fsname.with_nix_path(|cstr| {
fsconfig(
&ctx,
FsConfigCmd::SetString,
Some("source"),
Some(cstr.to_bytes_with_nul()),
0,
)
})??;
if let Some(opts) = opts {
for opt in opts.split(',') {
if opt.is_empty() {
continue; }
let (key, val) = if let Some((key, val)) = opt.split_once('=') {
let val = CString::new(val)
.or(Err(Errno::EINVAL))?
.into_bytes_with_nul();
(key, Some(val))
} else {
(opt, None)
};
let cmd = if val.is_none() {
FsConfigCmd::SetFlag
} else {
FsConfigCmd::SetString
};
retry_on_eintr(|| fsconfig(&ctx, cmd, Some(key), val.as_deref(), 0))?;
}
}
retry_on_eintr(|| {
fsconfig(
&ctx,
FsConfigCmd::CmdCreate,
None::<&[u8]>,
None::<&[u8]>,
0,
)
})?;
retry_on_eintr(|| fsmount(&ctx, FsMountFlags::FSMOUNT_CLOEXEC, flags)).and_then(|mnt| {
retry_on_eintr(|| {
move_mount(
&mnt,
c"",
&dst,
c"",
MoveMountFlags::MOVE_MOUNT_F_EMPTY_PATH | MoveMountFlags::MOVE_MOUNT_T_EMPTY_PATH,
)
})
})
}
pub fn mount_bind<Fd1, Fd2>(src: Fd1, dst: Fd2, flags: MountAttrFlags) -> Result<(), Errno>
where
Fd1: AsFd,
Fd2: AsFd,
{
let clr_flags = mountattr_fixup(flags);
let attr = MountAttr {
attr_set: flags.bits().into(),
attr_clr: clr_flags.bits().into(),
propagation: 0,
userns_fd: 0,
};
let src = retry_on_eintr(|| {
open_tree(
&src,
c"",
OpenTreeFlags::OPEN_TREE_CLOEXEC
| OpenTreeFlags::OPEN_TREE_CLONE
| OpenTreeFlags::AT_EMPTY_PATH
| OpenTreeFlags::AT_RECURSIVE,
)
})?;
retry_on_eintr(|| mount_setattr(&src, c"", AtFlags::AT_EMPTY_PATH, attr))?;
retry_on_eintr(|| {
move_mount(
&src,
c"",
&dst,
c"",
MoveMountFlags::MOVE_MOUNT_F_EMPTY_PATH | MoveMountFlags::MOVE_MOUNT_T_EMPTY_PATH,
)
})
}
pub fn set_root_mount_propagation(proptype: MsFlags) -> Result<(), Errno> {
#[expect(clippy::useless_conversion)]
let attr = MountAttr {
attr_set: 0,
attr_clr: 0,
propagation: proptype.bits().into(),
userns_fd: 0,
};
retry_on_eintr(|| open_tree(AT_BADFD, "/", OpenTreeFlags::OPEN_TREE_CLOEXEC))
.and_then(|fd| {
retry_on_eintr(|| mount_setattr(&fd, c"", AtFlags::AT_EMPTY_PATH | AT_RECURSIVE, attr))
})
.inspect(|_| {
let propname = propagation_name(proptype);
info!("ctx": "run", "op": "set_root_mount_propagation",
"type": propname, "bits": proptype.bits(),
"msg": format!("set root mount propagation type to {propname}"));
})
.inspect_err(|errno| {
let propname = propagation_name(proptype);
error!("ctx": "run", "op": "set_root_mount_propagation",
"type": propname, "bits": proptype.bits(), "err": *errno as i32,
"msg": format!("set root mount propagation type to {propname} failed: {errno}"));
})
}
const fn propagation_name(proptype: MsFlags) -> &'static str {
match proptype {
MsFlags::MS_SHARED => "shared",
MsFlags::MS_SLAVE => "slave",
MsFlags::MS_PRIVATE => "private",
MsFlags::MS_UNBINDABLE => "unbindable",
_ => "unknown",
}
}
fn mountattr_fixup(flags: MountAttrFlags) -> MountAttrFlags {
if flags.intersects(MountAttrFlags::MOUNT_ATTR__ATIME) {
MountAttrFlags::MOUNT_ATTR__ATIME
} else {
MountAttrFlags::empty()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::mount::api::MountAttrFlags;
#[test]
fn test_mountattr_fixup_1() {
let result = mountattr_fixup(MountAttrFlags::empty());
assert!(result.is_empty());
}
#[test]
fn test_mountattr_fixup_2() {
let result = mountattr_fixup(MountAttrFlags::MOUNT_ATTR__ATIME);
assert_eq!(result, MountAttrFlags::MOUNT_ATTR__ATIME);
}
#[test]
fn test_mountattr_fixup_3() {
let result = mountattr_fixup(MountAttrFlags::MOUNT_ATTR_RDONLY);
assert!(result.is_empty());
}
#[test]
fn test_mountattr_fixup_4() {
let flags = MountAttrFlags::MOUNT_ATTR__ATIME | MountAttrFlags::MOUNT_ATTR_RDONLY;
let result = mountattr_fixup(flags);
assert_eq!(result, MountAttrFlags::MOUNT_ATTR__ATIME);
}
#[test]
fn test_propagation_name_1() {
assert_eq!(propagation_name(MsFlags::MS_SHARED), "shared");
}
#[test]
fn test_propagation_name_2() {
assert_eq!(propagation_name(MsFlags::MS_SLAVE), "slave");
}
#[test]
fn test_propagation_name_3() {
assert_eq!(propagation_name(MsFlags::MS_PRIVATE), "private");
}
#[test]
fn test_propagation_name_4() {
assert_eq!(propagation_name(MsFlags::MS_UNBINDABLE), "unbindable");
}
#[test]
fn test_propagation_name_5() {
assert_eq!(propagation_name(MsFlags::MS_RDONLY), "unknown");
}
}