use std::os::unix::io::{AsRawFd, OwnedFd, RawFd};
use crate::seccomp::notif::{write_child_mem, NotifAction};
use super::materialize::MaterializedMsg;
pub(crate) fn wants_blocking(fd: RawFd, send_flags: i32) -> bool {
if send_flags & libc::MSG_DONTWAIT != 0 {
return false;
}
let fl = unsafe { libc::fcntl(fd, libc::F_GETFL) };
fl >= 0 && (fl & libc::O_NONBLOCK) == 0
}
fn send_materialized_at(fd: RawFd, m: &MaterializedMsg, offset: usize, flags: i32) -> isize {
let iov = libc::iovec {
iov_base: unsafe { m.data.as_ptr().add(offset) } as *mut libc::c_void,
iov_len: m.data.len() - offset,
};
let mut msg: libc::msghdr = unsafe { std::mem::zeroed() };
if offset == 0 {
if !m.addr.is_empty() {
msg.msg_name = m.addr.as_ptr() as *mut libc::c_void;
msg.msg_namelen = m.addr.len() as u32;
}
if let Some(ref c) = m.control {
msg.msg_control = c.as_ptr() as *mut libc::c_void;
msg.msg_controllen = c.len();
}
}
msg.msg_iov = &iov as *const libc::iovec as *mut libc::iovec;
msg.msg_iovlen = 1;
unsafe { libc::sendmsg(fd, &msg, flags) }
}
pub(crate) fn resolve_send(dup_fd: OwnedFd, m: MaterializedMsg, flags: i32, child_blocking: bool) -> NotifAction {
let ret = send_materialized_at(dup_fd.as_raw_fd(), &m, 0, flags | libc::MSG_DONTWAIT);
if ret >= 0 {
let sent = ret as usize;
if !child_blocking || sent >= m.data.len() {
return NotifAction::ReturnValue(ret as i64);
}
return NotifAction::defer(defer_send(dup_fd, m, flags, sent));
}
let err = unsafe { *libc::__errno_location() };
if err == libc::EAGAIN || err == libc::EWOULDBLOCK {
if child_blocking {
return NotifAction::defer(defer_send(dup_fd, m, flags, 0));
}
return NotifAction::Errno(libc::EAGAIN);
}
NotifAction::Errno(err)
}
async fn push_until_done(
dup_fd: OwnedFd,
m: MaterializedMsg,
flags: i32,
mut offset: usize,
) -> Result<usize, i32> {
let afd = tokio::io::unix::AsyncFd::with_interest(dup_fd, tokio::io::Interest::WRITABLE)
.map_err(|_| libc::EIO)?;
loop {
let mut guard = afd.writable().await.map_err(|_| libc::EIO)?;
let ret = send_materialized_at(afd.get_ref().as_raw_fd(), &m, offset, flags | libc::MSG_DONTWAIT);
if ret >= 0 {
offset += ret as usize;
if offset >= m.data.len() {
return Ok(m.data.len());
}
continue;
}
let err = unsafe { *libc::__errno_location() };
if err == libc::EAGAIN || err == libc::EWOULDBLOCK {
guard.clear_ready();
continue;
}
return if offset > 0 { Ok(offset) } else { Err(err) };
}
}
async fn defer_send(dup_fd: OwnedFd, m: MaterializedMsg, flags: i32, offset: usize) -> NotifAction {
match push_until_done(dup_fd, m, flags, offset).await {
Ok(n) => NotifAction::ReturnValue(n as i64),
Err(e) => NotifAction::Errno(e),
}
}
fn complete_batch_entry(
dup_fd: OwnedFd,
m: MaterializedMsg,
flags: i32,
offset: usize,
notif_fd: RawFd,
notif_id: u64,
notif_pid: u32,
msglen_addr: u64,
prior_count: usize,
) -> NotifAction {
NotifAction::defer(async move {
match push_until_done(dup_fd, m, flags, offset).await {
Ok(n) => {
let bytes = (n as u32).to_ne_bytes();
let _ = write_child_mem(notif_fd, notif_id, notif_pid, msglen_addr, &bytes);
NotifAction::ReturnValue((prior_count + 1) as i64)
}
Err(e) => {
if prior_count == 0 {
NotifAction::Errno(e)
} else {
NotifAction::ReturnValue(prior_count as i64)
}
}
}
})
}
pub(crate) enum BatchStep {
Sent,
Done(NotifAction),
Stop(i32),
}
pub(crate) fn batch_send_step(
dup_fd: &OwnedFd,
m: MaterializedMsg,
flags: i32,
notif_fd: RawFd,
notif_id: u64,
notif_pid: u32,
msglen_addr: u64,
prior_count: usize,
) -> BatchStep {
let blocking = wants_blocking(dup_fd.as_raw_fd(), flags);
let ret = send_materialized_at(dup_fd.as_raw_fd(), &m, 0, flags | libc::MSG_DONTWAIT);
if ret >= 0 {
if blocking && (ret as usize) < m.data.len() {
let dup = match dup_fd.try_clone() {
Ok(d) => d,
Err(_) => {
let bytes = (ret as u32).to_ne_bytes();
let _ = write_child_mem(notif_fd, notif_id, notif_pid, msglen_addr, &bytes);
return BatchStep::Sent;
}
};
return BatchStep::Done(complete_batch_entry(
dup, m, flags, ret as usize, notif_fd, notif_id, notif_pid, msglen_addr,
prior_count,
));
}
let bytes = (ret as u32).to_ne_bytes();
let _ = write_child_mem(notif_fd, notif_id, notif_pid, msglen_addr, &bytes);
return BatchStep::Sent;
}
let err = unsafe { *libc::__errno_location() };
if err == libc::EAGAIN || err == libc::EWOULDBLOCK {
if prior_count == 0 && blocking {
let dup = match dup_fd.try_clone() {
Ok(d) => d,
Err(_) => return BatchStep::Stop(libc::EIO),
};
return BatchStep::Done(complete_batch_entry(
dup, m, flags, 0, notif_fd, notif_id, notif_pid, msglen_addr, 0,
));
}
return BatchStep::Stop(libc::EAGAIN);
}
BatchStep::Stop(err)
}