diff --git a/libc/sock/getsockopt-nt.c b/libc/sock/getsockopt-nt.c index 7fb5737578e..680bf6fc2dd 100644 --- a/libc/sock/getsockopt-nt.c +++ b/libc/sock/getsockopt-nt.c @@ -48,40 +48,34 @@ textwindows int sys_getsockopt_nt(struct Fd *fd, int level, int optname, } if (level == SOL_SOCKET && optname == SO_ERROR) { - 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 { + if (in_optlen < sizeof(int)) 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 && (optname == SO_RCVTIMEO || optname == SO_SNDTIMEO)) { - 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 { + if (in_optlen < sizeof(struct timeval)) 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? if (__imp_getsockopt(fd->handle, level, optname, out_opt_optval, - inout_optlen) == -1) { + inout_optlen) == -1) return __winsockerr(); - } if (level == SOL_SOCKET) { if (optname == SO_LINGER && in_optlen == sizeof(struct linger)) { diff --git a/libc/sock/recv.c b/libc/sock/recv.c index ec85a9b48c7..629668a5bc3 100644 --- a/libc/sock/recv.c +++ b/libc/sock/recv.c @@ -38,7 +38,7 @@ * EPIPE (if MSG_NOSIGNAL), EMSGSIZE, ENOTSOCK, EFAULT, etc. * @cancelationpoint * @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 rc; diff --git a/libc/sock/recvfrom.c b/libc/sock/recvfrom.c index d5e7565cf0d..d323b775c84 100644 --- a/libc/sock/recvfrom.c +++ b/libc/sock/recvfrom.c @@ -49,7 +49,7 @@ * EPIPE (if MSG_NOSIGNAL), EMSGSIZE, ENOTSOCK, EFAULT, etc. * @cancelationpoint * @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, struct sockaddr *opt_out_srcaddr, diff --git a/libc/sock/send.c b/libc/sock/send.c index 5cb91fad166..25c836ddcb1 100644 --- a/libc/sock/send.c +++ b/libc/sock/send.c @@ -51,7 +51,7 @@ * EPIPE (if MSG_NOSIGNAL), EMSGSIZE, ENOTSOCK, EFAULT, etc. * @cancelationpoint * @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 rc; diff --git a/libc/sock/sendto.c b/libc/sock/sendto.c index b533fdc3ef0..a949c711d00 100644 --- a/libc/sock/sendto.c +++ b/libc/sock/sendto.c @@ -51,7 +51,7 @@ * EPIPE (if MSG_NOSIGNAL), EMSGSIZE, ENOTSOCK, EFAULT, etc. * @cancelationpoint * @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, const struct sockaddr *opt_addr, uint32_t addrsize) { diff --git a/libc/sock/setsockopt-nt.c b/libc/sock/setsockopt-nt.c index c8fafce0007..670c9a150f8 100644 --- a/libc/sock/setsockopt-nt.c +++ b/libc/sock/setsockopt-nt.c @@ -36,14 +36,17 @@ textwindows int sys_setsockopt_nt(struct Fd *fd, int level, int optname, const void *optval, uint32_t optlen) { // socket read/write timeouts + // timeout of zero means wait forever (default) if (level == SOL_SOCKET && (optname == SO_RCVTIMEO || optname == SO_SNDTIMEO)) { - if (!(optval && optlen == sizeof(struct timeval))) + if (!optval) + return einval(); + if (optlen < sizeof(struct timeval)) return einval(); const struct timeval *tv = optval; int64_t ms = timeval_tomillis(*tv); if (ms > -1u) - ms = 0; // wait forever (default) yes zero actually means this + ms = -1u; if (optname == SO_RCVTIMEO) fd->rcvtimeo = ms; 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 } - // how to make close() a blocking i/o call + // how to make close() a blocking i/o call lool union { uint32_t millis; struct linger_nt linger; diff --git a/libc/sock/winsockblock.c b/libc/sock/winsockblock.c index 16b62efa0c7..6eb1e702e11 100644 --- a/libc/sock/winsockblock.c +++ b/libc/sock/winsockblock.c @@ -191,6 +191,11 @@ __winsock_block(int64_t handle, uint32_t flags, int nonblock, // check if signal handler without SA_RESTART was called if (handler_was_called & SIG_HANDLED_NO_RESTART) 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 diff --git a/test/posix/socket_timeout_signal_test.c b/test/posix/socket_timeout_signal_test.c new file mode 100644 index 00000000000..9d27db6df8b --- /dev/null +++ b/test/posix/socket_timeout_signal_test.c @@ -0,0 +1,215 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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; +}