mirror of
https://github.com/jart/cosmopolitan.git
synced 2025-03-16 05:46:25 +00:00
Emulate Linux socket timeout signaling on Windows
This commit is contained in:
parent
65e425fbca
commit
b14dddcc18
8 changed files with 246 additions and 29 deletions
|
@ -48,40 +48,34 @@ textwindows int sys_getsockopt_nt(struct Fd *fd, int level, int optname,
|
||||||
}
|
}
|
||||||
|
|
||||||
if (level == SOL_SOCKET && optname == SO_ERROR) {
|
if (level == SOL_SOCKET && optname == SO_ERROR) {
|
||||||
if (in_optlen >= sizeof(int)) {
|
if (in_optlen < sizeof(int))
|
||||||
int err;
|
|
||||||
uint32_t len = sizeof(err);
|
|
||||||
if (__imp_getsockopt(fd->handle, SOL_SOCKET, SO_ERROR, &err, &len) == -1)
|
|
||||||
return __winsockerr();
|
|
||||||
*(int *)out_opt_optval = __dos2errno(err);
|
|
||||||
*inout_optlen = sizeof(int);
|
|
||||||
} else {
|
|
||||||
return einval();
|
return einval();
|
||||||
}
|
int err;
|
||||||
|
uint32_t len = sizeof(err);
|
||||||
|
if (__imp_getsockopt(fd->handle, SOL_SOCKET, SO_ERROR, &err, &len) == -1)
|
||||||
|
return __winsockerr();
|
||||||
|
*(int *)out_opt_optval = __dos2errno(err);
|
||||||
|
*inout_optlen = sizeof(int);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (level == SOL_SOCKET &&
|
if (level == SOL_SOCKET &&
|
||||||
(optname == SO_RCVTIMEO || optname == SO_SNDTIMEO)) {
|
(optname == SO_RCVTIMEO || optname == SO_SNDTIMEO)) {
|
||||||
if (in_optlen >= sizeof(struct timeval)) {
|
if (in_optlen < sizeof(struct timeval))
|
||||||
if (optname == SO_RCVTIMEO) {
|
|
||||||
ms = fd->rcvtimeo;
|
|
||||||
} else {
|
|
||||||
ms = fd->sndtimeo;
|
|
||||||
}
|
|
||||||
((struct timeval *)out_opt_optval)->tv_sec = ms / 1000;
|
|
||||||
((struct timeval *)out_opt_optval)->tv_usec = ms % 1000 * 1000;
|
|
||||||
*inout_optlen = sizeof(struct timeval);
|
|
||||||
return 0;
|
|
||||||
} else {
|
|
||||||
return einval();
|
return einval();
|
||||||
|
if (optname == SO_RCVTIMEO) {
|
||||||
|
ms = fd->rcvtimeo;
|
||||||
|
} else {
|
||||||
|
ms = fd->sndtimeo;
|
||||||
}
|
}
|
||||||
|
*(struct timeval *)out_opt_optval = timeval_frommillis(ms);
|
||||||
|
*inout_optlen = sizeof(struct timeval);
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(jart): Use WSAIoctl?
|
// TODO(jart): Use WSAIoctl?
|
||||||
if (__imp_getsockopt(fd->handle, level, optname, out_opt_optval,
|
if (__imp_getsockopt(fd->handle, level, optname, out_opt_optval,
|
||||||
inout_optlen) == -1) {
|
inout_optlen) == -1)
|
||||||
return __winsockerr();
|
return __winsockerr();
|
||||||
}
|
|
||||||
|
|
||||||
if (level == SOL_SOCKET) {
|
if (level == SOL_SOCKET) {
|
||||||
if (optname == SO_LINGER && in_optlen == sizeof(struct linger)) {
|
if (optname == SO_LINGER && in_optlen == sizeof(struct linger)) {
|
||||||
|
|
|
@ -38,7 +38,7 @@
|
||||||
* EPIPE (if MSG_NOSIGNAL), EMSGSIZE, ENOTSOCK, EFAULT, etc.
|
* EPIPE (if MSG_NOSIGNAL), EMSGSIZE, ENOTSOCK, EFAULT, etc.
|
||||||
* @cancelationpoint
|
* @cancelationpoint
|
||||||
* @asyncsignalsafe
|
* @asyncsignalsafe
|
||||||
* @restartable (unless SO_RCVTIMEO)
|
* @restartable (unless SO_RCVTIMEO on Linux or Windows)
|
||||||
*/
|
*/
|
||||||
ssize_t recv(int fd, void *buf, size_t size, int flags) {
|
ssize_t recv(int fd, void *buf, size_t size, int flags) {
|
||||||
ssize_t rc;
|
ssize_t rc;
|
||||||
|
|
|
@ -49,7 +49,7 @@
|
||||||
* EPIPE (if MSG_NOSIGNAL), EMSGSIZE, ENOTSOCK, EFAULT, etc.
|
* EPIPE (if MSG_NOSIGNAL), EMSGSIZE, ENOTSOCK, EFAULT, etc.
|
||||||
* @cancelationpoint
|
* @cancelationpoint
|
||||||
* @asyncsignalsafe
|
* @asyncsignalsafe
|
||||||
* @restartable (unless SO_RCVTIMEO)
|
* @restartable (unless SO_RCVTIMEO on Linux or Windows)
|
||||||
*/
|
*/
|
||||||
ssize_t recvfrom(int fd, void *buf, size_t size, int flags,
|
ssize_t recvfrom(int fd, void *buf, size_t size, int flags,
|
||||||
struct sockaddr *opt_out_srcaddr,
|
struct sockaddr *opt_out_srcaddr,
|
||||||
|
|
|
@ -51,7 +51,7 @@
|
||||||
* EPIPE (if MSG_NOSIGNAL), EMSGSIZE, ENOTSOCK, EFAULT, etc.
|
* EPIPE (if MSG_NOSIGNAL), EMSGSIZE, ENOTSOCK, EFAULT, etc.
|
||||||
* @cancelationpoint
|
* @cancelationpoint
|
||||||
* @asyncsignalsafe
|
* @asyncsignalsafe
|
||||||
* @restartable (unless SO_RCVTIMEO)
|
* @restartable (unless SO_SNDTIMEO on Linux or Windows)
|
||||||
*/
|
*/
|
||||||
ssize_t send(int fd, const void *buf, size_t size, int flags) {
|
ssize_t send(int fd, const void *buf, size_t size, int flags) {
|
||||||
ssize_t rc;
|
ssize_t rc;
|
||||||
|
|
|
@ -51,7 +51,7 @@
|
||||||
* EPIPE (if MSG_NOSIGNAL), EMSGSIZE, ENOTSOCK, EFAULT, etc.
|
* EPIPE (if MSG_NOSIGNAL), EMSGSIZE, ENOTSOCK, EFAULT, etc.
|
||||||
* @cancelationpoint
|
* @cancelationpoint
|
||||||
* @asyncsignalsafe
|
* @asyncsignalsafe
|
||||||
* @restartable (unless SO_RCVTIMEO)
|
* @restartable (unless SO_SNDTIMEO on Linux or Windows)
|
||||||
*/
|
*/
|
||||||
ssize_t sendto(int fd, const void *buf, size_t size, int flags,
|
ssize_t sendto(int fd, const void *buf, size_t size, int flags,
|
||||||
const struct sockaddr *opt_addr, uint32_t addrsize) {
|
const struct sockaddr *opt_addr, uint32_t addrsize) {
|
||||||
|
|
|
@ -36,14 +36,17 @@ textwindows int sys_setsockopt_nt(struct Fd *fd, int level, int optname,
|
||||||
const void *optval, uint32_t optlen) {
|
const void *optval, uint32_t optlen) {
|
||||||
|
|
||||||
// socket read/write timeouts
|
// socket read/write timeouts
|
||||||
|
// timeout of zero means wait forever (default)
|
||||||
if (level == SOL_SOCKET &&
|
if (level == SOL_SOCKET &&
|
||||||
(optname == SO_RCVTIMEO || optname == SO_SNDTIMEO)) {
|
(optname == SO_RCVTIMEO || optname == SO_SNDTIMEO)) {
|
||||||
if (!(optval && optlen == sizeof(struct timeval)))
|
if (!optval)
|
||||||
|
return einval();
|
||||||
|
if (optlen < sizeof(struct timeval))
|
||||||
return einval();
|
return einval();
|
||||||
const struct timeval *tv = optval;
|
const struct timeval *tv = optval;
|
||||||
int64_t ms = timeval_tomillis(*tv);
|
int64_t ms = timeval_tomillis(*tv);
|
||||||
if (ms > -1u)
|
if (ms > -1u)
|
||||||
ms = 0; // wait forever (default) yes zero actually means this
|
ms = -1u;
|
||||||
if (optname == SO_RCVTIMEO)
|
if (optname == SO_RCVTIMEO)
|
||||||
fd->rcvtimeo = ms;
|
fd->rcvtimeo = ms;
|
||||||
if (optname == SO_SNDTIMEO)
|
if (optname == SO_SNDTIMEO)
|
||||||
|
@ -51,7 +54,7 @@ textwindows int sys_setsockopt_nt(struct Fd *fd, int level, int optname,
|
||||||
return 0; // we want to handle this on our own
|
return 0; // we want to handle this on our own
|
||||||
}
|
}
|
||||||
|
|
||||||
// how to make close() a blocking i/o call
|
// how to make close() a blocking i/o call lool
|
||||||
union {
|
union {
|
||||||
uint32_t millis;
|
uint32_t millis;
|
||||||
struct linger_nt linger;
|
struct linger_nt linger;
|
||||||
|
|
|
@ -191,6 +191,11 @@ __winsock_block(int64_t handle, uint32_t flags, int nonblock,
|
||||||
// check if signal handler without SA_RESTART was called
|
// check if signal handler without SA_RESTART was called
|
||||||
if (handler_was_called & SIG_HANDLED_NO_RESTART)
|
if (handler_was_called & SIG_HANDLED_NO_RESTART)
|
||||||
return eintr();
|
return eintr();
|
||||||
|
|
||||||
|
// emulates linux behavior of having timeouts @norestart
|
||||||
|
if (handler_was_called & SIG_HANDLED_SA_RESTART)
|
||||||
|
if (srwtimeout)
|
||||||
|
return eintr();
|
||||||
}
|
}
|
||||||
|
|
||||||
// otherwise try the i/o operation again
|
// otherwise try the i/o operation again
|
||||||
|
|
215
test/posix/socket_timeout_signal_test.c
Normal file
215
test/posix/socket_timeout_signal_test.c
Normal file
|
@ -0,0 +1,215 @@
|
||||||
|
#include <arpa/inet.h>
|
||||||
|
#include <cosmo.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <netinet/in.h>
|
||||||
|
#include <pthread.h>
|
||||||
|
#include <signal.h>
|
||||||
|
#include <stdatomic.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <sys/time.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include "libc/limits.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @fileoverview SO_RCVTIMEO + SA_RESTART interaction test
|
||||||
|
*
|
||||||
|
* This code tests that setting a read timeout on a socket will cause
|
||||||
|
* read() to change its signal handling behavior from @restartable to
|
||||||
|
* @norestart. This is currently the case on GNU/Systemd and Windows.
|
||||||
|
*/
|
||||||
|
|
||||||
|
struct sockaddr_in serv_addr;
|
||||||
|
atomic_bool g_ready_for_conn;
|
||||||
|
atomic_bool g_ready_for_data;
|
||||||
|
atomic_bool g_ready_for_more;
|
||||||
|
atomic_bool g_ready_for_exit;
|
||||||
|
atomic_bool got_sigusr1;
|
||||||
|
atomic_bool got_sigusr2;
|
||||||
|
|
||||||
|
void on_sigusr1(int sig) {
|
||||||
|
got_sigusr1 = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void on_sigusr2(int sig) {
|
||||||
|
got_sigusr2 = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void *server_thread(void *arg) {
|
||||||
|
int server, client;
|
||||||
|
struct timeval timeout;
|
||||||
|
socklen_t len;
|
||||||
|
struct sockaddr_in cli_addr;
|
||||||
|
|
||||||
|
// create listening socket
|
||||||
|
server = socket(AF_INET, SOCK_STREAM, 0);
|
||||||
|
if (server == -1) {
|
||||||
|
perror("socket");
|
||||||
|
exit(31);
|
||||||
|
}
|
||||||
|
|
||||||
|
// initialize server address
|
||||||
|
memset(&serv_addr, 0, sizeof(serv_addr));
|
||||||
|
serv_addr.sin_family = AF_INET;
|
||||||
|
serv_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
|
||||||
|
serv_addr.sin_port = htons(0);
|
||||||
|
|
||||||
|
// bind socket
|
||||||
|
if (bind(server, (struct sockaddr *)&serv_addr, sizeof(serv_addr))) {
|
||||||
|
perror("bind");
|
||||||
|
exit(32);
|
||||||
|
}
|
||||||
|
|
||||||
|
// get assigned port
|
||||||
|
len = sizeof(serv_addr);
|
||||||
|
if (getsockname(server, (struct sockaddr *)&serv_addr, &len)) {
|
||||||
|
perror("getsockname");
|
||||||
|
exit(30);
|
||||||
|
}
|
||||||
|
|
||||||
|
// listen on the socket
|
||||||
|
if (listen(server, SOMAXCONN)) {
|
||||||
|
perror("listen");
|
||||||
|
exit(33);
|
||||||
|
}
|
||||||
|
|
||||||
|
// wake main thread
|
||||||
|
g_ready_for_conn = true;
|
||||||
|
|
||||||
|
// accept connection
|
||||||
|
len = sizeof(cli_addr);
|
||||||
|
client = accept(server, (struct sockaddr *)&cli_addr, &len);
|
||||||
|
if (client == -1) {
|
||||||
|
perror("accept");
|
||||||
|
exit(35);
|
||||||
|
}
|
||||||
|
|
||||||
|
// wake main thread
|
||||||
|
g_ready_for_data = true;
|
||||||
|
|
||||||
|
// check read() has @restartable behavior
|
||||||
|
char buf[1];
|
||||||
|
int rc = read(client, buf, 1);
|
||||||
|
if (rc != -1)
|
||||||
|
exit(35);
|
||||||
|
if (errno != EINTR)
|
||||||
|
exit(36);
|
||||||
|
if (!got_sigusr1)
|
||||||
|
exit(37);
|
||||||
|
if (!got_sigusr2)
|
||||||
|
exit(38);
|
||||||
|
got_sigusr1 = false;
|
||||||
|
got_sigusr2 = false;
|
||||||
|
|
||||||
|
// install a socket receive timeout
|
||||||
|
timeout.tv_sec = 5000000;
|
||||||
|
timeout.tv_usec = 0;
|
||||||
|
if (setsockopt(client, SOL_SOCKET, SO_RCVTIMEO, &timeout,
|
||||||
|
sizeof(timeout) + !IsNetbsd())) {
|
||||||
|
perror("setsockopt");
|
||||||
|
exit(34);
|
||||||
|
}
|
||||||
|
|
||||||
|
// wake main thread
|
||||||
|
g_ready_for_more = true;
|
||||||
|
|
||||||
|
// check read() has @norestart behavior
|
||||||
|
rc = read(client, buf, 1);
|
||||||
|
if (rc != -1)
|
||||||
|
exit(35);
|
||||||
|
if (errno != EINTR)
|
||||||
|
exit(36);
|
||||||
|
if (!got_sigusr1)
|
||||||
|
exit(37);
|
||||||
|
|
||||||
|
// here's the whammy
|
||||||
|
if (IsLinux() || IsWindows()) {
|
||||||
|
if (got_sigusr2)
|
||||||
|
exit(38);
|
||||||
|
} else {
|
||||||
|
if (!got_sigusr2)
|
||||||
|
exit(38);
|
||||||
|
}
|
||||||
|
|
||||||
|
// wait for main thread
|
||||||
|
for (;;)
|
||||||
|
if (g_ready_for_exit)
|
||||||
|
break;
|
||||||
|
|
||||||
|
// close listening socket
|
||||||
|
if (close(server))
|
||||||
|
exit(40);
|
||||||
|
if (close(client))
|
||||||
|
exit(39);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
|
||||||
|
// handle signals
|
||||||
|
struct sigaction sa = {0};
|
||||||
|
sa.sa_handler = on_sigusr1;
|
||||||
|
sa.sa_flags = SA_RESTART;
|
||||||
|
sigaction(SIGUSR1, &sa, 0);
|
||||||
|
sa.sa_handler = on_sigusr2;
|
||||||
|
sa.sa_flags = 0;
|
||||||
|
sigaction(SIGUSR2, &sa, 0);
|
||||||
|
|
||||||
|
// create server thread
|
||||||
|
pthread_t th;
|
||||||
|
if (pthread_create(&th, 0, server_thread, 0))
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
// wait for thread
|
||||||
|
for (;;)
|
||||||
|
if (g_ready_for_conn)
|
||||||
|
break;
|
||||||
|
|
||||||
|
// create socket
|
||||||
|
int client = socket(AF_INET, SOCK_STREAM, 0);
|
||||||
|
if (client == -1) {
|
||||||
|
perror("socket");
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// connect to server
|
||||||
|
if (connect(client, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) == -1) {
|
||||||
|
perror("connect");
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
// wait for thread
|
||||||
|
for (;;)
|
||||||
|
if (g_ready_for_data)
|
||||||
|
break;
|
||||||
|
|
||||||
|
usleep(100e3);
|
||||||
|
if (pthread_kill(th, SIGUSR1))
|
||||||
|
return 4;
|
||||||
|
|
||||||
|
usleep(100e3);
|
||||||
|
if (pthread_kill(th, SIGUSR2))
|
||||||
|
return 5;
|
||||||
|
|
||||||
|
// wait for thread
|
||||||
|
for (;;)
|
||||||
|
if (g_ready_for_more)
|
||||||
|
break;
|
||||||
|
|
||||||
|
usleep(100e3);
|
||||||
|
if (pthread_kill(th, SIGUSR1))
|
||||||
|
return 4;
|
||||||
|
|
||||||
|
usleep(400e3);
|
||||||
|
if (pthread_kill(th, SIGUSR2))
|
||||||
|
return 5;
|
||||||
|
|
||||||
|
g_ready_for_exit = true;
|
||||||
|
|
||||||
|
if (pthread_join(th, 0))
|
||||||
|
return 20;
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue