use std::os::fd::AsFd;
use bitflags::bitflags;
use libseccomp::ScmpNotifResp;
use nix::{
errno::Errno,
sys::socket::{SockFlag, SockaddrLike},
};
use crate::{
compat::{getsockdomain, sockaddr_family, AddressFamily},
cookie::{safe_accept4, SizedSockaddrStorage},
fd::{fd_inode, get_nonblock, has_recv_timeout, SafeOwnedFd},
ip::SocketCall,
kernel::net::sandbox_addr,
req::UNotifyEventRequest,
sandbox::Capability,
unix::unix_addr_len,
};
bitflags! {
#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
struct Accept4Flags: u8 {
const WANT_ADDR = 1 << 0; const INTERRUPT = 1 << 1; const IGNORERES = 1 << 2; }
}
pub(crate) fn handle_accept(
fd: SafeOwnedFd,
request: &UNotifyEventRequest,
subcall: SocketCall,
args: &[u64; 6],
) -> Result<ScmpNotifResp, Errno> {
let mut flags = if subcall == SocketCall::Accept4 {
#[expect(clippy::cast_possible_truncation)]
SockFlag::from_bits(args[3] as libc::c_int).ok_or(Errno::EINVAL)?
} else {
SockFlag::empty()
};
let sandbox = request.get_sandbox();
let force_cloexec = sandbox.flags.force_cloexec();
let force_rand_fd = sandbox.flags.force_rand_fd();
drop(sandbox); let cloexec = force_cloexec || flags.contains(SockFlag::SOCK_CLOEXEC);
flags.insert(SockFlag::SOCK_CLOEXEC);
let (is_blocking, ignore_restart) = if !get_nonblock(&fd)? {
let ignore_restart = has_recv_timeout(&fd)?;
(true, ignore_restart)
} else {
(false, false)
};
let mut aflags = Accept4Flags::empty();
if args[1] != 0 || matches!(getsockdomain(&fd)?, libc::AF_INET | libc::AF_INET6) {
aflags.insert(Accept4Flags::WANT_ADDR);
}
if is_blocking {
aflags.insert(Accept4Flags::INTERRUPT);
}
if ignore_restart {
aflags.insert(Accept4Flags::IGNORERES);
}
let (fd, maybe_addr) = do_accept4(fd, request, flags, aflags)?;
let addrlen = if args[1] != 0 && args[2] != 0 {
const SIZEOF_SOCKLEN_T: usize = size_of::<libc::socklen_t>();
let mut buf = [0u8; SIZEOF_SOCKLEN_T];
if request.read_mem(&mut buf, args[2], SIZEOF_SOCKLEN_T)? == SIZEOF_SOCKLEN_T {
let len = i32::from_ne_bytes(buf);
let len = libc::socklen_t::try_from(len).or(Err(Errno::EINVAL))?;
Some(len)
} else {
return Err(Errno::EFAULT);
}
} else if args[1] != 0 {
return Err(Errno::EFAULT);
} else {
None
};
if let Some(SizedSockaddrStorage { addr, .. }) = maybe_addr.as_ref().filter(|addr| {
matches!(
sockaddr_family(&addr.addr),
AddressFamily::Inet | AddressFamily::Inet6
)
}) {
let sandbox = request.get_sandbox();
sandbox_addr(
request,
&sandbox,
subcall,
addr,
&None,
Capability::empty(),
)?;
}
if let Some(addrlen) = addrlen {
#[expect(clippy::disallowed_methods)]
let SizedSockaddrStorage { addr, mut size } = maybe_addr.unwrap();
let addr = if addr
.as_unix_addr()
.map(|addr| addr.path().is_some())
.unwrap_or(false)
{
if let Ok(ino) = fd_inode(&fd) {
if let Ok(addr) = request.resolve_unix_peer(&addr, ino) {
size = addr.as_unix_addr().map_or(size, unix_addr_len);
addr
} else {
addr
}
} else {
addr
}
} else {
addr
};
let buf = (size as libc::socklen_t).to_ne_bytes();
request.write_mem_all(&buf, args[2])?;
let buf =
unsafe { std::slice::from_raw_parts(addr.as_ptr().cast::<u8>(), addr.len() as usize) };
let len = size.min(addrlen) as usize;
request.write_mem_all(&buf[..len], args[1])?;
}
request.send_fd(fd, cloexec, force_rand_fd)
}
fn do_accept4<Fd: AsFd>(
fd: Fd,
request: &UNotifyEventRequest,
flags: SockFlag,
aflags: Accept4Flags,
) -> Result<(SafeOwnedFd, Option<SizedSockaddrStorage>), Errno> {
if aflags.contains(Accept4Flags::INTERRUPT) {
request
.cache
.add_sys_block(request.scmpreq, aflags.contains(Accept4Flags::IGNORERES))?;
};
let result = safe_accept4(&fd, flags, aflags.contains(Accept4Flags::WANT_ADDR));
if aflags.contains(Accept4Flags::INTERRUPT) {
request.cache.del_sys_block(request.scmpreq.id)?;
}
result
}