use std::fs::File;
use std::io::{self, Write};
use std::mem::MaybeUninit;
use std::os::fd::{AsRawFd, BorrowedFd, OwnedFd};
use std::os::unix::process::CommandExt;
use std::path::Path;
use std::process::Command;
use rustix::io::FdFlags;
use rustix::mount::{MountFlags, UnmountFlags, mount, unmount};
use rustix::net::{
AddressFamily, RecvAncillaryBuffer, RecvAncillaryMessage, RecvFlags, SocketFlags, SocketType,
recvmsg, socketpair,
};
use rustix::thread::{UnshareFlags, unshare_unsafe};
pub fn enter_namespace() -> io::Result<()> {
let real_uid = rustix::process::getuid().as_raw();
let real_gid = rustix::process::getgid().as_raw();
unsafe {
unshare_unsafe(UnshareFlags::NEWUSER | UnshareFlags::NEWNS)
.map_err(|e| io::Error::other(format!("unshare: {e}")))?;
}
File::create("/proc/self/setgroups")?.write_all(b"deny")?;
File::create("/proc/self/uid_map")?.write_all(format!("{real_uid} {real_uid} 1").as_bytes())?;
File::create("/proc/self/gid_map")?.write_all(format!("{real_gid} {real_gid} 1").as_bytes())?;
Ok(())
}
pub fn fuse_mount_unshare(mountpoint: &Path) -> io::Result<OwnedFd> {
enter_namespace()?;
let real_uid = rustix::process::getuid().as_raw();
let real_gid = rustix::process::getgid().as_raw();
let fuse_fd: OwnedFd = std::fs::OpenOptions::new()
.read(true)
.write(true)
.open("/dev/fuse")?
.into();
let data = format!(
"fd={},rootmode=40000,user_id={real_uid},group_id={real_gid}",
fuse_fd.as_raw_fd()
);
let data_c = std::ffi::CString::new(data).unwrap();
mount(
"fuse",
mountpoint,
"fuse",
MountFlags::NOSUID | MountFlags::NODEV | MountFlags::NOATIME | MountFlags::RDONLY,
Some(data_c.as_c_str()),
)
.map_err(|e| io::Error::other(format!("mount /dev/fuse: {e}")))?;
Ok(fuse_fd)
}
pub fn mount_tmpfs(mountpoint: &Path, size_bytes: u64) -> io::Result<()> {
let data = format!("size={size_bytes},mode=0755");
let data_c = std::ffi::CString::new(data).unwrap();
mount(
"tmpfs",
mountpoint,
"tmpfs",
MountFlags::NOSUID | MountFlags::NODEV,
Some(data_c.as_c_str()),
)
.map_err(|e| io::Error::other(format!("mount tmpfs: {e}")))
}
pub fn fuse_unmount_direct(mountpoint: &Path) {
let _ = unmount(mountpoint, UnmountFlags::DETACH);
}
pub fn sweep_stale_mountpoints() {
let Some(base) = crate::paths::private_dir() else {
return;
};
let Ok(entries) = std::fs::read_dir(&base) else {
return;
};
for entry in entries.flatten() {
let name = entry.file_name();
let Some(s) = name.to_str() else { continue };
if !s.starts_with("onelf-") {
continue;
}
let path = entry.path();
if !path.is_dir() {
continue;
}
let _ = std::fs::remove_dir(&path);
}
}
pub fn fuse_mount(mountpoint: &Path) -> io::Result<OwnedFd> {
let (sock_parent, sock_child) = socketpair(
AddressFamily::UNIX,
SocketType::STREAM,
SocketFlags::CLOEXEC,
None,
)
.map_err(|e| io::Error::new(io::ErrorKind::Other, format!("socketpair: {e}")))?;
let child_fd = sock_child.as_raw_fd();
let status = unsafe {
Command::new("fusermount3")
.args(["-o", "ro,nosuid,nodev,noatime,default_permissions", "--"])
.arg(mountpoint)
.env("_FUSE_COMMFD", child_fd.to_string())
.pre_exec(move || {
let fd = BorrowedFd::borrow_raw(child_fd);
let flags = rustix::io::fcntl_getfd(fd)
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
rustix::io::fcntl_setfd(fd, flags.difference(FdFlags::CLOEXEC))
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
Ok(())
})
.status()
}
.map_err(|e| io::Error::new(io::ErrorKind::NotFound, format!("fusermount3: {e}")))?;
drop(sock_child);
if !status.success() {
return Err(io::Error::new(
io::ErrorKind::Other,
format!("fusermount3 exited with {status}"),
));
}
let mut cmsg_buf = [MaybeUninit::<u8>::uninit(); rustix::cmsg_space!(ScmRights(1))];
let mut ancillary = RecvAncillaryBuffer::new(&mut cmsg_buf);
let mut iov_buf = [0u8; 1];
let iov = io::IoSliceMut::new(&mut iov_buf);
let _msg = recvmsg(&sock_parent, &mut [iov], &mut ancillary, RecvFlags::empty())
.map_err(|e| io::Error::new(io::ErrorKind::Other, format!("recvmsg: {e}")))?;
for msg in ancillary.drain() {
if let RecvAncillaryMessage::ScmRights(fds) = msg {
for fd in fds {
return Ok(fd);
}
}
}
Err(io::Error::new(
io::ErrorKind::Other,
"fusermount3 did not send /dev/fuse fd",
))
}
pub fn fuse_unmount(mountpoint: &Path) {
let _ = Command::new("fusermount3")
.args(["-u", "-z", "-q", "--"])
.arg(mountpoint)
.status();
}
pub fn fusermount3_available() -> bool {
Command::new("fusermount3")
.arg("--version")
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::null())
.status()
.is_ok()
}