use crate::soft_idmap::{HostGid, HostUid, Id};
use bitflags::bitflags;
use std::ffi::{CStr, CString};
use std::fs::File;
use std::io::{self, Error, Result};
use std::os::unix::io::{AsRawFd, BorrowedFd, RawFd};
use std::os::unix::prelude::FromRawFd;
fn check_retval<T: From<i8> + PartialEq>(t: T) -> Result<T> {
if t == T::from(-1_i8) {
Err(Error::last_os_error())
} else {
Ok(t)
}
}
pub struct OsFacts {
pub has_openat2: bool,
}
#[allow(clippy::new_without_default)]
impl OsFacts {
#[must_use]
pub fn new() -> Self {
let how: libc::open_how = unsafe { std::mem::zeroed() };
let cwd = CString::new(".").unwrap();
let fd = unsafe {
libc::syscall(
libc::SYS_openat2,
libc::AT_FDCWD,
cwd.as_ptr(),
std::ptr::addr_of!(how),
std::mem::size_of::<libc::open_how>(),
)
};
let has_openat2 = fd >= 0;
if has_openat2 {
unsafe {
libc::close(fd as libc::c_int);
}
}
Self { has_openat2 }
}
}
pub fn mount(source: Option<&str>, target: &str, fstype: Option<&str>, flags: u64) -> Result<()> {
let source = CString::new(source.unwrap_or("")).unwrap();
let source = source.as_ptr();
let target = CString::new(target).unwrap();
let target = target.as_ptr();
let fstype = CString::new(fstype.unwrap_or("")).unwrap();
let fstype = fstype.as_ptr();
check_retval(unsafe { libc::mount(source, target, fstype, flags, std::ptr::null()) })?;
Ok(())
}
pub fn umount2(target: &str, flags: i32) -> Result<()> {
let target = CString::new(target).unwrap();
let target = target.as_ptr();
check_retval(unsafe { libc::umount2(target, flags) })?;
Ok(())
}
pub fn fchdir(fd: RawFd) -> Result<()> {
check_retval(unsafe { libc::fchdir(fd) })?;
Ok(())
}
pub fn fchmod(fd: RawFd, mode: libc::mode_t) -> Result<()> {
check_retval(unsafe { libc::fchmod(fd, mode) })?;
Ok(())
}
pub fn fchmodat(dirfd: RawFd, pathname: String, mode: libc::mode_t, flags: i32) -> Result<()> {
let pathname =
CString::new(pathname).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
let pathname = pathname.as_ptr();
check_retval(unsafe { libc::fchmodat(dirfd, pathname, mode, flags) })?;
Ok(())
}
pub fn umask(mask: u32) -> u32 {
unsafe { libc::umask(mask) }
}
pub struct ScopedUmask {
umask: libc::mode_t,
}
impl ScopedUmask {
pub fn new(new_umask: u32) -> Self {
Self {
umask: umask(new_umask),
}
}
}
impl Drop for ScopedUmask {
fn drop(&mut self) {
umask(self.umask);
}
}
pub fn openat(dir: &impl AsRawFd, pathname: &CStr, flags: i32, mode: Option<u32>) -> Result<RawFd> {
let mode = u64::from(mode.unwrap_or(0));
check_retval(unsafe {
libc::openat(
dir.as_raw_fd(),
pathname.as_ptr(),
flags as libc::c_int,
mode,
)
})
}
pub fn do_open_relative_to(
dir: &impl AsRawFd,
pathname: &CStr,
flags: i32,
mode: Option<u32>,
) -> Result<RawFd> {
let mode = u64::from(mode.unwrap_or(0)) & 0o7777;
let mut how: libc::open_how = unsafe { std::mem::zeroed() };
how.resolve = libc::RESOLVE_IN_ROOT | libc::RESOLVE_NO_MAGICLINKS;
how.flags = flags as u64;
how.mode = mode;
check_retval(unsafe {
libc::syscall(
libc::SYS_openat2,
dir.as_raw_fd(),
pathname.as_ptr(),
std::ptr::addr_of!(how),
std::mem::size_of::<libc::open_how>(),
)
} as RawFd)
}
mod filehandle {
use crate::passthrough::file_handle::SerializableFileHandle;
use crate::util::other_io_error;
use std::convert::{TryFrom, TryInto};
use std::io;
const MAX_HANDLE_SZ: usize = 128;
#[derive(Clone, PartialOrd, Ord, PartialEq, Eq)]
#[repr(C)]
pub struct CFileHandle {
handle_bytes: libc::c_uint,
handle_type: libc::c_int,
f_handle: [u8; MAX_HANDLE_SZ],
}
impl Default for CFileHandle {
fn default() -> Self {
CFileHandle {
handle_bytes: MAX_HANDLE_SZ as libc::c_uint,
handle_type: 0,
f_handle: [0; MAX_HANDLE_SZ],
}
}
}
impl CFileHandle {
pub fn as_bytes(&self) -> &[u8] {
&self.f_handle[..(self.handle_bytes as usize)]
}
pub fn handle_type(&self) -> libc::c_int {
self.handle_type
}
}
impl TryFrom<&SerializableFileHandle> for CFileHandle {
type Error = io::Error;
fn try_from(sfh: &SerializableFileHandle) -> io::Result<Self> {
let sfh_bytes = sfh.as_bytes();
if sfh_bytes.len() > MAX_HANDLE_SZ {
return Err(other_io_error("File handle too long"));
}
let mut f_handle = [0u8; MAX_HANDLE_SZ];
f_handle[..sfh_bytes.len()].copy_from_slice(sfh_bytes);
Ok(CFileHandle {
handle_bytes: sfh_bytes.len().try_into().map_err(|err| {
other_io_error(format!(
"Handle size ({} bytes) too big: {err}",
sfh_bytes.len(),
))
})?,
#[allow(clippy::useless_conversion)]
handle_type: sfh.handle_type().try_into().map_err(|err| {
other_io_error(format!(
"Handle type (0x{:x}) too large: {err}",
sfh.handle_type(),
))
})?,
f_handle,
})
}
}
extern "C" {
pub fn name_to_handle_at(
dirfd: libc::c_int,
pathname: *const libc::c_char,
file_handle: *mut CFileHandle,
mount_id: *mut libc::c_int,
flags: libc::c_int,
) -> libc::c_int;
pub fn open_by_handle_at(
mount_fd: libc::c_int,
file_handle: *const CFileHandle,
flags: libc::c_int,
) -> libc::c_int;
}
}
pub use filehandle::CFileHandle;
pub fn name_to_handle_at(
dirfd: &impl AsRawFd,
pathname: &CStr,
file_handle: &mut CFileHandle,
mount_id: &mut libc::c_int,
flags: libc::c_int,
) -> Result<()> {
check_retval(unsafe {
filehandle::name_to_handle_at(
dirfd.as_raw_fd(),
pathname.as_ptr(),
file_handle,
mount_id,
flags,
)
})?;
Ok(())
}
pub fn open_by_handle_at(
mount_fd: &impl AsRawFd,
file_handle: &CFileHandle,
flags: libc::c_int,
) -> Result<File> {
let fd = check_retval(unsafe {
filehandle::open_by_handle_at(mount_fd.as_raw_fd(), file_handle, flags)
})?;
Ok(unsafe { File::from_raw_fd(fd) })
}
bitflags! {
pub struct WritevFlags: i32 {
const RWF_HIPRI = libc::RWF_HIPRI;
const RWF_DSYNC = libc::RWF_DSYNC;
const RWF_SYNC = libc::RWF_SYNC;
const RWF_APPEND = libc::RWF_APPEND;
const RWF_NOAPPEND = libc::RWF_NOAPPEND;
const RWF_ATOMIC = libc::RWF_ATOMIC;
const RWF_DONTCACHE = libc::RWF_DONTCACHE;
}
}
bitflags! {
pub struct ReadvFlags: i32 {
const RWF_HIPRI = libc::RWF_HIPRI;
const RWF_NOWAIT = libc::RWF_NOWAIT;
const RWF_DONTCACHE = libc::RWF_DONTCACHE;
}
}
pub unsafe fn writev_at(
fd: BorrowedFd,
iovecs: &[libc::iovec],
offset: i64,
flags: Option<WritevFlags>,
) -> Result<usize> {
let flags = flags.unwrap_or(WritevFlags::empty());
let bytes_written = check_retval(unsafe {
libc::pwritev2(
fd.as_raw_fd(),
iovecs.as_ptr(),
iovecs.len() as libc::c_int,
offset,
flags.bits(),
)
})?;
Ok(bytes_written as usize)
}
pub unsafe fn readv_at(
fd: BorrowedFd,
iovecs: &[libc::iovec],
offset: i64,
flags: Option<ReadvFlags>,
) -> Result<usize> {
let flags = flags.unwrap_or(ReadvFlags::empty());
let bytes_read = check_retval(unsafe {
libc::preadv2(
fd.as_raw_fd(),
iovecs.as_ptr(),
iovecs.len() as libc::c_int,
offset,
flags.bits(),
)
})?;
Ok(bytes_read as usize)
}
pub struct PipeReader(File);
impl io::Read for PipeReader {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
self.0.read(buf)
}
}
pub struct PipeWriter(File);
impl io::Write for PipeWriter {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.0.write(buf)
}
fn flush(&mut self) -> io::Result<()> {
self.0.flush()
}
}
pub fn pipe() -> io::Result<(PipeReader, PipeWriter)> {
let mut fds: [RawFd; 2] = [-1, -1];
let ret = unsafe { libc::pipe2(fds.as_mut_ptr(), libc::O_CLOEXEC) };
if ret == -1 {
Err(io::Error::last_os_error())
} else {
Ok((
PipeReader(unsafe { File::from_raw_fd(fds[0]) }),
PipeWriter(unsafe { File::from_raw_fd(fds[1]) }),
))
}
}
pub fn seteffuid(uid: HostUid) -> io::Result<()> {
check_retval(unsafe { libc::syscall(libc::SYS_setresuid, -1, uid.into_inner(), -1) })?;
Ok(())
}
pub fn seteffgid(gid: HostGid) -> io::Result<()> {
check_retval(unsafe { libc::syscall(libc::SYS_setresgid, -1, gid.into_inner(), -1) })?;
Ok(())
}
pub fn setsupgroup(gid: HostGid) -> io::Result<()> {
let gid_raw = gid.into_inner();
check_retval(unsafe { libc::syscall(libc::SYS_setgroups, 1, &gid_raw) })?;
Ok(())
}
pub fn dropsupgroups() -> io::Result<()> {
check_retval(unsafe {
libc::syscall(libc::SYS_setgroups, 0, std::ptr::null::<libc::gid_t>())
})?;
Ok(())
}