diff --git a/libctru/source/services/soc/soc_common.c b/libctru/source/services/soc/soc_common.c index a9a9e1988..6bcdf9ee9 100644 --- a/libctru/source/services/soc/soc_common.c +++ b/libctru/source/services/soc/soc_common.c @@ -100,3 +100,16 @@ s32 _net_convert_error(s32 sock_retval) return NET_UNKNOWN_ERROR_OFFSET + sock_retval; return -_net_error_code_map[-sock_retval]; } + +// The service reports a failed connection only as readable: receiving then fails, with ENOTCONN when the +// connection was refused. Returns the reason, or 0 while the connection is up or still being made. +int soc_connection_error(int sockfd) +{ + char byte; + int error = 0; + int saved_errno = errno; + if(recv(sockfd, &byte, 1, MSG_PEEK | MSG_DONTWAIT) < 0 && errno != EAGAIN) + error = errno == ENOTCONN ? ECONNREFUSED : errno; + errno = saved_errno; + return error; +} diff --git a/libctru/source/services/soc/soc_common.h b/libctru/source/services/soc/soc_common.h index 85759eb7b..33de2950a 100644 --- a/libctru/source/services/soc/soc_common.h +++ b/libctru/source/services/soc/soc_common.h @@ -28,6 +28,8 @@ soc_get_fd(int fd) s32 _net_convert_error(s32 sock_retval); +int soc_connection_error(int sockfd); + ssize_t soc_recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen); ssize_t soc_sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen); diff --git a/libctru/source/services/soc/soc_getsockopt.c b/libctru/source/services/soc/soc_getsockopt.c index e3fe64a86..752340ecc 100644 --- a/libctru/source/services/soc/soc_getsockopt.c +++ b/libctru/source/services/soc/soc_getsockopt.c @@ -6,6 +6,7 @@ int getsockopt(int sockfd, int level, int optname, void *optval, socklen_t *optlen) { int ret = 0; + int fd = sockfd; u32 *cmdbuf = getThreadCommandBuffer(); u32 saved_threadstorage[2]; @@ -50,5 +51,17 @@ int getsockopt(int sockfd, int level, int optname, void *optval, socklen_t *optl *optlen = cmdbuf[3]; + // For SO_ERROR the service returns the result code of an earlier operation on the socket, in its own + // codes: EINPROGRESS after a non-blocking connect() even once it has completed, EAGAIN after a recv() + // that found nothing, ENOTCONN once a refused connection was polled. None of those is an error of the + // socket itself, so report whether its connection failed instead. + if(level == SOL_SOCKET && optname == SO_ERROR && *optlen >= sizeof(int)) { + int *error = (int*)optval; + if(*error < 0) + *error = -_net_convert_error(*error); + if(*error == EINPROGRESS || *error == EALREADY || *error == ENOTCONN || *error == EAGAIN) + *error = soc_connection_error(fd); + } + return ret; } diff --git a/libctru/source/services/soc/soc_poll.c b/libctru/source/services/soc/soc_poll.c index 145fff2cb..a2c190079 100644 --- a/libctru/source/services/soc/soc_poll.c +++ b/libctru/source/services/soc/soc_poll.c @@ -4,15 +4,67 @@ #include #include #include <3ds/ipc.h> +#include <3ds/os.h> -int poll(struct pollfd *fds, nfds_t nfds, int timeout) +// The SOC service doesn't wake a poll() waiting for POLLOUT when a non-blocking connect() completes: +// it only notices once the poll times out. Waits that include POLLOUT are therefore split into slices. +#define POLLOUT_SLICE_MS 10 + +static int soc_poll(struct pollfd *fds, nfds_t nfds, int timeout) { int ret = 0; - nfds_t i; u32 size = sizeof(struct pollfd)*nfds; u32 *cmdbuf = getThreadCommandBuffer(); u32 saved_threadstorage[2]; + cmdbuf[0] = IPC_MakeHeader(0x14,2,4); // 0x140084 + cmdbuf[1] = (u32)nfds; + cmdbuf[2] = (u32)timeout; + cmdbuf[3] = IPC_Desc_CurProcessId(); + cmdbuf[5] = IPC_Desc_StaticBuffer(size,10); + cmdbuf[6] = (u32)fds; + + u32 * staticbufs = getThreadStaticBuffers(); + saved_threadstorage[0] = staticbufs[0]; + saved_threadstorage[1] = staticbufs[1]; + + staticbufs[0] = IPC_Desc_StaticBuffer(size,0); + staticbufs[1] = (u32)fds; + + ret = svcSendSyncRequest(SOCU_handle); + + staticbufs[0] = saved_threadstorage[0]; + staticbufs[1] = saved_threadstorage[1]; + + if(ret != 0) { + errno = SYNC_ERROR; + return ret; + } + + ret = (int)cmdbuf[1]; + if(ret == 0) + ret = _net_convert_error(cmdbuf[2]); + + if(ret < 0) { + errno = -ret; + return -1; + } + + return ret; +} + +static s64 elapsed_ms(u64 start) +{ + return (svcGetSystemTick() - start) * 1000 / SYSCLOCK_ARM11; +} + +int poll(struct pollfd *fds, nfds_t nfds, int timeout) +{ + int ret = 0; + nfds_t i; + bool sliced = false; + u64 start = svcGetSystemTick(); + if(nfds == 0) { errno = EINVAL; return -1; @@ -34,6 +86,13 @@ int poll(struct pollfd *fds, nfds_t nfds, int timeout) free(tmp_fds); return -1; } + + // a failed connect() is only reported as readable, so watch for that too + if(fds[i].events & POLLOUT) { + tmp_fds[i].events |= POLLIN; + if(timeout != 0) + sliced = true; + } } // negative fds are ignored, however 3ds poll only // ignores fd's that are -1. @@ -41,47 +100,48 @@ int poll(struct pollfd *fds, nfds_t nfds, int timeout) else { tmp_fds[i].fd = -1; } - tmp_fds[i].revents = 0; } - cmdbuf[0] = IPC_MakeHeader(0x14,2,4); // 0x140084 - cmdbuf[1] = (u32)nfds; - cmdbuf[2] = (u32)timeout; - cmdbuf[3] = IPC_Desc_CurProcessId(); - cmdbuf[5] = IPC_Desc_StaticBuffer(size,10); - cmdbuf[6] = (u32)tmp_fds; - - u32 * staticbufs = getThreadStaticBuffers(); - saved_threadstorage[0] = staticbufs[0]; - saved_threadstorage[1] = staticbufs[1]; + for(;;) { + int slice = timeout; + if(sliced) { + slice = POLLOUT_SLICE_MS; + if(timeout > 0) { + s64 left = timeout - elapsed_ms(start); + if(left < slice) + slice = left > 0 ? left : 0; + } + } - staticbufs[0] = IPC_Desc_StaticBuffer(size,0); - staticbufs[1] = (u32)tmp_fds; + for(i = 0; i < nfds; ++i) + tmp_fds[i].revents = 0; - ret = svcSendSyncRequest(SOCU_handle); + ret = soc_poll(tmp_fds, nfds, slice); + if(ret < 0) + break; - staticbufs[0] = saved_threadstorage[0]; - staticbufs[1] = saved_threadstorage[1]; + ret = 0; + for(i = 0; i < nfds; ++i) { + short revents = tmp_fds[i].revents; - if(ret != 0) { - free(tmp_fds); - errno = SYNC_ERROR; - return ret; - } + if((fds[i].events & POLLOUT) && (revents & POLLIN) && !(revents & POLLOUT)) { + if(soc_connection_error(fds[i].fd)) + revents |= POLLERR | POLLHUP; // the connection failed or was reset + else if(!(fds[i].events & POLLIN)) + tmp_fds[i].events &= ~POLLIN; // only data nobody asked for: stop watching for it + } + if(!(fds[i].events & POLLIN)) + revents &= ~POLLIN; - ret = (int)cmdbuf[1]; - if(ret == 0) - ret = _net_convert_error(cmdbuf[2]); + fds[i].revents = revents; + if(revents != 0) + ++ret; + } - if(ret < 0) { - free(tmp_fds); - errno = -ret; - return -1; + if(ret != 0 || !sliced || (timeout > 0 && elapsed_ms(start) >= timeout)) + break; } - for(i = 0; i < nfds; ++i) - fds[i].revents = tmp_fds[i].revents; - free(tmp_fds); return ret;