mirror of
https://github.com/jart/cosmopolitan.git
synced 2025-07-29 14:00:29 +00:00
Make improvements
- We now serialize the file descriptor table when spawning / executing processes on Windows. This means you can now inherit more stuff than just standard i/o. It's needed by bash, which duplicates the console to file descriptor #255. We also now do a better job serializing the environment variables, so you're less likely to encounter E2BIG when using your bash shell. We also no longer coerce environ to uppercase - execve() on Windows now remotely controls its parent process to make them spawn a replacement for itself. Then it'll be able to terminate immediately once the spawn succeeds, without having to linger around for the lifetime as a shell process for proxying the exit code. When process worker thread running in the parent sees the child die, it's given a handle to the new child, to replace it in the process table. - execve() and posix_spawn() on Windows will now provide CreateProcess an explicit handle list. This allows us to remove handle locks which enables better fork/spawn concurrency, with seriously correct thread safety. Other codebases like Go use the same technique. On the other hand fork() still favors the conventional WIN32 inheritence approach which can be a little bit messy, but is *controlled* by guaranteeing perfectly clean slates at both the spawning and execution boundaries - sigset_t is now 64 bits. Having it be 128 bits was a mistake because there's no reason to use that and it's only supported by FreeBSD. By using the system word size, signal mask manipulation on Windows goes very fast. Furthermore @asyncsignalsafe funcs have been rewritten on Windows to take advantage of signal masking, now that it's much more pleasant to use. - All the overlapped i/o code on Windows has been rewritten for pretty good signal and cancelation safety. We're now able to ensure overlap data structures are cleaned up so long as you don't longjmp() out of out of a signal handler that interrupted an i/o operation. Latencies are also improved thanks to the removal of lots of "busy wait" code. Waits should be optimal for everything except poll(), which shall be the last and final demon we slay in the win32 i/o horror show. - getrusage() on Windows is now able to report RUSAGE_CHILDREN as well as RUSAGE_SELF, thanks to aggregation in the process manager thread.
This commit is contained in:
parent
af7cb3c82f
commit
791f79fcb3
382 changed files with 4008 additions and 4511 deletions
|
@ -16,9 +16,13 @@
|
|||
│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │
|
||||
│ PERFORMANCE OF THIS SOFTWARE. │
|
||||
╚─────────────────────────────────────────────────────────────────────────────*/
|
||||
#include "libc/assert.h"
|
||||
#include "libc/calls/internal.h"
|
||||
#include "libc/calls/state.internal.h"
|
||||
#include "libc/calls/struct/fd.internal.h"
|
||||
#include "libc/calls/struct/sigset.internal.h"
|
||||
#include "libc/errno.h"
|
||||
#include "libc/intrin/strace.internal.h"
|
||||
#include "libc/macros.internal.h"
|
||||
#include "libc/mem/mem.h"
|
||||
#include "libc/nt/thunk/msabi.h"
|
||||
|
@ -30,8 +34,12 @@
|
|||
#include "libc/sysv/consts/af.h"
|
||||
#include "libc/sysv/consts/o.h"
|
||||
#include "libc/sysv/consts/sock.h"
|
||||
#include "libc/sysv/consts/sol.h"
|
||||
#include "libc/thread/thread.h"
|
||||
#ifdef __x86_64__
|
||||
|
||||
__msabi extern typeof(__sys_closesocket_nt) *const __imp_closesocket;
|
||||
__msabi extern typeof(__sys_setsockopt_nt) *const __imp_setsockopt;
|
||||
|
||||
union AcceptExAddr {
|
||||
struct sockaddr_storage addr;
|
||||
|
@ -43,58 +51,83 @@ struct AcceptExBuffer {
|
|||
union AcceptExAddr remote;
|
||||
};
|
||||
|
||||
textwindows int sys_accept_nt(struct Fd *fd, struct sockaddr_storage *addr,
|
||||
int accept4_flags) {
|
||||
struct AcceptResources {
|
||||
int64_t handle;
|
||||
int client, oflags;
|
||||
uint32_t bytes_received;
|
||||
uint32_t completion_flags;
|
||||
struct AcceptExBuffer buffer;
|
||||
struct SockFd *sockfd, *sockfd2;
|
||||
};
|
||||
|
||||
struct AcceptArgs {
|
||||
int64_t listensock;
|
||||
struct AcceptExBuffer *buffer;
|
||||
};
|
||||
|
||||
static void sys_accept_nt_unwind(void *arg) {
|
||||
struct AcceptResources *resources = arg;
|
||||
if (resources->handle != -1) {
|
||||
__imp_closesocket(resources->handle);
|
||||
}
|
||||
}
|
||||
|
||||
static int sys_accept_nt_start(int64_t handle, struct NtOverlapped *overlap,
|
||||
uint32_t *flags, void *arg) {
|
||||
struct AcceptArgs *args = arg;
|
||||
if (AcceptEx(args->listensock, handle, args->buffer, 0,
|
||||
sizeof(args->buffer->local), sizeof(args->buffer->remote), 0,
|
||||
overlap)) {
|
||||
// inherit properties of listening socket
|
||||
unassert(!__imp_setsockopt(args->listensock, SOL_SOCKET,
|
||||
kNtSoUpdateAcceptContext, &handle,
|
||||
sizeof(handle)));
|
||||
return 0;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
textwindows int sys_accept_nt(struct Fd *f, struct sockaddr_storage *addr,
|
||||
int accept4_flags) {
|
||||
int client = -1;
|
||||
sigset_t m = __sig_block();
|
||||
struct AcceptResources resources = {-1};
|
||||
pthread_cleanup_push(sys_accept_nt_unwind, &resources);
|
||||
|
||||
// creates resources for child socket
|
||||
// inherit the listener configuration
|
||||
sockfd = (struct SockFd *)fd->extra;
|
||||
if (!(sockfd2 = malloc(sizeof(struct SockFd)))) {
|
||||
return -1;
|
||||
}
|
||||
memcpy(sockfd2, sockfd, sizeof(*sockfd));
|
||||
if ((handle = WSASocket(sockfd2->family, sockfd2->type, sockfd2->protocol,
|
||||
NULL, 0, kNtWsaFlagOverlapped)) == -1) {
|
||||
free(sockfd2);
|
||||
return __winsockerr();
|
||||
if ((resources.handle = WSASocket(f->family, f->type, f->protocol, 0, 0,
|
||||
kNtWsaFlagOverlapped)) == -1) {
|
||||
client = __winsockerr();
|
||||
goto WeFailed;
|
||||
}
|
||||
|
||||
// accept network connection
|
||||
struct NtOverlapped overlapped = {.hEvent = WSACreateEvent()};
|
||||
if (!AcceptEx(fd->handle, handle, &buffer, 0, sizeof(buffer.local),
|
||||
sizeof(buffer.remote), &bytes_received, &overlapped)) {
|
||||
sockfd = (struct SockFd *)fd->extra;
|
||||
if (__wsablock(fd, &overlapped, &completion_flags, kSigOpRestartable,
|
||||
sockfd->rcvtimeo) == -1) {
|
||||
WSACloseEvent(overlapped.hEvent);
|
||||
__imp_closesocket(handle);
|
||||
free(sockfd2);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
WSACloseEvent(overlapped.hEvent);
|
||||
// this operation can re-enter, interrupt, cancel, block, timeout, etc.
|
||||
struct AcceptExBuffer buffer;
|
||||
ssize_t bytes_received = __winsock_block(
|
||||
resources.handle, 0, !!(f->flags & O_NONBLOCK), f->rcvtimeo, m,
|
||||
sys_accept_nt_start, &(struct AcceptArgs){f->handle, &buffer});
|
||||
if (bytes_received == -1) goto WeFailed;
|
||||
|
||||
// create file descriptor for new socket
|
||||
// don't inherit the file open mode bits
|
||||
oflags = 0;
|
||||
int oflags = 0;
|
||||
if (accept4_flags & SOCK_CLOEXEC) oflags |= O_CLOEXEC;
|
||||
if (accept4_flags & SOCK_NONBLOCK) oflags |= O_NONBLOCK;
|
||||
__fds_lock();
|
||||
client = __reservefd_unlocked(-1);
|
||||
g_fds.p[client].kind = kFdSocket;
|
||||
client = __reservefd(-1);
|
||||
g_fds.p[client].flags = oflags;
|
||||
g_fds.p[client].mode = 0140666;
|
||||
g_fds.p[client].handle = handle;
|
||||
g_fds.p[client].extra = (uintptr_t)sockfd2;
|
||||
__fds_unlock();
|
||||
|
||||
// handoff information to caller;
|
||||
g_fds.p[client].family = f->family;
|
||||
g_fds.p[client].type = f->type;
|
||||
g_fds.p[client].protocol = f->protocol;
|
||||
g_fds.p[client].sndtimeo = f->sndtimeo;
|
||||
g_fds.p[client].rcvtimeo = f->rcvtimeo;
|
||||
g_fds.p[client].handle = resources.handle;
|
||||
resources.handle = -1;
|
||||
memcpy(addr, &buffer.remote.addr, sizeof(*addr));
|
||||
g_fds.p[client].kind = kFdSocket;
|
||||
|
||||
WeFailed:
|
||||
pthread_cleanup_pop(false);
|
||||
__sig_unblock(m);
|
||||
return client;
|
||||
}
|
||||
|
||||
#endif /* __x86_64__ */
|
||||
|
|
|
@ -26,7 +26,7 @@
|
|||
* @param opt_out_addr will receive the remote address
|
||||
* @param opt_inout_addrsize provides and receives addr's byte length
|
||||
* @return client fd which needs close(), or -1 w/ errno
|
||||
* @cancellationpoint
|
||||
* @cancelationpoint
|
||||
* @asyncsignalsafe
|
||||
* @restartable (unless SO_RCVTIMEO)
|
||||
*/
|
||||
|
|
|
@ -37,7 +37,7 @@
|
|||
* @param flags can have SOCK_{CLOEXEC,NONBLOCK}, which may apply to
|
||||
* both the newly created socket and the server one
|
||||
* @return client fd which needs close(), or -1 w/ errno
|
||||
* @cancellationpoint
|
||||
* @cancelationpoint
|
||||
* @asyncsignalsafe
|
||||
* @restartable (unless SO_RCVTIMEO)
|
||||
*/
|
||||
|
@ -45,7 +45,7 @@ int accept4(int fd, struct sockaddr *opt_out_addr, uint32_t *opt_inout_addrsize,
|
|||
int flags) {
|
||||
int rc;
|
||||
struct sockaddr_storage ss = {0};
|
||||
BEGIN_CANCELLATION_POINT;
|
||||
BEGIN_CANCELATION_POINT;
|
||||
|
||||
if (fd < g_fds.n && g_fds.p[fd].kind == kFdZip) {
|
||||
rc = enotsock();
|
||||
|
@ -66,7 +66,7 @@ int accept4(int fd, struct sockaddr *opt_out_addr, uint32_t *opt_inout_addrsize,
|
|||
__write_sockaddr(&ss, opt_out_addr, opt_inout_addrsize);
|
||||
}
|
||||
|
||||
END_CANCELLATION_POINT;
|
||||
END_CANCELATION_POINT;
|
||||
STRACE("accept4(%d, [%s]) -> %d% lm", fd,
|
||||
DescribeSockaddr(opt_out_addr,
|
||||
opt_inout_addrsize ? *opt_inout_addrsize : 0),
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
#include "libc/nt/winsock.h"
|
||||
#include "libc/sock/internal.h"
|
||||
#include "libc/sock/syscall_fd.internal.h"
|
||||
#ifdef __x86_64__
|
||||
|
||||
__msabi extern typeof(__sys_bind_nt) *const __imp_bind;
|
||||
|
||||
|
@ -34,3 +35,5 @@ textwindows int sys_bind_nt(struct Fd *fd, const void *addr,
|
|||
return __winsockerr();
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* __x86_64__ */
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
#include "libc/nt/winsock.h"
|
||||
#include "libc/sock/internal.h"
|
||||
#include "libc/sock/syscall_fd.internal.h"
|
||||
#ifdef __x86_64__
|
||||
|
||||
__msabi extern typeof(__sys_closesocket_nt) *const __imp_closesocket;
|
||||
|
||||
|
@ -30,9 +31,6 @@ __msabi extern typeof(__sys_closesocket_nt) *const __imp_closesocket;
|
|||
* This function should only be called by close().
|
||||
*/
|
||||
textwindows int sys_closesocket_nt(struct Fd *fd) {
|
||||
struct SockFd *sockfd;
|
||||
sockfd = (struct SockFd *)fd->extra;
|
||||
free(sockfd);
|
||||
int rc = __imp_closesocket(fd->handle);
|
||||
if (rc != -1) {
|
||||
return 0;
|
||||
|
@ -40,3 +38,5 @@ textwindows int sys_closesocket_nt(struct Fd *fd) {
|
|||
return __winsockerr();
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* __x86_64__ */
|
||||
|
|
|
@ -21,16 +21,45 @@
|
|||
#include "libc/nt/winsock.h"
|
||||
#include "libc/sock/internal.h"
|
||||
#include "libc/sock/syscall_fd.internal.h"
|
||||
#include "libc/sock/yoink.inc"
|
||||
#include "libc/sysv/errfuns.h"
|
||||
#ifdef __x86_64__
|
||||
#include "libc/errno.h"
|
||||
#include "libc/sock/yoink.inc"
|
||||
|
||||
static textwindows int64_t __connect_block(int64_t fh, unsigned eventbit,
|
||||
int64_t rc, uint32_t timeout) {
|
||||
int64_t eh;
|
||||
struct NtWsaNetworkEvents ev;
|
||||
if (rc != -1) return rc;
|
||||
if (WSAGetLastError() != EWOULDBLOCK) return __winsockerr();
|
||||
eh = WSACreateEvent();
|
||||
bzero(&ev, sizeof(ev));
|
||||
/* The proper way to reset the state of an event object used with the
|
||||
WSAEventSelect function is to pass the handle of the event object
|
||||
to the WSAEnumNetworkEvents function in the hEventObject parameter.
|
||||
This will reset the event object and adjust the status of active FD
|
||||
events on the socket in an atomic fashion. -- MSDN */
|
||||
if (WSAEventSelect(fh, eh, 1u << eventbit) != -1 &&
|
||||
WSAEnumNetworkEvents(fh, eh, &ev) != -1) {
|
||||
if (!ev.iErrorCode[eventbit]) {
|
||||
rc = 0;
|
||||
} else {
|
||||
errno = ev.iErrorCode[eventbit];
|
||||
}
|
||||
} else {
|
||||
__winsockerr();
|
||||
}
|
||||
WSACloseEvent(eh);
|
||||
return rc;
|
||||
}
|
||||
|
||||
textwindows int sys_connect_nt(struct Fd *fd, const void *addr,
|
||||
uint32_t addrsize) {
|
||||
struct SockFd *sockfd;
|
||||
sockfd = (struct SockFd *)fd->extra;
|
||||
npassert(fd->kind == kFdSocket);
|
||||
return __winsockblock(
|
||||
return __connect_block(
|
||||
fd->handle, _bsr(kNtFdConnect),
|
||||
WSAConnect(fd->handle, addr, addrsize, NULL, NULL, NULL, NULL),
|
||||
sockfd->rcvtimeo);
|
||||
fd->rcvtimeo);
|
||||
}
|
||||
|
||||
#endif /* __x86_64__ */
|
||||
|
|
|
@ -37,13 +37,13 @@
|
|||
* also means getsockname() can be called to retrieve routing details.
|
||||
*
|
||||
* @return 0 on success or -1 w/ errno
|
||||
* @cancellationpoint
|
||||
* @cancelationpoint
|
||||
* @asyncsignalsafe
|
||||
* @restartable (unless SO_RCVTIMEO)
|
||||
*/
|
||||
int connect(int fd, const struct sockaddr *addr, uint32_t addrsize) {
|
||||
int rc;
|
||||
BEGIN_CANCELLATION_POINT;
|
||||
BEGIN_CANCELATION_POINT;
|
||||
|
||||
if (addr && !(IsAsan() && !__asan_is_valid(addr, addrsize))) {
|
||||
if (fd < g_fds.n && g_fds.p[fd].kind == kFdZip) {
|
||||
|
@ -61,7 +61,7 @@ int connect(int fd, const struct sockaddr *addr, uint32_t addrsize) {
|
|||
rc = efault();
|
||||
}
|
||||
|
||||
END_CANCELLATION_POINT;
|
||||
END_CANCELATION_POINT;
|
||||
STRACE("connect(%d, %s) → %d% lm", fd, DescribeSockaddr(addr, addrsize), rc);
|
||||
return rc;
|
||||
}
|
||||
|
|
|
@ -1,30 +0,0 @@
|
|||
/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
|
||||
│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
|
||||
╞══════════════════════════════════════════════════════════════════════════════╡
|
||||
│ Copyright 2022 Justine Alexandra Roberts Tunney │
|
||||
│ │
|
||||
│ Permission to use, copy, modify, and/or distribute this software for │
|
||||
│ any purpose with or without fee is hereby granted, provided that the │
|
||||
│ above copyright notice and this permission notice appear in all copies. │
|
||||
│ │
|
||||
│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │
|
||||
│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │
|
||||
│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │
|
||||
│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │
|
||||
│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │
|
||||
│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │
|
||||
│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │
|
||||
│ PERFORMANCE OF THIS SOFTWARE. │
|
||||
╚─────────────────────────────────────────────────────────────────────────────*/
|
||||
#include "libc/mem/mem.h"
|
||||
#include "libc/nt/winsock.h"
|
||||
#include "libc/sock/internal.h"
|
||||
#include "libc/str/str.h"
|
||||
|
||||
textwindows struct SockFd *_dupsockfd(struct SockFd *sockfd) {
|
||||
struct SockFd *newsf;
|
||||
if ((newsf = malloc(sizeof(struct SockFd)))) {
|
||||
memcpy(newsf, sockfd, sizeof(*sockfd));
|
||||
}
|
||||
return newsf;
|
||||
}
|
|
@ -36,7 +36,6 @@
|
|||
#include "libc/assert.h"
|
||||
#include "libc/calls/cp.internal.h"
|
||||
#include "libc/calls/internal.h"
|
||||
#include "libc/calls/sig.internal.h"
|
||||
#include "libc/calls/state.internal.h"
|
||||
#include "libc/calls/struct/sigset.internal.h"
|
||||
#include "libc/calls/syscall_support-sysv.internal.h"
|
||||
|
@ -1442,7 +1441,9 @@ int epoll_create(int size) {
|
|||
if (size <= 0) {
|
||||
rc = einval();
|
||||
} else {
|
||||
BLOCK_SIGNALS;
|
||||
rc = epoll_create1(0);
|
||||
ALLOW_SIGNALS;
|
||||
}
|
||||
STRACE("epoll_create(%d) → %d% m", size, rc);
|
||||
return rc;
|
||||
|
@ -1462,7 +1463,9 @@ int epoll_create1(int flags) {
|
|||
} else if (!IsWindows()) {
|
||||
rc = __fixupnewfd(sys_epoll_create(1337), flags);
|
||||
} else {
|
||||
BLOCK_SIGNALS;
|
||||
rc = sys_epoll_create1_nt(flags);
|
||||
ALLOW_SIGNALS;
|
||||
}
|
||||
STRACE("epoll_create1(%#x) → %d% m", flags, rc);
|
||||
return rc;
|
||||
|
@ -1505,7 +1508,9 @@ int epoll_ctl(int epfd, int op, int fd, struct epoll_event *ev) {
|
|||
if (!IsWindows()) {
|
||||
rc = sys_epoll_ctl(epfd, op, fd, ev);
|
||||
} else {
|
||||
BLOCK_SIGNALS;
|
||||
rc = sys_epoll_ctl_nt(epfd, op, fd, ev);
|
||||
ALLOW_SIGNALS;
|
||||
}
|
||||
STRACE("epoll_ctl(%d, %d, %d, %p) → %d% m", epfd, op, fd, ev, rc);
|
||||
return rc;
|
||||
|
@ -1518,13 +1523,13 @@ int epoll_ctl(int epfd, int op, int fd, struct epoll_event *ev) {
|
|||
* @param maxevents is array length of events
|
||||
* @param timeoutms is milliseconds, 0 to not block, or -1 for forever
|
||||
* @return number of events stored, 0 on timeout, or -1 w/ errno
|
||||
* @cancellationpoint
|
||||
* @cancelationpoint
|
||||
* @norestart
|
||||
*/
|
||||
int epoll_wait(int epfd, struct epoll_event *events, int maxevents,
|
||||
int timeoutms) {
|
||||
int e, rc;
|
||||
BEGIN_CANCELLATION_POINT;
|
||||
BEGIN_CANCELATION_POINT;
|
||||
if (!IsWindows()) {
|
||||
e = errno;
|
||||
rc = sys_epoll_wait(epfd, events, maxevents, timeoutms);
|
||||
|
@ -1533,9 +1538,12 @@ int epoll_wait(int epfd, struct epoll_event *events, int maxevents,
|
|||
rc = sys_epoll_pwait(epfd, events, maxevents, timeoutms, 0, 0);
|
||||
}
|
||||
} else {
|
||||
BLOCK_SIGNALS;
|
||||
// eintr/ecanceled not implemented for epoll() on win32 yet
|
||||
rc = sys_epoll_wait_nt(epfd, events, maxevents, timeoutms);
|
||||
ALLOW_SIGNALS;
|
||||
}
|
||||
END_CANCELLATION_POINT;
|
||||
END_CANCELATION_POINT;
|
||||
STRACE("epoll_wait(%d, %p, %d, %d) → %d% m", epfd, events, maxevents,
|
||||
timeoutms, rc);
|
||||
return rc;
|
||||
|
@ -1549,14 +1557,14 @@ int epoll_wait(int epfd, struct epoll_event *events, int maxevents,
|
|||
* @param timeoutms is milliseconds, 0 to not block, or -1 for forever
|
||||
* @param sigmask is an optional sigprocmask() to use during call
|
||||
* @return number of events stored, 0 on timeout, or -1 w/ errno
|
||||
* @cancellationpoint
|
||||
* @cancelationpoint
|
||||
* @norestart
|
||||
*/
|
||||
int epoll_pwait(int epfd, struct epoll_event *events, int maxevents,
|
||||
int timeoutms, const sigset_t *sigmask) {
|
||||
int e, rc;
|
||||
sigset_t oldmask;
|
||||
BEGIN_CANCELLATION_POINT;
|
||||
BEGIN_CANCELATION_POINT;
|
||||
if (!IsWindows()) {
|
||||
e = errno;
|
||||
rc = sys_epoll_pwait(epfd, events, maxevents, timeoutms, sigmask,
|
||||
|
@ -1568,11 +1576,12 @@ int epoll_pwait(int epfd, struct epoll_event *events, int maxevents,
|
|||
if (sigmask) sys_sigprocmask(SIG_SETMASK, &oldmask, 0);
|
||||
}
|
||||
} else {
|
||||
if (sigmask) __sig_mask(SIG_SETMASK, sigmask, &oldmask);
|
||||
BLOCK_SIGNALS;
|
||||
// eintr/ecanceled not implemented for epoll() on win32 yet
|
||||
rc = sys_epoll_wait_nt(epfd, events, maxevents, timeoutms);
|
||||
if (sigmask) __sig_mask(SIG_SETMASK, &oldmask, 0);
|
||||
ALLOW_SIGNALS;
|
||||
}
|
||||
END_CANCELLATION_POINT;
|
||||
END_CANCELATION_POINT;
|
||||
STRACE("epoll_pwait(%d, %p, %d, %d) → %d% m", epfd, events, maxevents,
|
||||
timeoutms, DescribeSigset(0, sigmask), rc);
|
||||
return rc;
|
||||
|
|
|
@ -50,7 +50,7 @@ static int __getsockpeername(int fd, struct sockaddr *out_addr,
|
|||
// The socket has not been bound to an address with bind, or
|
||||
// ADDR_ANY is specified in bind but connection has not yet
|
||||
// occurred. -MSDN
|
||||
ss.ss_family = ((struct SockFd *)g_fds.p[fd].extra)->family;
|
||||
ss.ss_family = g_fds.p[fd].family;
|
||||
rc = 0;
|
||||
} else {
|
||||
rc = __winsockerr();
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
#include "libc/sysv/consts/so.h"
|
||||
#include "libc/sysv/consts/sol.h"
|
||||
#include "libc/sysv/errfuns.h"
|
||||
#ifdef __x86_64__
|
||||
|
||||
__msabi extern typeof(__sys_getsockopt_nt) *const __imp_getsockopt;
|
||||
|
||||
|
@ -37,10 +38,8 @@ textwindows int sys_getsockopt_nt(struct Fd *fd, int level, int optname,
|
|||
uint32_t *inout_optlen) {
|
||||
uint64_t ms;
|
||||
uint32_t in_optlen;
|
||||
struct SockFd *sockfd;
|
||||
struct linger_nt linger;
|
||||
npassert(fd->kind == kFdSocket);
|
||||
sockfd = (struct SockFd *)fd->extra;
|
||||
|
||||
if (out_opt_optval && inout_optlen) {
|
||||
in_optlen = *inout_optlen;
|
||||
|
@ -52,9 +51,9 @@ textwindows int sys_getsockopt_nt(struct Fd *fd, int level, int optname,
|
|||
(optname == SO_RCVTIMEO || optname == SO_SNDTIMEO)) {
|
||||
if (in_optlen >= sizeof(struct timeval)) {
|
||||
if (optname == SO_RCVTIMEO) {
|
||||
ms = sockfd->rcvtimeo;
|
||||
ms = fd->rcvtimeo;
|
||||
} else {
|
||||
ms = sockfd->sndtimeo;
|
||||
ms = fd->sndtimeo;
|
||||
}
|
||||
((struct timeval *)out_opt_optval)->tv_sec = ms / 1000;
|
||||
((struct timeval *)out_opt_optval)->tv_usec = ms % 1000 * 1000;
|
||||
|
@ -90,3 +89,5 @@ textwindows int sys_getsockopt_nt(struct Fd *fd, int level, int optname,
|
|||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif /* __x86_64__ */
|
||||
|
|
|
@ -26,14 +26,6 @@ COSMOPOLITAN_C_START_
|
|||
|
||||
#define SOCKFD_OVERLAP_BUFSIZ 128
|
||||
|
||||
struct SockFd {
|
||||
int family;
|
||||
int type;
|
||||
int protocol;
|
||||
uint32_t rcvtimeo;
|
||||
uint32_t sndtimeo;
|
||||
};
|
||||
|
||||
errno_t __dos2errno(uint32_t);
|
||||
|
||||
int32_t __sys_accept(int32_t, void *, uint32_t *, int) __wur;
|
||||
|
@ -79,11 +71,14 @@ int sys_select_nt(int, fd_set *, fd_set *, fd_set *, struct timeval *,
|
|||
|
||||
size_t __iovec2nt(struct NtIovec[hasatleast 16], const struct iovec *, size_t);
|
||||
|
||||
ssize_t __winsock_block(int64_t, uint32_t, bool, uint32_t, uint64_t,
|
||||
int (*)(int64_t, struct NtOverlapped *, uint32_t *,
|
||||
void *),
|
||||
void *);
|
||||
|
||||
void WinSockInit(void);
|
||||
int64_t __winsockerr(void);
|
||||
int __fixupnewsockfd(int, int);
|
||||
int64_t __winsockblock(int64_t, unsigned, int64_t, uint32_t);
|
||||
struct SockFd *_dupsockfd(struct SockFd *);
|
||||
int64_t GetNtBaseSocket(int64_t);
|
||||
int sys_close_epoll(int);
|
||||
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
#include "libc/nt/winsock.h"
|
||||
#include "libc/sock/internal.h"
|
||||
#include "libc/sock/syscall_fd.internal.h"
|
||||
#ifdef __x86_64__
|
||||
|
||||
__msabi extern typeof(__sys_listen_nt) *const __imp_listen;
|
||||
|
||||
|
@ -32,3 +33,5 @@ textwindows int sys_listen_nt(struct Fd *fd, int backlog) {
|
|||
return __winsockerr();
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* __x86_64__ */
|
||||
|
|
27
libc/sock/overlapped.internal.h
Normal file
27
libc/sock/overlapped.internal.h
Normal file
|
@ -0,0 +1,27 @@
|
|||
#ifndef COSMOPOLITAN_LIBC_SOCK_OVERLAPPED_H_
|
||||
#define COSMOPOLITAN_LIBC_SOCK_OVERLAPPED_H_
|
||||
#include "libc/nt/struct/overlapped.h"
|
||||
#include "libc/thread/thread.h"
|
||||
#if !(__ASSEMBLER__ + __LINKER__ + 0)
|
||||
COSMOPOLITAN_C_START_
|
||||
|
||||
#define wsa_overlapped_cleanup_push(handle, overlap) \
|
||||
{ \
|
||||
struct WsaOverlappedCleanup wsa_overlapped_cleanup = {handle, overlap}; \
|
||||
pthread_cleanup_push(wsa_overlapped_cleanup_callback, \
|
||||
&wsa_overlapped_cleanup);
|
||||
|
||||
#define wsa_overlapped_cleanup_pop() \
|
||||
pthread_cleanup_pop(false); \
|
||||
}
|
||||
|
||||
struct WsaOverlappedCleanup {
|
||||
int64_t handle;
|
||||
struct NtOverlapped *overlap;
|
||||
};
|
||||
|
||||
void wsa_overlapped_cleanup_callback(void *);
|
||||
|
||||
COSMOPOLITAN_C_END_
|
||||
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
|
||||
#endif /* COSMOPOLITAN_LIBC_SOCK_OVERLAPPED_H_ */
|
|
@ -1,36 +0,0 @@
|
|||
/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
|
||||
│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
|
||||
╞══════════════════════════════════════════════════════════════════════════════╡
|
||||
│ Copyright 2020 Justine Alexandra Roberts Tunney │
|
||||
│ │
|
||||
│ Permission to use, copy, modify, and/or distribute this software for │
|
||||
│ any purpose with or without fee is hereby granted, provided that the │
|
||||
│ above copyright notice and this permission notice appear in all copies. │
|
||||
│ │
|
||||
│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │
|
||||
│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │
|
||||
│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │
|
||||
│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │
|
||||
│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │
|
||||
│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │
|
||||
│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │
|
||||
│ PERFORMANCE OF THIS SOFTWARE. │
|
||||
╚─────────────────────────────────────────────────────────────────────────────*/
|
||||
#include "libc/fmt/conv.h"
|
||||
#include "libc/sock/sock.h"
|
||||
#include "libc/sysv/errfuns.h"
|
||||
|
||||
/* parses string to port number.
|
||||
*
|
||||
* @param service is a NULL-terminated string
|
||||
* @return valid port number or einval()
|
||||
*
|
||||
* @see strtoimax
|
||||
*/
|
||||
int parseport(const char* service) {
|
||||
char* end;
|
||||
int port = strtoimax(service, &end, 0);
|
||||
if (!service || end == service || *end != '\0' || port < 0 || port > 65535)
|
||||
return einval();
|
||||
return port;
|
||||
}
|
|
@ -53,7 +53,7 @@
|
|||
*
|
||||
* @raise ECANCELED if thread was cancelled in masked mode
|
||||
* @raise EINTR if signal was delivered
|
||||
* @cancellationpoint
|
||||
* @cancelationpoint
|
||||
* @asyncsignalsafe
|
||||
* @norestart
|
||||
*/
|
||||
|
@ -76,7 +76,7 @@ int pselect(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds,
|
|||
fd_set *old_exceptfds_ptr = 0;
|
||||
#endif
|
||||
|
||||
BEGIN_CANCELLATION_POINT;
|
||||
BEGIN_CANCELATION_POINT;
|
||||
if (nfds < 0) {
|
||||
rc = einval();
|
||||
} else if (IsAsan() &&
|
||||
|
@ -125,7 +125,7 @@ int pselect(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds,
|
|||
rc = sys_select_nt(nfds, readfds, writefds, exceptfds, tvp, sigmask);
|
||||
}
|
||||
}
|
||||
END_CANCELLATION_POINT;
|
||||
END_CANCELATION_POINT;
|
||||
|
||||
STRACE("pselect(%d, %s → [%s], %s → [%s], %s → [%s], %s, %s) → %d% m", nfds,
|
||||
DescribeFdSet(rc, nfds, old_readfds_ptr),
|
||||
|
|
|
@ -16,41 +16,41 @@
|
|||
│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │
|
||||
│ PERFORMANCE OF THIS SOFTWARE. │
|
||||
╚─────────────────────────────────────────────────────────────────────────────*/
|
||||
#include "libc/assert.h"
|
||||
#include "libc/calls/internal.h"
|
||||
#include "libc/calls/struct/fd.internal.h"
|
||||
#include "libc/errno.h"
|
||||
#include "libc/intrin/strace.internal.h"
|
||||
#include "libc/calls/struct/iovec.h"
|
||||
#include "libc/calls/struct/sigset.internal.h"
|
||||
#include "libc/nt/struct/iovec.h"
|
||||
#include "libc/nt/struct/overlapped.h"
|
||||
#include "libc/nt/winsock.h"
|
||||
#include "libc/sock/internal.h"
|
||||
#include "libc/sock/syscall_fd.internal.h"
|
||||
#include "libc/sysv/errfuns.h"
|
||||
#include "libc/sysv/consts/o.h"
|
||||
#ifdef __x86_64__
|
||||
|
||||
struct RecvArgs {
|
||||
const struct iovec *iov;
|
||||
size_t iovlen;
|
||||
struct NtIovec iovnt[16];
|
||||
};
|
||||
|
||||
static textwindows int sys_recv_nt_start(int64_t handle,
|
||||
struct NtOverlapped *overlap,
|
||||
uint32_t *flags, void *arg) {
|
||||
struct RecvArgs *args = arg;
|
||||
return WSARecv(handle, args->iovnt,
|
||||
__iovec2nt(args->iovnt, args->iov, args->iovlen), 0, flags,
|
||||
overlap, 0);
|
||||
}
|
||||
|
||||
textwindows ssize_t sys_recv_nt(int fd, const struct iovec *iov, size_t iovlen,
|
||||
uint32_t flags) {
|
||||
int err;
|
||||
ssize_t rc;
|
||||
uint32_t got;
|
||||
struct SockFd *sockfd;
|
||||
struct NtIovec iovnt[16];
|
||||
struct NtOverlapped overlapped = {.hEvent = WSACreateEvent()};
|
||||
err = errno;
|
||||
if (!WSARecv(g_fds.p[fd].handle, iovnt, __iovec2nt(iovnt, iov, iovlen), 0,
|
||||
&flags, &overlapped, 0)) {
|
||||
if (WSAGetOverlappedResult(g_fds.p[fd].handle, &overlapped, &got, false,
|
||||
&flags)) {
|
||||
rc = got;
|
||||
} else {
|
||||
rc = -1;
|
||||
}
|
||||
} else {
|
||||
errno = err;
|
||||
sockfd = (struct SockFd *)g_fds.p[fd].extra;
|
||||
rc = __wsablock(g_fds.p + fd, &overlapped, &flags, kSigOpRestartable,
|
||||
sockfd->rcvtimeo);
|
||||
}
|
||||
unassert(WSACloseEvent(overlapped.hEvent));
|
||||
struct Fd *f = g_fds.p + fd;
|
||||
sigset_t m = __sig_block();
|
||||
rc = __winsock_block(f->handle, flags, !!(f->flags & O_NONBLOCK), f->rcvtimeo,
|
||||
m, sys_recv_nt_start, &(struct RecvArgs){iov, iovlen});
|
||||
__sig_unblock(m);
|
||||
return rc;
|
||||
}
|
||||
|
||||
#endif /* __x86_64__ */
|
||||
|
|
|
@ -37,13 +37,13 @@
|
|||
* @return number of bytes received, 0 on remote close, or -1 w/ errno
|
||||
* @error EINTR, EHOSTUNREACH, ECONNRESET (UDP ICMP Port Unreachable),
|
||||
* EPIPE (if MSG_NOSIGNAL), EMSGSIZE, ENOTSOCK, EFAULT, etc.
|
||||
* @cancellationpoint
|
||||
* @cancelationpoint
|
||||
* @asyncsignalsafe
|
||||
* @restartable (unless SO_RCVTIMEO)
|
||||
*/
|
||||
ssize_t recv(int fd, void *buf, size_t size, int flags) {
|
||||
ssize_t rc;
|
||||
BEGIN_CANCELLATION_POINT;
|
||||
BEGIN_CANCELATION_POINT;
|
||||
|
||||
if (IsAsan() && !__asan_is_valid(buf, size)) {
|
||||
rc = efault();
|
||||
|
@ -67,7 +67,7 @@ ssize_t recv(int fd, void *buf, size_t size, int flags) {
|
|||
rc = ebadf();
|
||||
}
|
||||
|
||||
END_CANCELLATION_POINT;
|
||||
END_CANCELATION_POINT;
|
||||
DATATRACE("recv(%d, [%#.*hhs%s], %'zu, %#x) → %'ld% lm", fd,
|
||||
MAX(0, MIN(40, rc)), buf, rc > 40 ? "..." : "", size, flags);
|
||||
return rc;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8
|
||||
-*-│ │vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
|
||||
/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
|
||||
│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
|
||||
╞══════════════════════════════════════════════════════════════════════════════╡
|
||||
│ Copyright 2020 Justine Alexandra Roberts Tunney │
|
||||
│ Copyright 2023 Justine Alexandra Roberts Tunney │
|
||||
│ │
|
||||
│ Permission to use, copy, modify, and/or distribute this software for │
|
||||
│ any purpose with or without fee is hereby granted, provided that the │
|
||||
|
@ -17,40 +17,46 @@
|
|||
│ PERFORMANCE OF THIS SOFTWARE. │
|
||||
╚─────────────────────────────────────────────────────────────────────────────*/
|
||||
#include "libc/calls/internal.h"
|
||||
#include "libc/calls/struct/fd.internal.h"
|
||||
#include "libc/calls/struct/iovec.h"
|
||||
#include "libc/errno.h"
|
||||
#include "libc/nt/struct/overlapped.h"
|
||||
#include "libc/calls/struct/sigset.internal.h"
|
||||
#include "libc/nt/struct/iovec.h"
|
||||
#include "libc/nt/winsock.h"
|
||||
#include "libc/sock/internal.h"
|
||||
#include "libc/sock/syscall_fd.internal.h"
|
||||
#include "libc/sysv/errfuns.h"
|
||||
#include "libc/sysv/consts/o.h"
|
||||
#ifdef __x86_64__
|
||||
|
||||
struct RecvFromArgs {
|
||||
const struct iovec *iov;
|
||||
size_t iovlen;
|
||||
void *opt_out_srcaddr;
|
||||
uint32_t *opt_inout_srcaddrsize;
|
||||
struct NtIovec iovnt[16];
|
||||
};
|
||||
|
||||
static textwindows int sys_recvfrom_nt_start(int64_t handle,
|
||||
struct NtOverlapped *overlap,
|
||||
uint32_t *flags, void *arg) {
|
||||
struct RecvFromArgs *args = arg;
|
||||
return WSARecvFrom(
|
||||
handle, args->iovnt, __iovec2nt(args->iovnt, args->iov, args->iovlen), 0,
|
||||
flags, args->opt_out_srcaddr, args->opt_inout_srcaddrsize, overlap, 0);
|
||||
}
|
||||
|
||||
textwindows ssize_t sys_recvfrom_nt(int fd, const struct iovec *iov,
|
||||
size_t iovlen, uint32_t flags,
|
||||
void *opt_out_srcaddr,
|
||||
uint32_t *opt_inout_srcaddrsize) {
|
||||
int err;
|
||||
ssize_t rc;
|
||||
uint32_t got;
|
||||
struct SockFd *sockfd;
|
||||
struct NtIovec iovnt[16];
|
||||
struct NtOverlapped overlapped = {.hEvent = WSACreateEvent()};
|
||||
err = errno;
|
||||
if (!WSARecvFrom(g_fds.p[fd].handle, iovnt, __iovec2nt(iovnt, iov, iovlen), 0,
|
||||
&flags, opt_out_srcaddr, opt_inout_srcaddrsize, &overlapped,
|
||||
NULL)) {
|
||||
if (WSAGetOverlappedResult(g_fds.p[fd].handle, &overlapped, &got, false,
|
||||
&flags)) {
|
||||
rc = got;
|
||||
} else {
|
||||
rc = -1;
|
||||
}
|
||||
} else {
|
||||
errno = err;
|
||||
sockfd = (struct SockFd *)g_fds.p[fd].extra;
|
||||
rc = __wsablock(g_fds.p + fd, &overlapped, &flags, kSigOpRestartable,
|
||||
sockfd->rcvtimeo);
|
||||
}
|
||||
WSACloseEvent(overlapped.hEvent);
|
||||
struct Fd *f = g_fds.p + fd;
|
||||
sigset_t m = __sig_block();
|
||||
rc = __winsock_block(f->handle, flags, !!(f->flags & O_NONBLOCK), f->rcvtimeo,
|
||||
m, sys_recvfrom_nt_start,
|
||||
&(struct RecvFromArgs){iov, iovlen, opt_out_srcaddr,
|
||||
opt_inout_srcaddrsize});
|
||||
__sig_unblock(m);
|
||||
return rc;
|
||||
}
|
||||
|
||||
#endif /* __x86_64__ */
|
||||
|
|
|
@ -48,7 +48,7 @@
|
|||
* @return number of bytes received, 0 on remote close, or -1 w/ errno
|
||||
* @error EINTR, EHOSTUNREACH, ECONNRESET (UDP ICMP Port Unreachable),
|
||||
* EPIPE (if MSG_NOSIGNAL), EMSGSIZE, ENOTSOCK, EFAULT, etc.
|
||||
* @cancellationpoint
|
||||
* @cancelationpoint
|
||||
* @asyncsignalsafe
|
||||
* @restartable (unless SO_RCVTIMEO)
|
||||
*/
|
||||
|
@ -58,7 +58,7 @@ ssize_t recvfrom(int fd, void *buf, size_t size, int flags,
|
|||
ssize_t rc;
|
||||
struct sockaddr_storage addr = {0};
|
||||
uint32_t addrsize = sizeof(addr);
|
||||
BEGIN_CANCELLATION_POINT;
|
||||
BEGIN_CANCELATION_POINT;
|
||||
|
||||
if (IsAsan() && !__asan_is_valid(buf, size)) {
|
||||
rc = efault();
|
||||
|
@ -90,7 +90,7 @@ ssize_t recvfrom(int fd, void *buf, size_t size, int flags,
|
|||
__write_sockaddr(&addr, opt_out_srcaddr, opt_inout_srcaddrsize);
|
||||
}
|
||||
|
||||
END_CANCELLATION_POINT;
|
||||
END_CANCELATION_POINT;
|
||||
DATATRACE("recvfrom(%d, [%#.*hhs%s], %'zu, %#x) → %'ld% lm", fd,
|
||||
MAX(0, MIN(40, rc)), buf, rc > 40 ? "..." : "", size, flags, rc);
|
||||
return rc;
|
||||
|
|
|
@ -43,7 +43,7 @@
|
|||
* @return number of bytes received, or -1 w/ errno
|
||||
* @error EINTR, EHOSTUNREACH, ECONNRESET (UDP ICMP Port Unreachable),
|
||||
* EPIPE (if MSG_NOSIGNAL), EMSGSIZE, ENOTSOCK, EFAULT, etc.
|
||||
* @cancellationpoint
|
||||
* @cancelationpoint
|
||||
* @asyncsignalsafe
|
||||
* @restartable (unless SO_RCVTIMEO)
|
||||
*/
|
||||
|
@ -52,7 +52,7 @@ ssize_t recvmsg(int fd, struct msghdr *msg, int flags) {
|
|||
struct msghdr msg2;
|
||||
union sockaddr_storage_bsd bsd;
|
||||
|
||||
BEGIN_CANCELLATION_POINT;
|
||||
BEGIN_CANCELATION_POINT;
|
||||
if (IsAsan() && !__asan_is_valid_msghdr(msg)) {
|
||||
rc = efault();
|
||||
} else if (fd < g_fds.n && g_fds.p[fd].kind == kFdZip) {
|
||||
|
@ -97,7 +97,7 @@ ssize_t recvmsg(int fd, struct msghdr *msg, int flags) {
|
|||
} else {
|
||||
rc = ebadf();
|
||||
}
|
||||
END_CANCELLATION_POINT;
|
||||
END_CANCELATION_POINT;
|
||||
|
||||
#if defined(SYSDEBUG) && _DATATRACE
|
||||
if (__strace > 0 && strace_enabled(0) > 0) {
|
||||
|
|
|
@ -17,7 +17,6 @@
|
|||
│ PERFORMANCE OF THIS SOFTWARE. │
|
||||
╚─────────────────────────────────────────────────────────────────────────────*/
|
||||
#include "libc/assert.h"
|
||||
#include "libc/calls/bo.internal.h"
|
||||
#include "libc/calls/internal.h"
|
||||
#include "libc/calls/state.internal.h"
|
||||
#include "libc/calls/struct/timeval.h"
|
||||
|
@ -30,7 +29,6 @@
|
|||
#include "libc/stdckdint.h"
|
||||
#include "libc/sysv/consts/poll.h"
|
||||
#include "libc/sysv/errfuns.h"
|
||||
|
||||
#ifdef __x86_64__
|
||||
|
||||
int sys_select_nt(int nfds, fd_set *readfds, fd_set *writefds,
|
||||
|
@ -71,10 +69,8 @@ int sys_select_nt(int nfds, fd_set *readfds, fd_set *writefds,
|
|||
}
|
||||
|
||||
// call our nt poll implementation
|
||||
BEGIN_BLOCKING_OPERATION;
|
||||
fdcount = sys_poll_nt(fds, pfds, &millis, sigmask);
|
||||
unassert(fdcount < 64);
|
||||
END_BLOCKING_OPERATION;
|
||||
if (fdcount < 0) return -1;
|
||||
|
||||
// convert pollfd back to bitsets
|
||||
|
|
|
@ -40,12 +40,13 @@
|
|||
*
|
||||
* @raise ECANCELED if thread was cancelled in masked mode
|
||||
* @raise EINTR if signal was delivered
|
||||
* @cancellationpoint
|
||||
* @cancelationpoint
|
||||
* @asyncsignalsafe
|
||||
* @norestart
|
||||
*/
|
||||
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds,
|
||||
struct timeval *timeout) {
|
||||
|
||||
int rc;
|
||||
#ifdef SYSDEBUG
|
||||
fd_set old_readfds;
|
||||
|
@ -61,7 +62,7 @@ int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds,
|
|||
POLLTRACE("select(%d, %p, %p, %p, %s) → ...", nfds, readfds, writefds,
|
||||
exceptfds, DescribeTimeval(0, timeout));
|
||||
|
||||
BEGIN_CANCELLATION_POINT;
|
||||
BEGIN_CANCELATION_POINT;
|
||||
if (nfds < 0) {
|
||||
rc = einval();
|
||||
} else if (IsAsan() &&
|
||||
|
@ -109,7 +110,7 @@ int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds,
|
|||
rc = sys_select_nt(nfds, readfds, writefds, exceptfds, timeout, 0);
|
||||
}
|
||||
}
|
||||
END_CANCELLATION_POINT;
|
||||
END_CANCELATION_POINT;
|
||||
|
||||
STRACE("select(%d, %s → [%s], %s → [%s], %s → [%s], %s → [%s]) → %d% m", nfds,
|
||||
DescribeFdSet(rc, nfds, old_readfds_ptr),
|
||||
|
|
|
@ -17,33 +17,40 @@
|
|||
│ PERFORMANCE OF THIS SOFTWARE. │
|
||||
╚─────────────────────────────────────────────────────────────────────────────*/
|
||||
#include "libc/calls/internal.h"
|
||||
#include "libc/calls/struct/fd.internal.h"
|
||||
#include "libc/calls/struct/iovec.h"
|
||||
#include "libc/nt/struct/overlapped.h"
|
||||
#include "libc/calls/struct/sigset.internal.h"
|
||||
#include "libc/nt/struct/iovec.h"
|
||||
#include "libc/nt/winsock.h"
|
||||
#include "libc/sock/internal.h"
|
||||
#include "libc/sock/syscall_fd.internal.h"
|
||||
#include "libc/sysv/errfuns.h"
|
||||
#include "libc/sysv/consts/o.h"
|
||||
#ifdef __x86_64__
|
||||
|
||||
struct SendArgs {
|
||||
const struct iovec *iov;
|
||||
size_t iovlen;
|
||||
struct NtIovec iovnt[16];
|
||||
};
|
||||
|
||||
static textwindows int sys_send_nt_start(int64_t handle,
|
||||
struct NtOverlapped *overlap,
|
||||
uint32_t *flags, void *arg) {
|
||||
struct SendArgs *args = arg;
|
||||
return WSASend(handle, args->iovnt,
|
||||
__iovec2nt(args->iovnt, args->iov, args->iovlen), 0, *flags,
|
||||
overlap, 0);
|
||||
}
|
||||
|
||||
textwindows ssize_t sys_send_nt(int fd, const struct iovec *iov, size_t iovlen,
|
||||
uint32_t flags) {
|
||||
ssize_t rc;
|
||||
uint32_t sent;
|
||||
struct SockFd *sockfd;
|
||||
struct NtIovec iovnt[16];
|
||||
struct NtOverlapped overlapped = {.hEvent = WSACreateEvent()};
|
||||
if (!WSASend(g_fds.p[fd].handle, iovnt, __iovec2nt(iovnt, iov, iovlen), 0,
|
||||
flags, &overlapped, NULL)) {
|
||||
if (WSAGetOverlappedResult(g_fds.p[fd].handle, &overlapped, &sent, false,
|
||||
&flags)) {
|
||||
rc = sent;
|
||||
} else {
|
||||
rc = -1;
|
||||
}
|
||||
} else {
|
||||
sockfd = (struct SockFd *)g_fds.p[fd].extra;
|
||||
rc = __wsablock(g_fds.p + fd, &overlapped, &flags, kSigOpRestartable,
|
||||
sockfd->sndtimeo);
|
||||
}
|
||||
WSACloseEvent(overlapped.hEvent);
|
||||
struct Fd *f = g_fds.p + fd;
|
||||
sigset_t m = __sig_block();
|
||||
rc = __winsock_block(f->handle, flags, !!(f->flags & O_NONBLOCK), f->sndtimeo,
|
||||
m, sys_send_nt_start, &(struct SendArgs){iov, iovlen});
|
||||
__sig_unblock(m);
|
||||
return rc;
|
||||
}
|
||||
|
||||
#endif /* __x86_64__ */
|
||||
|
|
|
@ -38,13 +38,13 @@
|
|||
* @return number of bytes transmitted, or -1 w/ errno
|
||||
* @error EINTR, EHOSTUNREACH, ECONNRESET (UDP ICMP Port Unreachable),
|
||||
* EPIPE (if MSG_NOSIGNAL), EMSGSIZE, ENOTSOCK, EFAULT, etc.
|
||||
* @cancellationpoint
|
||||
* @cancelationpoint
|
||||
* @asyncsignalsafe
|
||||
* @restartable (unless SO_RCVTIMEO)
|
||||
*/
|
||||
ssize_t send(int fd, const void *buf, size_t size, int flags) {
|
||||
ssize_t rc;
|
||||
BEGIN_CANCELLATION_POINT;
|
||||
BEGIN_CANCELATION_POINT;
|
||||
|
||||
if (IsAsan() && !__asan_is_valid(buf, size)) {
|
||||
rc = efault();
|
||||
|
@ -68,7 +68,7 @@ ssize_t send(int fd, const void *buf, size_t size, int flags) {
|
|||
rc = ebadf();
|
||||
}
|
||||
|
||||
END_CANCELLATION_POINT;
|
||||
END_CANCELATION_POINT;
|
||||
DATATRACE("send(%d, %#.*hhs%s, %'zu, %#x) → %'ld% lm", fd,
|
||||
MAX(0, MIN(40, rc)), buf, rc > 40 ? "..." : "", size, flags, rc);
|
||||
return rc;
|
||||
|
|
|
@ -17,88 +17,41 @@
|
|||
│ PERFORMANCE OF THIS SOFTWARE. │
|
||||
╚─────────────────────────────────────────────────────────────────────────────*/
|
||||
#include "libc/assert.h"
|
||||
#include "libc/calls/bo.internal.h"
|
||||
#include "libc/calls/calls.h"
|
||||
#include "libc/calls/internal.h"
|
||||
#include "libc/calls/sig.internal.h"
|
||||
#include "libc/calls/struct/fd.internal.h"
|
||||
#include "libc/calls/struct/sigset.internal.h"
|
||||
#include "libc/calls/syscall-sysv.internal.h"
|
||||
#include "libc/calls/syscall_support-nt.internal.h"
|
||||
#include "libc/dce.h"
|
||||
#include "libc/errno.h"
|
||||
#include "libc/intrin/asan.internal.h"
|
||||
#include "libc/intrin/describeflags.internal.h"
|
||||
#include "libc/intrin/safemacros.internal.h"
|
||||
#include "libc/intrin/strace.internal.h"
|
||||
#include "libc/macros.internal.h"
|
||||
#include "libc/nt/enum/filetype.h"
|
||||
#include "libc/nt/enum/wait.h"
|
||||
#include "libc/nt/errors.h"
|
||||
#include "libc/nt/files.h"
|
||||
#include "libc/nt/struct/byhandlefileinformation.h"
|
||||
#include "libc/nt/struct/overlapped.h"
|
||||
#include "libc/nt/winsock.h"
|
||||
#include "libc/sock/internal.h"
|
||||
#include "libc/sock/sendfile.internal.h"
|
||||
#include "libc/str/str.h"
|
||||
#include "libc/sock/sock.h"
|
||||
#include "libc/stdio/sysparam.h"
|
||||
#include "libc/sysv/errfuns.h"
|
||||
#include "libc/thread/posixthread.internal.h"
|
||||
|
||||
// sendfile() isn't specified as raising eintr
|
||||
static textwindows int SendfileBlock(int64_t handle,
|
||||
struct NtOverlapped *overlapped) {
|
||||
struct PosixThread *pt;
|
||||
uint32_t i, got, flags = 0;
|
||||
if (WSAGetLastError() != kNtErrorIoPending &&
|
||||
WSAGetLastError() != WSAEINPROGRESS) {
|
||||
NTTRACE("TransmitFile failed %lm");
|
||||
return __winsockerr();
|
||||
}
|
||||
pt = _pthread_self();
|
||||
pt->abort_errno = 0;
|
||||
pt->ioverlap = overlapped;
|
||||
pt->iohandle = handle;
|
||||
for (;;) {
|
||||
i = WSAWaitForMultipleEvents(1, &overlapped->hEvent, true,
|
||||
__SIG_IO_INTERVAL_MS, true);
|
||||
if (i == kNtWaitFailed) {
|
||||
NTTRACE("WSAWaitForMultipleEvents failed %lm");
|
||||
return __winsockerr();
|
||||
} else if (i == kNtWaitTimeout || i == kNtWaitIoCompletion) {
|
||||
if (_check_interrupts(kSigOpRestartable)) return -1;
|
||||
#if _NTTRACE
|
||||
POLLTRACE("WSAWaitForMultipleEvents...");
|
||||
#endif
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
pt->ioverlap = 0;
|
||||
pt->iohandle = 0;
|
||||
if (WSAGetOverlappedResult(handle, overlapped, &got, false, &flags)) {
|
||||
return got;
|
||||
} else {
|
||||
if (WSAGetLastError() == kNtErrorOperationAborted) {
|
||||
errno = pt->abort_errno;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
static dontinline textwindows ssize_t sys_sendfile_nt(
|
||||
int outfd, int infd, int64_t *opt_in_out_inoffset, uint32_t uptobytes) {
|
||||
ssize_t rc;
|
||||
int64_t ih, oh, pos, eof, offset;
|
||||
uint32_t flags = 0;
|
||||
int64_t ih, oh, eof, offset;
|
||||
struct NtByHandleFileInformation wst;
|
||||
if (!__isfdkind(infd, kFdFile)) return ebadf();
|
||||
if (!__isfdkind(outfd, kFdSocket)) return ebadf();
|
||||
ih = g_fds.p[infd].handle;
|
||||
oh = g_fds.p[outfd].handle;
|
||||
if (!SetFilePointerEx(ih, 0, &pos, SEEK_CUR)) {
|
||||
return __winerr();
|
||||
}
|
||||
if (opt_in_out_inoffset) {
|
||||
offset = *opt_in_out_inoffset;
|
||||
} else {
|
||||
offset = pos;
|
||||
offset = g_fds.p[infd].pointer;
|
||||
}
|
||||
if (GetFileInformationByHandle(ih, &wst)) {
|
||||
// TransmitFile() returns EINVAL if `uptobytes` goes past EOF.
|
||||
|
@ -109,26 +62,26 @@ static dontinline textwindows ssize_t sys_sendfile_nt(
|
|||
} else {
|
||||
return ebadf();
|
||||
}
|
||||
struct NtOverlapped ov = {
|
||||
.Pointer = offset,
|
||||
.hEvent = WSACreateEvent(),
|
||||
};
|
||||
if (TransmitFile(oh, ih, uptobytes, 0, &ov, 0, 0)) {
|
||||
rc = uptobytes;
|
||||
} else {
|
||||
BEGIN_BLOCKING_OPERATION;
|
||||
rc = SendfileBlock(oh, &ov);
|
||||
END_BLOCKING_OPERATION;
|
||||
}
|
||||
if (rc != -1) {
|
||||
if (opt_in_out_inoffset) {
|
||||
*opt_in_out_inoffset = offset + rc;
|
||||
npassert(SetFilePointerEx(ih, pos, 0, SEEK_SET));
|
||||
BLOCK_SIGNALS;
|
||||
struct NtOverlapped ov = {.hEvent = WSACreateEvent(), .Pointer = offset};
|
||||
if (TransmitFile(oh, ih, uptobytes, 0, &ov, 0, 0) ||
|
||||
WSAGetLastError() == kNtErrorIoPending ||
|
||||
WSAGetLastError() == WSAEINPROGRESS) {
|
||||
if (WSAGetOverlappedResult(oh, &ov, &uptobytes, true, &flags)) {
|
||||
rc = uptobytes;
|
||||
if (opt_in_out_inoffset) {
|
||||
*opt_in_out_inoffset = offset + rc;
|
||||
} else {
|
||||
g_fds.p[infd].pointer = offset + rc;
|
||||
}
|
||||
} else {
|
||||
npassert(SetFilePointerEx(ih, offset + rc, 0, SEEK_SET));
|
||||
rc = __winsockerr();
|
||||
}
|
||||
} else {
|
||||
rc = __winsockerr();
|
||||
}
|
||||
WSACloseEvent(ov.hEvent);
|
||||
ALLOW_SIGNALS;
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
@ -154,7 +107,8 @@ static ssize_t sys_sendfile_bsd(int outfd, int infd,
|
|||
if (opt_in_out_inoffset) {
|
||||
*opt_in_out_inoffset += sbytes;
|
||||
} else {
|
||||
npassert(lseek(infd, offset + sbytes, SEEK_SET) == offset + sbytes);
|
||||
unassert(sys_lseek(infd, offset + sbytes, SEEK_SET, 0) ==
|
||||
offset + sbytes);
|
||||
}
|
||||
return sbytes;
|
||||
} else {
|
||||
|
|
|
@ -45,7 +45,7 @@
|
|||
* @return number of bytes transmitted, or -1 w/ errno
|
||||
* @error EINTR, EHOSTUNREACH, ECONNRESET (UDP ICMP Port Unreachable),
|
||||
* EPIPE (if MSG_NOSIGNAL), EMSGSIZE, ENOTSOCK, EFAULT, etc.
|
||||
* @cancellationpoint
|
||||
* @cancelationpoint
|
||||
* @asyncsignalsafe
|
||||
* @restartable (unless SO_RCVTIMEO)
|
||||
*/
|
||||
|
@ -54,7 +54,7 @@ ssize_t sendmsg(int fd, const struct msghdr *msg, int flags) {
|
|||
struct msghdr msg2;
|
||||
union sockaddr_storage_bsd bsd;
|
||||
|
||||
BEGIN_CANCELLATION_POINT;
|
||||
BEGIN_CANCELATION_POINT;
|
||||
if (IsAsan() && !__asan_is_valid_msghdr(msg)) {
|
||||
rc = efault();
|
||||
} else if (fd < g_fds.n && g_fds.p[fd].kind == kFdZip) {
|
||||
|
@ -84,7 +84,7 @@ ssize_t sendmsg(int fd, const struct msghdr *msg, int flags) {
|
|||
} else {
|
||||
rc = ebadf();
|
||||
}
|
||||
END_CANCELLATION_POINT;
|
||||
END_CANCELATION_POINT;
|
||||
|
||||
#if defined(SYSDEBUG) && _DATATRACE
|
||||
if (__strace > 0 && strace_enabled(0) > 0) {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
|
||||
│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
|
||||
╞══════════════════════════════════════════════════════════════════════════════╡
|
||||
│ Copyright 2020 Justine Alexandra Roberts Tunney │
|
||||
│ Copyright 2023 Justine Alexandra Roberts Tunney │
|
||||
│ │
|
||||
│ Permission to use, copy, modify, and/or distribute this software for │
|
||||
│ any purpose with or without fee is hereby granted, provided that the │
|
||||
|
@ -17,34 +17,45 @@
|
|||
│ PERFORMANCE OF THIS SOFTWARE. │
|
||||
╚─────────────────────────────────────────────────────────────────────────────*/
|
||||
#include "libc/calls/internal.h"
|
||||
#include "libc/calls/struct/fd.internal.h"
|
||||
#include "libc/calls/struct/iovec.h"
|
||||
#include "libc/nt/struct/overlapped.h"
|
||||
#include "libc/calls/struct/sigset.internal.h"
|
||||
#include "libc/nt/struct/iovec.h"
|
||||
#include "libc/nt/winsock.h"
|
||||
#include "libc/sock/internal.h"
|
||||
#include "libc/sock/syscall_fd.internal.h"
|
||||
#include "libc/sysv/errfuns.h"
|
||||
#include "libc/sysv/consts/o.h"
|
||||
#ifdef __x86_64__
|
||||
|
||||
struct SendToArgs {
|
||||
const struct iovec *iov;
|
||||
size_t iovlen;
|
||||
void *opt_in_addr;
|
||||
uint32_t in_addrsize;
|
||||
struct NtIovec iovnt[16];
|
||||
};
|
||||
|
||||
static textwindows int sys_sendto_nt_start(int64_t handle,
|
||||
struct NtOverlapped *overlap,
|
||||
uint32_t *flags, void *arg) {
|
||||
struct SendToArgs *args = arg;
|
||||
return WSASendTo(handle, args->iovnt,
|
||||
__iovec2nt(args->iovnt, args->iov, args->iovlen), 0, *flags,
|
||||
args->opt_in_addr, args->in_addrsize, overlap, 0);
|
||||
}
|
||||
|
||||
textwindows ssize_t sys_sendto_nt(int fd, const struct iovec *iov,
|
||||
size_t iovlen, uint32_t flags,
|
||||
void *opt_in_addr, uint32_t in_addrsize) {
|
||||
ssize_t rc;
|
||||
uint32_t sent = 0;
|
||||
struct SockFd *sockfd;
|
||||
struct NtIovec iovnt[16];
|
||||
struct NtOverlapped overlapped = {.hEvent = WSACreateEvent()};
|
||||
if (!WSASendTo(g_fds.p[fd].handle, iovnt, __iovec2nt(iovnt, iov, iovlen), 0,
|
||||
flags, opt_in_addr, in_addrsize, &overlapped, NULL)) {
|
||||
if (WSAGetOverlappedResult(g_fds.p[fd].handle, &overlapped, &sent, false,
|
||||
&flags)) {
|
||||
rc = sent;
|
||||
} else {
|
||||
rc = -1;
|
||||
}
|
||||
} else {
|
||||
sockfd = (struct SockFd *)g_fds.p[fd].extra;
|
||||
rc = __wsablock(g_fds.p + fd, &overlapped, &flags, kSigOpRestartable,
|
||||
sockfd->sndtimeo);
|
||||
}
|
||||
WSACloseEvent(overlapped.hEvent);
|
||||
struct Fd *f = g_fds.p + fd;
|
||||
sigset_t m = __sig_block();
|
||||
rc = __winsock_block(
|
||||
f->handle, flags, !!(f->flags & O_NONBLOCK), f->sndtimeo, m,
|
||||
sys_sendto_nt_start,
|
||||
&(struct SendToArgs){iov, iovlen, opt_in_addr, in_addrsize});
|
||||
__sig_unblock(m);
|
||||
return rc;
|
||||
}
|
||||
|
||||
#endif /* __x86_64__ */
|
||||
|
|
|
@ -50,7 +50,7 @@
|
|||
* @return number of bytes transmitted, or -1 w/ errno
|
||||
* @error EINTR, EHOSTUNREACH, ECONNRESET (UDP ICMP Port Unreachable),
|
||||
* EPIPE (if MSG_NOSIGNAL), EMSGSIZE, ENOTSOCK, EFAULT, etc.
|
||||
* @cancellationpoint
|
||||
* @cancelationpoint
|
||||
* @asyncsignalsafe
|
||||
* @restartable (unless SO_RCVTIMEO)
|
||||
*/
|
||||
|
@ -59,7 +59,7 @@ ssize_t sendto(int fd, const void *buf, size_t size, int flags,
|
|||
ssize_t rc;
|
||||
uint32_t bsdaddrsize;
|
||||
union sockaddr_storage_bsd bsd;
|
||||
BEGIN_CANCELLATION_POINT;
|
||||
BEGIN_CANCELATION_POINT;
|
||||
|
||||
if (IsAsan() && (!__asan_is_valid(buf, size) ||
|
||||
(opt_addr && !__asan_is_valid(opt_addr, addrsize)))) {
|
||||
|
@ -91,7 +91,7 @@ ssize_t sendto(int fd, const void *buf, size_t size, int flags,
|
|||
rc = ebadf();
|
||||
}
|
||||
|
||||
END_CANCELLATION_POINT;
|
||||
END_CANCELATION_POINT;
|
||||
DATATRACE("sendto(%d, %#.*hhs%s, %'zu, %#x, %p, %u) → %'ld% lm", fd,
|
||||
MAX(0, MIN(40, rc)), buf, rc > 40 ? "..." : "", size, flags,
|
||||
opt_addr, addrsize, rc);
|
||||
|
|
|
@ -16,61 +16,50 @@
|
|||
│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │
|
||||
│ PERFORMANCE OF THIS SOFTWARE. │
|
||||
╚─────────────────────────────────────────────────────────────────────────────*/
|
||||
#include "libc/calls/struct/fd.internal.h"
|
||||
#include "libc/calls/struct/timeval.h"
|
||||
#include "libc/limits.h"
|
||||
#include "libc/macros.internal.h"
|
||||
#include "libc/nt/struct/linger.h"
|
||||
#include "libc/nt/thunk/msabi.h"
|
||||
#include "libc/nt/winsock.h"
|
||||
#include "libc/sock/internal.h"
|
||||
#include "libc/sock/struct/linger.h"
|
||||
#include "libc/sock/syscall_fd.internal.h"
|
||||
#include "libc/stdckdint.h"
|
||||
#include "libc/stdio/sysparam.h"
|
||||
#include "libc/sysv/consts/so.h"
|
||||
#include "libc/sysv/consts/sol.h"
|
||||
#include "libc/sysv/errfuns.h"
|
||||
#ifdef __x86_64__
|
||||
|
||||
__msabi extern typeof(__sys_setsockopt_nt) *const __imp_setsockopt;
|
||||
|
||||
textwindows int sys_setsockopt_nt(struct Fd *fd, int level, int optname,
|
||||
const void *optval, uint32_t optlen) {
|
||||
int64_t ms, micros;
|
||||
struct SockFd *sockfd;
|
||||
const struct timeval *tv;
|
||||
const struct linger *linger;
|
||||
|
||||
// socket read/write timeouts
|
||||
if (level == SOL_SOCKET &&
|
||||
(optname == SO_RCVTIMEO || optname == SO_SNDTIMEO)) {
|
||||
if (!(optval && optlen == sizeof(struct timeval))) return einval();
|
||||
const struct timeval *tv = optval;
|
||||
int64_t ms = timeval_tomillis(*tv);
|
||||
if (ms >= 0xffffffffu) ms = 0; // wait forever (default)
|
||||
if (optname == SO_RCVTIMEO) fd->rcvtimeo = ms;
|
||||
if (optname == SO_SNDTIMEO) fd->sndtimeo = ms;
|
||||
return 0; // we want to handle this on our own
|
||||
}
|
||||
|
||||
// how to make close() a blocking i/o call
|
||||
union {
|
||||
uint32_t millis;
|
||||
struct linger_nt linger;
|
||||
} u;
|
||||
|
||||
if (level == SOL_SOCKET) {
|
||||
if (optname == SO_LINGER && optval && optlen == sizeof(struct linger)) {
|
||||
linger = optval;
|
||||
u.linger.l_onoff = linger->l_onoff;
|
||||
u.linger.l_linger = MIN(0xFFFF, MAX(0, linger->l_linger));
|
||||
optval = &u.linger;
|
||||
optlen = sizeof(u.linger);
|
||||
} else if ((optname == SO_RCVTIMEO || optname == SO_SNDTIMEO) && optval &&
|
||||
optlen == sizeof(struct timeval)) {
|
||||
tv = optval;
|
||||
if (ckd_mul(&ms, tv->tv_sec, 1000) || //
|
||||
ckd_add(µs, tv->tv_usec, 999) || //
|
||||
ckd_add(&ms, ms, micros / 1000) || //
|
||||
(ms < 0 || ms > 0xffffffff)) {
|
||||
u.millis = 0xffffffff;
|
||||
} else {
|
||||
u.millis = ms;
|
||||
}
|
||||
optval = &u.millis;
|
||||
optlen = sizeof(u.millis);
|
||||
sockfd = (struct SockFd *)fd->extra;
|
||||
if (optname == SO_RCVTIMEO) {
|
||||
sockfd->rcvtimeo = u.millis;
|
||||
}
|
||||
if (optname == SO_SNDTIMEO) {
|
||||
sockfd->sndtimeo = u.millis;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
if (level == SOL_SOCKET && //
|
||||
optname == SO_LINGER && optval && //
|
||||
optlen == sizeof(struct linger)) {
|
||||
const struct linger *linger = optval;
|
||||
u.linger.l_onoff = linger->l_onoff;
|
||||
u.linger.l_linger = MIN(0xFFFF, MAX(0, linger->l_linger));
|
||||
optval = &u.linger;
|
||||
optlen = sizeof(u.linger);
|
||||
}
|
||||
|
||||
if (__imp_setsockopt(fd->handle, level, optname, optval, optlen) != -1) {
|
||||
|
@ -79,3 +68,5 @@ textwindows int sys_setsockopt_nt(struct Fd *fd, int level, int optname,
|
|||
return __winsockerr();
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* __x86_64__ */
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
#include "libc/nt/winsock.h"
|
||||
#include "libc/sock/internal.h"
|
||||
#include "libc/sock/syscall_fd.internal.h"
|
||||
#ifdef __x86_64__
|
||||
|
||||
__msabi extern typeof(__sys_shutdown_nt) *const __imp_shutdown;
|
||||
|
||||
|
@ -30,3 +31,5 @@ textwindows int sys_shutdown_nt(struct Fd *fd, int how) {
|
|||
return __winsockerr();
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* __x86_64__ */
|
||||
|
|
|
@ -21,7 +21,6 @@ uint32_t ntohl(uint32_t) pureconst;
|
|||
const char *inet_ntop(int, const void *, char *, uint32_t);
|
||||
int inet_pton(int, const char *, void *);
|
||||
uint32_t inet_addr(const char *);
|
||||
int parseport(const char *);
|
||||
uint32_t *GetHostIps(void);
|
||||
|
||||
int socket(int, int, int);
|
||||
|
|
|
@ -26,7 +26,6 @@
|
|||
#include "libc/nt/thunk/msabi.h"
|
||||
#include "libc/nt/winsock.h"
|
||||
#include "libc/sock/internal.h"
|
||||
#include "libc/sock/yoink.inc"
|
||||
#include "libc/str/str.h"
|
||||
#include "libc/sysv/consts/af.h"
|
||||
#include "libc/sysv/consts/ipproto.h"
|
||||
|
@ -34,6 +33,8 @@
|
|||
#include "libc/sysv/consts/so.h"
|
||||
#include "libc/sysv/consts/sock.h"
|
||||
#include "libc/sysv/consts/sol.h"
|
||||
#ifdef __x86_64__
|
||||
#include "libc/sock/yoink.inc"
|
||||
|
||||
__msabi extern typeof(__sys_setsockopt_nt) *const __imp_setsockopt;
|
||||
|
||||
|
@ -44,11 +45,9 @@ __msabi extern typeof(__sys_setsockopt_nt) *const __imp_setsockopt;
|
|||
*/
|
||||
__static_yoink("GetAdaptersAddresses");
|
||||
__static_yoink("tprecode16to8");
|
||||
__static_yoink("_dupsockfd");
|
||||
|
||||
textwindows int sys_socket_nt(int family, int type, int protocol) {
|
||||
int64_t h;
|
||||
struct SockFd *sockfd;
|
||||
int fd, oflags, truetype, yes = 1;
|
||||
fd = __reservefd(-1);
|
||||
if (fd == -1) return -1;
|
||||
|
@ -67,17 +66,13 @@ textwindows int sys_socket_nt(int family, int type, int protocol) {
|
|||
oflags = O_RDWR;
|
||||
if (type & SOCK_CLOEXEC) oflags |= O_CLOEXEC;
|
||||
if (type & SOCK_NONBLOCK) oflags |= O_NONBLOCK;
|
||||
sockfd = calloc(1, sizeof(struct SockFd));
|
||||
sockfd->family = family;
|
||||
sockfd->type = truetype;
|
||||
sockfd->protocol = protocol;
|
||||
__fds_lock();
|
||||
g_fds.p[fd].family = family;
|
||||
g_fds.p[fd].type = truetype;
|
||||
g_fds.p[fd].protocol = protocol;
|
||||
g_fds.p[fd].kind = kFdSocket;
|
||||
g_fds.p[fd].flags = oflags;
|
||||
g_fds.p[fd].mode = 0140666;
|
||||
g_fds.p[fd].handle = h;
|
||||
g_fds.p[fd].extra = (uintptr_t)sockfd;
|
||||
__fds_unlock();
|
||||
|
||||
return fd;
|
||||
} else {
|
||||
|
@ -85,3 +80,5 @@ textwindows int sys_socket_nt(int family, int type, int protocol) {
|
|||
return __winsockerr();
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* __x86_64__ */
|
||||
|
|
|
@ -30,6 +30,7 @@
|
|||
#include "libc/sysv/consts/o.h"
|
||||
#include "libc/sysv/consts/sock.h"
|
||||
#include "libc/sysv/errfuns.h"
|
||||
#ifdef __x86_64__
|
||||
|
||||
textwindows int sys_socketpair_nt(int family, int type, int proto, int sv[2]) {
|
||||
uint32_t mode;
|
||||
|
@ -64,16 +65,16 @@ textwindows int sys_socketpair_nt(int family, int type, int proto, int sv[2]) {
|
|||
if (writer != -1) __releasefd(writer);
|
||||
return -1;
|
||||
}
|
||||
if ((hpipe = CreateNamedPipe(pipename,
|
||||
kNtPipeAccessDuplex | kNtFileFlagOverlapped,
|
||||
mode, 1, 65536, 65536, 0, 0)) == -1) {
|
||||
if ((hpipe = CreateNamedPipe(
|
||||
pipename, kNtPipeAccessDuplex | kNtFileFlagOverlapped, mode, 1,
|
||||
65536, 65536, 0, &kNtIsInheritable)) == -1) {
|
||||
__releasefd(writer);
|
||||
__releasefd(reader);
|
||||
return -1;
|
||||
}
|
||||
|
||||
h1 = CreateFile(pipename, kNtGenericWrite | kNtGenericRead, 0, 0,
|
||||
kNtOpenExisting, kNtFileFlagOverlapped, 0);
|
||||
h1 = CreateFile(pipename, kNtGenericWrite | kNtGenericRead, 0,
|
||||
&kNtIsInheritable, kNtOpenExisting, kNtFileFlagOverlapped, 0);
|
||||
|
||||
__fds_lock();
|
||||
|
||||
|
@ -104,3 +105,5 @@ textwindows int sys_socketpair_nt(int family, int type, int proto, int sv[2]) {
|
|||
|
||||
return rc;
|
||||
}
|
||||
|
||||
#endif /* __x86_64__ */
|
||||
|
|
|
@ -12,8 +12,7 @@ struct pollfd {
|
|||
};
|
||||
|
||||
int poll(struct pollfd *, uint64_t, int32_t);
|
||||
int ppoll(struct pollfd *, uint64_t, const struct timespec *,
|
||||
const struct sigset *);
|
||||
int ppoll(struct pollfd *, uint64_t, const struct timespec *, const sigset_t *);
|
||||
|
||||
COSMOPOLITAN_C_END_
|
||||
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
|
||||
|
|
|
@ -20,7 +20,6 @@ int sys_shutdown_nt(struct Fd *, int);
|
|||
ssize_t sys_recv_nt(int, const struct iovec *, size_t, uint32_t);
|
||||
ssize_t sys_recvfrom_nt(int, const struct iovec *, size_t, uint32_t, void *,
|
||||
uint32_t *);
|
||||
int __wsablock(struct Fd *, struct NtOverlapped *, uint32_t *, int, uint32_t);
|
||||
|
||||
COSMOPOLITAN_C_END_
|
||||
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
|
||||
|
|
|
@ -125,7 +125,7 @@ void vsyslog(int priority, const char *message, va_list ap) {
|
|||
int l, l2;
|
||||
int hlen; /* If LOG_CONS is specified, use to store the point in
|
||||
* the header message after the timestamp */
|
||||
BLOCK_CANCELLATIONS;
|
||||
BLOCK_CANCELATION;
|
||||
if (log_fd < 0) __openlog();
|
||||
if (!(priority & LOG_FACMASK)) priority |= log_facility;
|
||||
/* Build the time string */
|
||||
|
@ -210,7 +210,7 @@ void vsyslog(int priority, const char *message, va_list ap) {
|
|||
dprintf(2, "%.*s", l - hlen, buf + hlen);
|
||||
}
|
||||
}
|
||||
ALLOW_CANCELLATIONS;
|
||||
ALLOW_CANCELATION;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -261,7 +261,7 @@ int setlogmask(int maskpri) {
|
|||
* @asyncsignalsafe
|
||||
*/
|
||||
void openlog(const char *ident, int opt, int facility) {
|
||||
BLOCK_CANCELLATIONS;
|
||||
BLOCK_CANCELATION;
|
||||
if (log_facility == -1) __initlog();
|
||||
if (!ident) ident = firstnonnull(program_invocation_short_name, "unknown");
|
||||
tprecode8to16(log_ident, ARRAYLEN(log_ident), ident);
|
||||
|
@ -269,7 +269,7 @@ void openlog(const char *ident, int opt, int facility) {
|
|||
log_facility = facility;
|
||||
log_id = 0;
|
||||
if ((opt & LOG_NDELAY) && log_fd < 0) __openlog();
|
||||
ALLOW_CANCELLATIONS;
|
||||
ALLOW_CANCELATION;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -17,35 +17,136 @@
|
|||
│ PERFORMANCE OF THIS SOFTWARE. │
|
||||
╚─────────────────────────────────────────────────────────────────────────────*/
|
||||
#include "libc/assert.h"
|
||||
#include "libc/calls/internal.h"
|
||||
#include "libc/calls/struct/fd.internal.h"
|
||||
#include "libc/calls/struct/sigset.internal.h"
|
||||
#include "libc/errno.h"
|
||||
#include "libc/intrin/atomic.h"
|
||||
#include "libc/intrin/kprintf.h"
|
||||
#include "libc/intrin/strace.internal.h"
|
||||
#include "libc/nt/enum/wait.h"
|
||||
#include "libc/nt/errors.h"
|
||||
#include "libc/nt/runtime.h"
|
||||
#include "libc/nt/struct/iovec.h"
|
||||
#include "libc/nt/struct/overlapped.h"
|
||||
#include "libc/nt/thread.h"
|
||||
#include "libc/nt/winsock.h"
|
||||
#include "libc/runtime/runtime.h"
|
||||
#include "libc/sock/internal.h"
|
||||
#include "libc/sock/sock.h"
|
||||
#include "libc/str/str.h"
|
||||
#include "libc/sock/syscall_fd.internal.h"
|
||||
#include "libc/sysv/consts/o.h"
|
||||
#include "libc/sysv/errfuns.h"
|
||||
#include "libc/thread/posixthread.internal.h"
|
||||
#ifdef __x86_64__
|
||||
|
||||
textwindows int64_t __winsockblock(int64_t fh, unsigned eventbit, int64_t rc,
|
||||
uint32_t timeout) {
|
||||
int64_t eh;
|
||||
struct NtWsaNetworkEvents ev;
|
||||
if (rc != -1) return rc;
|
||||
if (WSAGetLastError() != EWOULDBLOCK) return __winsockerr();
|
||||
eh = WSACreateEvent();
|
||||
bzero(&ev, sizeof(ev));
|
||||
/* The proper way to reset the state of an event object used with the
|
||||
WSAEventSelect function is to pass the handle of the event object
|
||||
to the WSAEnumNetworkEvents function in the hEventObject parameter.
|
||||
This will reset the event object and adjust the status of active FD
|
||||
events on the socket in an atomic fashion. -- MSDN */
|
||||
if (WSAEventSelect(fh, eh, 1u << eventbit) != -1 &&
|
||||
WSAEnumNetworkEvents(fh, eh, &ev) != -1) {
|
||||
if (!ev.iErrorCode[eventbit]) {
|
||||
rc = 0;
|
||||
} else {
|
||||
errno = ev.iErrorCode[eventbit];
|
||||
}
|
||||
} else {
|
||||
__winsockerr();
|
||||
}
|
||||
WSACloseEvent(eh);
|
||||
return rc;
|
||||
struct WinsockBlockResources {
|
||||
int64_t handle;
|
||||
struct NtOverlapped *overlap;
|
||||
};
|
||||
|
||||
static void UnwindWinsockBlock(void *arg) {
|
||||
struct WinsockBlockResources *wbr = arg;
|
||||
uint32_t got, flags;
|
||||
CancelIoEx(wbr->handle, wbr->overlap);
|
||||
WSAGetOverlappedResult(wbr->handle, wbr->overlap, &got, true, &flags);
|
||||
WSACloseEvent(wbr->overlap->hEvent);
|
||||
}
|
||||
|
||||
static void CancelWinsockBlock(int64_t handle, struct NtOverlapped *overlap) {
|
||||
if (!CancelIoEx(handle, overlap)) {
|
||||
unassert(WSAGetLastError() == kNtErrorNotFound);
|
||||
}
|
||||
}
|
||||
|
||||
textwindows ssize_t
|
||||
__winsock_block(int64_t handle, uint32_t flags, bool nonblock,
|
||||
uint32_t srwtimeout, sigset_t wait_signal_mask,
|
||||
int StartSocketOp(int64_t handle, struct NtOverlapped *overlap,
|
||||
uint32_t *flags, void *arg),
|
||||
void *arg) {
|
||||
|
||||
int rc;
|
||||
uint64_t m;
|
||||
uint32_t status;
|
||||
uint32_t exchanged;
|
||||
bool eagained = false;
|
||||
bool eintered = false;
|
||||
bool canceled = false;
|
||||
bool olderror = errno;
|
||||
struct PosixThread *pt;
|
||||
struct NtOverlapped overlap = {.hEvent = WSACreateEvent()};
|
||||
struct WinsockBlockResources wbr = {handle, &overlap};
|
||||
|
||||
pthread_cleanup_push(UnwindWinsockBlock, &wbr);
|
||||
rc = StartSocketOp(handle, &overlap, &flags, arg);
|
||||
if (rc && WSAGetLastError() == kNtErrorIoPending) {
|
||||
BlockingOperation:
|
||||
pt = _pthread_self();
|
||||
pt->pt_iohandle = handle;
|
||||
pt->pt_ioverlap = &overlap;
|
||||
pt->pt_flags |= PT_RESTARTABLE;
|
||||
atomic_store_explicit(&pt->pt_blocker, PT_BLOCKER_IO, memory_order_release);
|
||||
m = __sig_beginwait(wait_signal_mask);
|
||||
if (nonblock) {
|
||||
CancelWinsockBlock(handle, &overlap);
|
||||
eagained = true;
|
||||
} else if (_check_cancel()) {
|
||||
CancelWinsockBlock(handle, &overlap);
|
||||
canceled = true;
|
||||
} else if (_check_signal(true)) {
|
||||
CancelWinsockBlock(handle, &overlap);
|
||||
eintered = true;
|
||||
} else {
|
||||
status = WSAWaitForMultipleEvents(1, &overlap.hEvent, 0,
|
||||
srwtimeout ? srwtimeout : -1u, 0);
|
||||
if (status == kNtWaitTimeout) {
|
||||
// rcvtimeo or sndtimeo elapsed
|
||||
CancelWinsockBlock(handle, &overlap);
|
||||
eagained = true;
|
||||
} else if (status == kNtWaitFailed) {
|
||||
// Failure should be an impossible condition, but MSDN lists
|
||||
// WSAENETDOWN and WSA_NOT_ENOUGH_MEMORY as possible errors.
|
||||
CancelWinsockBlock(handle, &overlap);
|
||||
eintered = true;
|
||||
}
|
||||
}
|
||||
__sig_finishwait(m);
|
||||
atomic_store_explicit(&pt->pt_blocker, PT_BLOCKER_CPU,
|
||||
memory_order_release);
|
||||
pt->pt_flags &= ~PT_RESTARTABLE;
|
||||
pt->pt_ioverlap = 0;
|
||||
pt->pt_iohandle = 0;
|
||||
rc = 0;
|
||||
}
|
||||
if (!rc) {
|
||||
bool32 should_wait = canceled || eagained;
|
||||
bool32 ok = WSAGetOverlappedResult(handle, &overlap, &exchanged,
|
||||
should_wait, &flags);
|
||||
if (!ok && WSAGetLastError() == kNtErrorIoIncomplete) {
|
||||
goto BlockingOperation;
|
||||
}
|
||||
rc = ok ? 0 : -1;
|
||||
}
|
||||
WSACloseEvent(overlap.hEvent);
|
||||
pthread_cleanup_pop(false);
|
||||
|
||||
if (canceled) {
|
||||
return ecanceled();
|
||||
}
|
||||
if (!rc) {
|
||||
errno = olderror;
|
||||
return exchanged;
|
||||
}
|
||||
if (eagained) {
|
||||
return eagain();
|
||||
}
|
||||
if (WSAGetLastError() == kNtErrorOperationAborted && _check_cancel()) {
|
||||
return ecanceled();
|
||||
}
|
||||
if (eintered) {
|
||||
return eintr();
|
||||
}
|
||||
return __winsockerr();
|
||||
}
|
||||
|
||||
#endif /* __x86_64__ */
|
||||
|
|
|
@ -1,131 +0,0 @@
|
|||
/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
|
||||
│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
|
||||
╞══════════════════════════════════════════════════════════════════════════════╡
|
||||
│ Copyright 2022 Justine Alexandra Roberts Tunney │
|
||||
│ │
|
||||
│ Permission to use, copy, modify, and/or distribute this software for │
|
||||
│ any purpose with or without fee is hereby granted, provided that the │
|
||||
│ above copyright notice and this permission notice appear in all copies. │
|
||||
│ │
|
||||
│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │
|
||||
│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │
|
||||
│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │
|
||||
│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │
|
||||
│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │
|
||||
│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │
|
||||
│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │
|
||||
│ PERFORMANCE OF THIS SOFTWARE. │
|
||||
╚─────────────────────────────────────────────────────────────────────────────*/
|
||||
#include "libc/assert.h"
|
||||
#include "libc/calls/bo.internal.h"
|
||||
#include "libc/calls/internal.h"
|
||||
#include "libc/calls/sig.internal.h"
|
||||
#include "libc/calls/struct/timespec.h"
|
||||
#include "libc/errno.h"
|
||||
#include "libc/intrin/weaken.h"
|
||||
#include "libc/nt/enum/wait.h"
|
||||
#include "libc/nt/enum/wsa.h"
|
||||
#include "libc/nt/errors.h"
|
||||
#include "libc/nt/runtime.h"
|
||||
#include "libc/nt/synchronization.h"
|
||||
#include "libc/nt/thread.h"
|
||||
#include "libc/nt/winsock.h"
|
||||
#include "libc/runtime/runtime.h"
|
||||
#include "libc/sock/internal.h"
|
||||
#include "libc/sock/sock.h"
|
||||
#include "libc/sock/syscall_fd.internal.h"
|
||||
#include "libc/str/str.h"
|
||||
#include "libc/sysv/consts/o.h"
|
||||
#include "libc/sysv/errfuns.h"
|
||||
#include "libc/thread/posixthread.internal.h"
|
||||
#include "libc/thread/tls.h"
|
||||
|
||||
textwindows int __wsablock(struct Fd *f, struct NtOverlapped *overlapped,
|
||||
uint32_t *flags, int sigops, uint32_t timeout) {
|
||||
bool nonblock;
|
||||
int e, rc, err;
|
||||
uint32_t i, got;
|
||||
uint32_t waitfor;
|
||||
struct PosixThread *pt;
|
||||
struct timespec now, remain, interval, deadline;
|
||||
|
||||
if (WSAGetLastError() != kNtErrorIoPending) {
|
||||
// our i/o operation never happened because it failed
|
||||
return __winsockerr();
|
||||
}
|
||||
|
||||
// our i/o operation is in flight and it needs to block
|
||||
nonblock = !!(f->flags & O_NONBLOCK);
|
||||
pt = _pthread_self();
|
||||
pt->abort_errno = EAGAIN;
|
||||
interval = timespec_frommillis(__SIG_IO_INTERVAL_MS);
|
||||
deadline = timeout
|
||||
? timespec_add(timespec_real(), timespec_frommillis(timeout))
|
||||
: timespec_max;
|
||||
e = errno;
|
||||
BlockingOperation:
|
||||
if (!nonblock) {
|
||||
pt->ioverlap = overlapped;
|
||||
pt->iohandle = f->handle;
|
||||
}
|
||||
if (nonblock) {
|
||||
CancelIoEx(f->handle, overlapped);
|
||||
} else if (_check_interrupts(sigops)) {
|
||||
Interrupted:
|
||||
pt->abort_errno = errno; // EINTR or ECANCELED
|
||||
CancelIoEx(f->handle, overlapped);
|
||||
} else {
|
||||
for (;;) {
|
||||
now = timespec_real();
|
||||
if (timespec_cmp(now, deadline) >= 0) {
|
||||
CancelIoEx(f->handle, overlapped);
|
||||
nonblock = true;
|
||||
break;
|
||||
}
|
||||
remain = timespec_sub(deadline, now);
|
||||
if (timespec_cmp(remain, interval) >= 0) {
|
||||
waitfor = __SIG_IO_INTERVAL_MS;
|
||||
} else {
|
||||
waitfor = timespec_tomillis(remain);
|
||||
}
|
||||
i = WSAWaitForMultipleEvents(1, &overlapped->hEvent, true, waitfor, true);
|
||||
if (i == kNtWaitFailed) {
|
||||
// Failure should be an impossible condition, but MSDN lists
|
||||
// WSAENETDOWN and WSA_NOT_ENOUGH_MEMORY as possible errors.
|
||||
pt->abort_errno = WSAGetLastError();
|
||||
CancelIoEx(f->handle, overlapped);
|
||||
nonblock = true;
|
||||
break;
|
||||
} else if (i == kNtWaitTimeout) {
|
||||
if (_check_interrupts(sigops)) {
|
||||
goto Interrupted;
|
||||
}
|
||||
continue;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
pt->ioverlap = 0;
|
||||
pt->iohandle = 0;
|
||||
|
||||
// overlapped is allocated on stack by caller, so it's important that
|
||||
// we wait for win32 to acknowledge that it's done using that memory.
|
||||
if (WSAGetOverlappedResult(f->handle, overlapped, &got, nonblock, flags)) {
|
||||
rc = got;
|
||||
} else {
|
||||
if (_weaken(pthread_testcancel_np) &&
|
||||
(err = _weaken(pthread_testcancel_np)())) {
|
||||
return ecanceled();
|
||||
}
|
||||
rc = -1;
|
||||
err = WSAGetLastError();
|
||||
if (err == kNtErrorOperationAborted) {
|
||||
errno = pt->abort_errno;
|
||||
} else if (err == kNtErrorIoIncomplete) {
|
||||
errno = e;
|
||||
goto BlockingOperation;
|
||||
}
|
||||
}
|
||||
return rc;
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue