tcp: override SchemeSocket::call to implement SocketCall::SendMsg + MSG_NOSIGNAL

Adds a call() override for TcpSocket that handles SocketCall::SendMsg.
The default trait impl returned EOPNOTSUPP, which meant TCP sendmsg()
with MSG_NOSIGNAL would fail even for perfectly valid traffic.

TCP send() returns EPIPE on a peer-closed connection, which triggers
SIGPIPE. MSG_NOSIGNAL emulation:
1. Block SIGPIPE for the duration of send_slice()
2. If send returned < 0 (likely EPIPE), dequeue any newly-pending
   SIGPIPE via sigtimedwait(0) before restoring the old mask
3. Restore the caller's signal mask
4. If pthread_sigmask failed, fall through to plain send
This commit is contained in:
Red Bear OS
2026-07-27 20:39:16 +09:00
parent dd0127b635
commit cf21752385
+57
View File
@@ -44,6 +44,63 @@ impl<'a> SchemeSocket for TcpSocket<'a> {
self.can_send()
}
fn call(
&mut self,
_file: &mut SchemeFile<Self>,
payload: &mut [u8],
metadata: &[u64],
_ctx: &CallerCtx,
) -> SyscallResult<usize> {
match SocketCall::from(metadata[0]) {
SocketCall::SendMsg => {
let flags = metadata[1] as u16;
// smoltcp's TCP socket has no MSG_NOSIGNAL awareness: a
// peer-closed connection returns EPIPE on the send path
// and the kernel delivers SIGPIPE to the calling thread.
// We emulate POSIX MSG_NOSIGNAL by suppressing SIGPIPE
// for the duration of this send only. The block is
// restored before returning, so any newly-pending SIGPIPE
// (delivered between the signal mask save and restore) is
// dequeued via sigtimedwait(..., NULL) before we resume
// the caller's signal mask.
if flags & libc::MSG_NOSIGNAL as u16 != 0 {
let mut old_mask: libc::sigset_t = unsafe { std::mem::zeroed() };
let mut block_mask: libc::sigset_t = unsafe { std::mem::zeroed() };
unsafe { libc::sigemptyset(&mut block_mask) };
unsafe { libc::sigaddset(&mut block_mask, libc::SIGPIPE) };
if unsafe { libc::pthread_sigmask(libc::SIG_BLOCK, &block_mask, &mut old_mask) } == 0 {
let written = self.send_slice(payload);
if written < 0 {
self.close();
let mut ts: libc::timespec =
unsafe { std::mem::zeroed() };
ts.tv_sec = 0;
ts.tv_nsec = 0;
unsafe {
let _ = libc::sigtimedwait(
&block_mask,
std::ptr::null_mut(),
&ts,
);
}
}
unsafe {
libc::pthread_sigmask(libc::SIG_SETMASK, &old_mask, std::ptr::null_mut());
}
Ok(written.max(0) as usize)
} else {
// pthread_sigmask failed; fall through to plain
// send without suppression.
Ok(self.send_slice(payload).max(0) as usize)
}
} else {
Ok(self.send_slice(payload).max(0) as usize)
}
}
_ => Err(SyscallError::new(syscall::EOPNOTSUPP)),
}
}
fn can_recv(&mut self, _data: &Self::DataT) -> bool {
smoltcp::socket::tcp::Socket::can_recv(self)
}