use std::{
io::Result,
os::fd::{AsFd, BorrowedFd, OwnedFd},
};
use rustix::{
mount::{
FsMountFlags, FsOpenFlags, MountAttrFlags, MoveMountFlags, fsconfig_create,
fsconfig_set_flag, fsconfig_set_string, fsmount, fsopen, move_mount,
},
path,
};
use crate::{
mountcompat::{
make_erofs_mountable, overlayfs_set_fd, overlayfs_set_lower_and_data_fds, prepare_mount,
},
util::proc_self_fd,
};
#[derive(Debug)]
pub struct FsHandle {
pub fd: OwnedFd,
}
impl FsHandle {
pub fn open(name: &str) -> Result<FsHandle> {
Ok(FsHandle {
fd: fsopen(name, FsOpenFlags::FSOPEN_CLOEXEC)?,
})
}
}
impl AsFd for FsHandle {
fn as_fd(&self) -> BorrowedFd<'_> {
self.fd.as_fd()
}
}
impl Drop for FsHandle {
fn drop(&mut self) {
let mut buffer = [0u8; 1024];
loop {
match rustix::io::read(&self.fd, &mut buffer) {
Err(_) => return, Ok(0) => return,
#[allow(clippy::print_stderr)]
Ok(size) => eprintln!("{}", String::from_utf8(buffer[0..size].to_vec()).unwrap()),
}
}
}
}
pub fn mount_at(
fs_fd: impl AsFd,
dirfd: impl AsFd,
path: impl path::Arg,
) -> rustix::io::Result<()> {
move_mount(
fs_fd.as_fd(),
"",
dirfd.as_fd(),
path,
MoveMountFlags::MOVE_MOUNT_F_EMPTY_PATH,
)
}
pub fn erofs_mount(image: OwnedFd) -> Result<OwnedFd> {
let image = make_erofs_mountable(image)?;
let erofs = FsHandle::open("erofs")?;
fsconfig_set_flag(erofs.as_fd(), "ro")?;
fsconfig_set_string(erofs.as_fd(), "source", proc_self_fd(&image))?;
fsconfig_create(erofs.as_fd())?;
Ok(fsmount(
erofs.as_fd(),
FsMountFlags::FSMOUNT_CLOEXEC,
MountAttrFlags::empty(),
)?)
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
pub enum VerityRequirement {
#[default]
Disabled,
Required,
Try,
}
#[derive(Debug, Default)]
#[non_exhaustive]
pub struct MountOptions {
upperdirs: Option<(OwnedFd, OwnedFd)>,
read_write: bool,
idmap_fd: Option<OwnedFd>,
}
impl MountOptions {
pub fn set_overlay(&mut self, upperdir: OwnedFd, workdir: OwnedFd) -> &mut Self {
self.upperdirs = Some((upperdir, workdir));
self
}
pub fn set_read_write(&mut self, read_write: bool) -> &mut Self {
self.read_write = read_write;
self
}
pub fn set_idmap(&mut self, fd: OwnedFd) -> &mut Self {
self.idmap_fd = Some(fd);
self
}
pub fn has_overlay(&self) -> bool {
self.upperdirs.is_some()
}
pub fn read_write(&self) -> bool {
self.read_write
}
pub fn into_overlay(self) -> Option<(OwnedFd, OwnedFd)> {
self.upperdirs
}
}
pub fn composefs_fsmount(
image: OwnedFd,
name: &str,
basedirs: &[BorrowedFd<'_>],
verity: VerityRequirement,
options: &MountOptions,
) -> Result<OwnedFd> {
let erofs_mnt = erofs_mount(image)?;
if let Some(idmap_fd) = &options.idmap_fd {
composefs_ioctls::mount::mount_setattr_idmap(erofs_mnt.as_fd(), idmap_fd.as_fd())?;
}
let erofs_mnt = prepare_mount(erofs_mnt)?;
let overlayfs = FsHandle::open("overlay")?;
fsconfig_set_string(overlayfs.as_fd(), "source", format!("composefs:{name}"))?;
fsconfig_set_string(overlayfs.as_fd(), "metacopy", "on")?;
fsconfig_set_string(overlayfs.as_fd(), "redirect_dir", "on")?;
match verity {
VerityRequirement::Disabled => {}
VerityRequirement::Required => {
fsconfig_set_string(overlayfs.as_fd(), "verity", "require")?;
}
VerityRequirement::Try => {
match fsconfig_set_string(overlayfs.as_fd(), "verity", "require") {
Ok(()) => {}
Err(rustix::io::Errno::INVAL) | Err(rustix::io::Errno::NOSYS) => {}
Err(e) => return Err(e.into()),
}
}
}
if let Some((upperdir, workdir)) = &options.upperdirs {
overlayfs_set_fd(overlayfs.as_fd(), "upperdir", upperdir.as_fd())?;
overlayfs_set_fd(overlayfs.as_fd(), "workdir", workdir.as_fd())?;
}
overlayfs_set_lower_and_data_fds(&overlayfs, &erofs_mnt, basedirs)?;
fsconfig_create(overlayfs.as_fd())?;
let mount_attr = if options.read_write {
MountAttrFlags::empty()
} else {
MountAttrFlags::MOUNT_ATTR_RDONLY
};
Ok(fsmount(
overlayfs.as_fd(),
FsMountFlags::FSMOUNT_CLOEXEC,
mount_attr,
)?)
}