From 1336fe81f4f1e38987b6c515d40c02c92fac2e61 Mon Sep 17 00:00:00 2001 From: Martin Liska Date: Tue, 3 Mar 2026 14:16:17 +0100 Subject: [PATCH 1/5] Do not use some missing wasix IPV6 related functions --- src/unix/tcp.c | 5 +++++ src/uv-common.c | 3 +++ 2 files changed, 8 insertions(+) diff --git a/src/unix/tcp.c b/src/unix/tcp.c index 98970d75278..852c189c7a7 100644 --- a/src/unix/tcp.c +++ b/src/unix/tcp.c @@ -250,6 +250,9 @@ static int uv__ipv6_link_local_scope_id(void) { uv_free_interface_addresses(interfaces, count); #else + +// TODO: missing wasix entrypoint +#ifndef __wasi__ struct ifaddrs* ifa; struct ifaddrs* p; @@ -268,6 +271,8 @@ static int uv__ipv6_link_local_scope_id(void) { } freeifaddrs(ifa); +#endif + #endif /* defined(_AIX) */ return rv; diff --git a/src/uv-common.c b/src/uv-common.c index 60ff56b9dd7..12b88f9b35f 100644 --- a/src/uv-common.c +++ b/src/uv-common.c @@ -289,7 +289,10 @@ int uv_ip6_addr(const char* ip, int port, struct sockaddr_in6* addr) { #ifdef _WIN32 addr->sin6_scope_id = atoi(zone_index); #else +// TODO: missing wasix entrypoint +#ifndef __wasi__ addr->sin6_scope_id = if_nametoindex(zone_index); +#endif #endif } From b1286f41bf4093017d8ec0644c3e825d65e947e9 Mon Sep 17 00:00:00 2001 From: Martin Liska Date: Tue, 3 Mar 2026 14:33:49 +0100 Subject: [PATCH 2/5] disable one function from the API --- include/uv.h | 3 +++ src/win/getaddrinfo.c | 3 +++ 2 files changed, 6 insertions(+) diff --git a/include/uv.h b/include/uv.h index 938e998fdc5..5e7a29249a3 100644 --- a/include/uv.h +++ b/include/uv.h @@ -1808,12 +1808,15 @@ UV_EXTERN int uv_random(uv_loop_t* loop, # define UV_IF_NAMESIZE (16 + 1) #endif +// TODO: missing wasix entrypoint +#ifndef __wasi__ UV_EXTERN int uv_if_indextoname(unsigned int ifindex, char* buffer, size_t* size); UV_EXTERN int uv_if_indextoiid(unsigned int ifindex, char* buffer, size_t* size); +#endif UV_EXTERN int uv_exepath(char* buffer, size_t* size); diff --git a/src/win/getaddrinfo.c b/src/win/getaddrinfo.c index 4b8ee75a062..67cc3dd43f9 100644 --- a/src/win/getaddrinfo.c +++ b/src/win/getaddrinfo.c @@ -346,6 +346,8 @@ int uv_getaddrinfo(uv_loop_t* loop, } } +// TODO: missing wasix entrypoint +#ifndef __wasi__ int uv_if_indextoname(unsigned int ifindex, char* buffer, size_t* size) { NET_LUID luid; wchar_t wname[NDIS_IF_MAX_STRING_SIZE + 1]; /* Add one for the NUL. */ @@ -386,3 +388,4 @@ int uv_if_indextoiid(unsigned int ifindex, char* buffer, size_t* size) { *size = r; return 0; } +#endif From c2f84cd93ce604158d7d53f33ba2043602b59d6a Mon Sep 17 00:00:00 2001 From: Sadhbh Code Date: Fri, 5 Jun 2026 19:31:18 +0100 Subject: [PATCH 3/5] =?UTF-8?q?multicast=20TTL/loop/interface=20shims=20fo?= =?UTF-8?q?r=20WASIX,=20plus=20WASIX=20UDP=20disconnect=20avoids=20the=20u?= =?UTF-8?q?nsupported=20AF=5FUNSPEC=20connect()=20trick.=20WASIX=20connect?= =?UTF-8?q?ed-state=20checks=20now=20trust=20libuv=E2=80=99s=20handle=20fl?= =?UTF-8?q?ag.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/unix/udp.c | 38 ++++++++++++++++++++++++++++++++++++++ src/uv-common.c | 5 +++++ 2 files changed, 43 insertions(+) diff --git a/src/unix/udp.c b/src/unix/udp.c index c4a3559d61e..2dc138e47f3 100644 --- a/src/unix/udp.c +++ b/src/unix/udp.c @@ -530,6 +530,10 @@ int uv__udp_connect(uv_udp_t* handle, * if (addr->sa_family == AF_UNSPEC) sodisconnect(so); */ int uv__udp_disconnect(uv_udp_t* handle) { +#if defined(__wasi__) + handle->flags &= ~UV_HANDLE_UDP_CONNECTED; + return 0; +#else int r; #if defined(__MVS__) struct sockaddr_storage addr; @@ -570,6 +574,7 @@ int uv__udp_disconnect(uv_udp_t* handle) { handle->flags &= ~UV_HANDLE_UDP_CONNECTED; return 0; +#endif } int uv__udp_send(uv_udp_send_t* req, @@ -1087,6 +1092,15 @@ int uv_udp_set_ttl(uv_udp_t* handle, int ttl) { int uv_udp_set_multicast_ttl(uv_udp_t* handle, int ttl) { + if (ttl < 1 || ttl > 255) + return UV_EINVAL; + if (handle->io_watcher.fd == -1) + return UV_EBADF; + +#if defined(__wasi__) + return 0; +#endif + /* * On Solaris and derivatives such as SmartOS, the length of socket options * is sizeof(int) for IPV6_MULTICAST_HOPS and sizeof(char) for @@ -1112,6 +1126,13 @@ int uv_udp_set_multicast_ttl(uv_udp_t* handle, int ttl) { int uv_udp_set_multicast_loop(uv_udp_t* handle, int on) { + if (handle->io_watcher.fd == -1) + return UV_EBADF; + +#if defined(__wasi__) + return 0; +#endif + /* * On Solaris and derivatives such as SmartOS, the length of socket options * is sizeof(int) for IPV6_MULTICAST_LOOP and sizeof(char) for @@ -1140,6 +1161,9 @@ int uv_udp_set_multicast_interface(uv_udp_t* handle, const char* interface_addr) struct sockaddr_in* addr4; struct sockaddr_in6* addr6; + if (handle->io_watcher.fd == -1) + return UV_EBADF; + addr4 = (struct sockaddr_in*) &addr_st; addr6 = (struct sockaddr_in6*) &addr_st; @@ -1160,6 +1184,20 @@ int uv_udp_set_multicast_interface(uv_udp_t* handle, const char* interface_addr) return UV_EINVAL; } +#if defined(__wasi__) + if ((handle->flags & UV_HANDLE_IPV6) && addr_st.ss_family == AF_INET) + return UV_EINVAL; + if (!(handle->flags & UV_HANDLE_IPV6) && addr_st.ss_family == AF_INET6) + return UV_EINVAL; + if (addr_st.ss_family == AF_INET && + IN_MULTICAST(ntohl(addr4->sin_addr.s_addr))) { + return UV_EINVAL; + } + if (addr_st.ss_family == AF_INET6 && IN6_IS_ADDR_MULTICAST(&addr6->sin6_addr)) + return UV_EINVAL; + return 0; +#endif + if (addr_st.ss_family == AF_INET) { if (setsockopt(handle->io_watcher.fd, IPPROTO_IP, diff --git a/src/uv-common.c b/src/uv-common.c index 12b88f9b35f..807b99a2130 100644 --- a/src/uv-common.c +++ b/src/uv-common.c @@ -443,6 +443,10 @@ int uv_udp_connect(uv_udp_t* handle, const struct sockaddr* addr) { int uv__udp_is_connected(uv_udp_t* handle) { +#if defined(__wasi__) + return handle->type == UV_UDP && + (handle->flags & UV_HANDLE_UDP_CONNECTED) != 0; +#else struct sockaddr_storage addr; int addrlen; if (handle->type != UV_UDP) @@ -453,6 +457,7 @@ int uv__udp_is_connected(uv_udp_t* handle) { return 0; return addrlen > 0; +#endif } From eb7fa2e6ca638061a85fecd7e7386fce556d2864 Mon Sep 17 00:00:00 2001 From: Artem Yarulin Date: Tue, 17 Mar 2026 16:15:18 +0200 Subject: [PATCH 4/5] libuv-wasix: posix_spawn child processes on WASIX Replace the fake fork success stub with a WASIX posix_spawnp backend and reap children through libc waitpid(WNOHANG) instead of direct __wasi_* calls. - Dispatch posix_spawnp from uv__spawn_resolve_and_spawn on __wasi__ - Return UV_ENOSYS from the fork fallback so failed spawns fail fast - Poll uv__wait_children after uv__io_poll and cap backend timeout when process handles are pending (no reliable SIGCHLD on WASIX) - Use uv_pipe for unidirectional child stdio where socketpair is unreliable Co-authored-by: Cursor --- src/unix/core.c | 19 +++- src/unix/process.c | 257 ++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 271 insertions(+), 5 deletions(-) diff --git a/src/unix/core.c b/src/unix/core.c index 6b937722125..0fe2d1799cb 100644 --- a/src/unix/core.c +++ b/src/unix/core.c @@ -399,14 +399,24 @@ static int uv__loop_alive(const uv_loop_t* loop) { static int uv__backend_timeout(const uv_loop_t* loop) { + int timeout; + if (loop->stop_flag == 0 && /* uv__loop_alive(loop) && */ (uv__has_active_handles(loop) || uv__has_active_reqs(loop)) && uv__queue_empty(&loop->pending_queue) && uv__queue_empty(&loop->idle_handles) && (loop->flags & UV_LOOP_REAP_CHILDREN) == 0 && - loop->closing_handles == NULL) - return uv__next_timeout(loop); + loop->closing_handles == NULL) { + timeout = uv__next_timeout(loop); +#if defined(__wasi__) + if (!uv__queue_empty(&loop->process_handles) && + (timeout == -1 || timeout > 10)) { + timeout = 10; + } +#endif + return timeout; + } return 0; } @@ -459,6 +469,11 @@ int uv_run(uv_loop_t* loop, uv_run_mode mode) { uv__io_poll(loop, timeout); +#if defined(__wasi__) + if (!uv__queue_empty(&loop->process_handles)) + uv__wait_children(loop); +#endif + /* Process immediate callbacks (e.g. write_cb) a small fixed number of * times to avoid loop starvation.*/ for (r = 0; r < 8 && !uv__queue_empty(&loop->pending_queue); r++) diff --git a/src/unix/process.c b/src/unix/process.c index a260bf1c194..3ad605754db 100644 --- a/src/unix/process.c +++ b/src/unix/process.c @@ -35,9 +35,12 @@ #include #include -#if defined(__APPLE__) +#if defined(__APPLE__) || defined(__wasi__) # include # include +#endif + +#if defined(__APPLE__) # include # include # include @@ -204,9 +207,33 @@ static int uv__process_init_stdio(uv_stdio_container_t* container, int fds[2]) { if (container->data.stream->type != UV_NAMED_PIPE) return UV_EINVAL; else { +#if defined(__wasi__) + if ((container->flags & UV_READABLE_PIPE) && + !(container->flags & UV_WRITABLE_PIPE)) { + ret = uv_pipe(fds, 0, 0); + if (ret == 0) { + /* Parent writes to stdin, child reads from fd 0. */ + int tmp = fds[0]; + fds[0] = fds[1]; + fds[1] = tmp; + } + } else if ((container->flags & UV_WRITABLE_PIPE) && + !(container->flags & UV_READABLE_PIPE)) { + /* Child writes stdout/stderr, parent reads. */ + ret = uv_pipe(fds, 0, 0); + } else { + ret = uv_socketpair(SOCK_STREAM, 0, fds, 0, 0); + } +#else ret = uv_socketpair(SOCK_STREAM, 0, fds, 0, 0); +#endif - if (ret == 0) + if (ret == 0 +#if defined(__wasi__) + && (container->flags & UV_READABLE_PIPE) != 0 + && (container->flags & UV_WRITABLE_PIPE) != 0 +#endif + ) for (i = 0; i < 2; i++) { setsockopt(fds[i], SOL_SOCKET, SO_RCVBUF, &size, sizeof(size)); setsockopt(fds[i], SOL_SOCKET, SO_SNDBUF, &size, sizeof(size)); @@ -419,6 +446,7 @@ static void uv__process_child_init(const uv_process_options_t* options, } +#if defined(__APPLE__) || defined(__wasi__) #if defined(__APPLE__) typedef struct uv__posix_spawn_fncs_tag { struct { @@ -466,8 +494,15 @@ static void uv__spawn_init_posix_spawn(void) { /* Init feature detection for POSIX_SPAWN_SETSID flag */ uv__spawn_init_can_use_setsid(); } +#endif +#endif +static int uv__spawn_resolve_and_spawn(const uv_process_options_t* options, + posix_spawnattr_t* attrs, + posix_spawn_file_actions_t* actions, + pid_t* pid); +#if defined(__APPLE__) static int uv__spawn_set_posix_spawn_attrs( posix_spawnattr_t* attrs, const uv__posix_spawn_fncs_t* posix_spawn_fncs, @@ -659,6 +694,7 @@ static int uv__spawn_set_posix_spawn_file_actions( (void) posix_spawn_file_actions_destroy(actions); return err; } +#endif char* uv__spawn_find_path_in_env(char** env) { char** env_iterator; @@ -714,6 +750,13 @@ static int uv__spawn_resolve_and_spawn(const uv_process_options_t* options, return err; } +#if defined(__wasi__) + do + err = posix_spawnp(pid, options->file, actions, attrs, options->args, env); + while (err == EINTR); + return err; +#endif + /* Look for the definition of PATH in the provided env */ path = uv__spawn_find_path_in_env(env); @@ -776,7 +819,7 @@ static int uv__spawn_resolve_and_spawn(const uv_process_options_t* options, return err; } - +#if defined(__APPLE__) static int uv__spawn_and_init_child_posix_spawn( const uv_process_options_t* options, int stdio_count, @@ -818,11 +861,209 @@ static int uv__spawn_and_init_child_posix_spawn( } #endif +#if defined(__wasi__) +static int uv__spawn_set_posix_spawn_attrs_wasi( + posix_spawnattr_t* attrs, + const uv_process_options_t* options) { + int err; + + err = posix_spawnattr_init(attrs); + if (err != 0) + return err; + + if (options->flags & (UV_PROCESS_SETUID | UV_PROCESS_SETGID)) { + err = ENOSYS; + goto error; + } + + if (options->flags & UV_PROCESS_DETACHED) { + err = ENOSYS; + goto error; + } + + return 0; + +error: + (void) posix_spawnattr_destroy(attrs); + return err; +} + + +static int uv__spawn_set_posix_spawn_file_actions_wasi( + posix_spawn_file_actions_t* actions, + const uv_process_options_t* options, + int stdio_count, + int (*pipes)[2]) { + int err; + int fd; + int fd2; + int use_fd; + + err = posix_spawn_file_actions_init(actions); + if (err != 0) + return err; + + if (options->cwd != NULL) { + err = posix_spawn_file_actions_addchdir_np(actions, options->cwd); + if (err != 0) + goto error; + } + + for (fd = 0; fd < stdio_count; fd++) { + use_fd = pipes[fd][1]; + if (use_fd < 0) + continue; + if (use_fd != fd && use_fd >= stdio_count) + continue; + +#ifdef F_DUPFD_CLOEXEC + pipes[fd][1] = fcntl(use_fd, F_DUPFD_CLOEXEC, stdio_count); +#else + pipes[fd][1] = fcntl(use_fd, F_DUPFD, stdio_count); +#endif + if (pipes[fd][1] == -1) { + err = errno; + goto error; + } + +#ifndef F_DUPFD_CLOEXEC + err = uv__cloexec(pipes[fd][1], 1); + if (err != 0) + goto error; +#endif + } + + for (fd = 0; fd < stdio_count; fd++) { + use_fd = pipes[fd][1]; + + if (use_fd < 0) { + if (fd >= 3) + continue; + + use_fd = open("/dev/null", fd == 0 ? O_RDONLY : O_RDWR); + if (use_fd < 0) { + err = errno; + goto error; + } + + err = uv__cloexec(use_fd, 1); + if (err != 0) { + uv__close(use_fd); + goto error; + } + + pipes[fd][1] = use_fd; + } + + use_fd = pipes[fd][1]; + if (use_fd == fd) + continue; + + err = posix_spawn_file_actions_adddup2(actions, use_fd, fd); + if (err != 0) + goto error; + + uv__nonblock_fcntl(use_fd, 0); + } + + for (fd = 0; fd < stdio_count; fd++) { + use_fd = pipes[fd][1]; + if (use_fd < 0) + continue; + if (use_fd < stdio_count && use_fd == fd) + continue; + + for (fd2 = 0; fd2 < fd; fd2++) { + if (pipes[fd2][1] == use_fd) + break; + } + if (fd2 < fd) + continue; + + err = posix_spawn_file_actions_addclose(actions, use_fd); + if (err != 0) + goto error; + } + + for (fd = 0; fd < stdio_count; fd++) { + use_fd = pipes[fd][0]; + if (use_fd < 0) + continue; + + for (fd2 = 0; fd2 < fd; fd2++) { + if (pipes[fd2][0] == use_fd) + break; + } + if (fd2 < fd) + continue; + + for (fd2 = 0; fd2 < stdio_count; fd2++) { + if (pipes[fd2][1] == use_fd) + break; + } + if (fd2 < stdio_count) + continue; + + err = posix_spawn_file_actions_addclose(actions, use_fd); + if (err != 0) + goto error; + } + + return 0; + +error: + (void) posix_spawn_file_actions_destroy(actions); + return err; +} + + +static int uv__spawn_and_init_child_posix_spawn_wasi( + const uv_process_options_t* options, + int stdio_count, + int (*pipes)[2], + pid_t* pid) { + int err; + posix_spawnattr_t attrs; + posix_spawn_file_actions_t actions; + + err = uv__spawn_set_posix_spawn_attrs_wasi(&attrs, options); + if (err != 0) + goto error; + + err = uv__spawn_set_posix_spawn_file_actions_wasi(&actions, + options, + stdio_count, + pipes); + if (err != 0) { + (void) posix_spawnattr_destroy(&attrs); + goto error; + } + + err = uv__spawn_resolve_and_spawn(options, &attrs, &actions, pid); + + (void) posix_spawn_file_actions_destroy(&actions); + (void) posix_spawnattr_destroy(&attrs); + +error: + return UV__ERR(err); +} +#endif + static int uv__spawn_and_init_child_fork(const uv_process_options_t* options, int stdio_count, int (*pipes)[2], int error_fd, pid_t* pid) { +#if defined(__wasi__) && defined(__wasm_exception_handling__) + /* WASIX builds with wasm EH cannot use asyncify-based fork(); fall back to + * the posix_spawn path in uv__spawn_and_init_child(). */ + (void) options; + (void) stdio_count; + (void) pipes; + (void) error_fd; + (void) pid; + return UV_ENOSYS; +#else sigset_t signewset; sigset_t sigoldset; @@ -857,6 +1098,7 @@ static int uv__spawn_and_init_child_fork(const uv_process_options_t* options, /* Fork succeeded, in the parent process */ return 0; +#endif } static int uv__spawn_and_init_child( @@ -901,6 +1143,15 @@ static int uv__spawn_and_init_child( #endif +#if defined(__wasi__) + err = uv__spawn_and_init_child_posix_spawn_wasi(options, + stdio_count, + pipes, + pid); + if (err != UV_ENOSYS) + return err; +#endif + /* This pipe is used by the parent to wait until * the child has called `execve()`. We need this * to avoid the following race condition: From 4923dd2d4828dd307620beb2ae01424a7d7da5a7 Mon Sep 17 00:00:00 2001 From: Arshia Ghafoori Date: Tue, 23 Jun 2026 17:50:37 +0000 Subject: [PATCH 5/5] wasix: clamp only unbounded poll sleep for live children Move child-exit polling out of uv__backend_timeout so uv_backend_timeout() and timer-driven poll deadlines stay accurate. When a spawned child is still tracked and the loop would poll forever, cap that single poll to UV__WASIX_CHILD_POLL_MS before calling uv__io_poll(). Co-authored-by: Cursor --- src/unix/core.c | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/src/unix/core.c b/src/unix/core.c index 0fe2d1799cb..f17ed70c9e0 100644 --- a/src/unix/core.c +++ b/src/unix/core.c @@ -46,6 +46,12 @@ #include #include /* clock_gettime */ +#if defined(__wasi__) +/* Fallback poll period when a child is live but uv__next_timeout() would block + * forever. Applied only at the uv__io_poll() call site in uv_run(). */ +#define UV__WASIX_CHILD_POLL_MS 100 +#endif + #ifdef __sun # include # include @@ -399,24 +405,14 @@ static int uv__loop_alive(const uv_loop_t* loop) { static int uv__backend_timeout(const uv_loop_t* loop) { - int timeout; - if (loop->stop_flag == 0 && /* uv__loop_alive(loop) && */ (uv__has_active_handles(loop) || uv__has_active_reqs(loop)) && uv__queue_empty(&loop->pending_queue) && uv__queue_empty(&loop->idle_handles) && (loop->flags & UV_LOOP_REAP_CHILDREN) == 0 && - loop->closing_handles == NULL) { - timeout = uv__next_timeout(loop); -#if defined(__wasi__) - if (!uv__queue_empty(&loop->process_handles) && - (timeout == -1 || timeout > 10)) { - timeout = 10; - } -#endif - return timeout; - } + loop->closing_handles == NULL) + return uv__next_timeout(loop); return 0; } @@ -467,6 +463,14 @@ int uv_run(uv_loop_t* loop, uv_run_mode mode) { uv__metrics_inc_loop_count(loop); +#if defined(__wasi__) + /* WASIX does not reliably deliver SIGCHLD for posix_spawn children. + * Reap after each poll; only override an unbounded poll sleep so timer + * deadlines and short poll timeouts stay unchanged. */ + if (!uv__queue_empty(&loop->process_handles) && timeout < 0) + timeout = UV__WASIX_CHILD_POLL_MS; +#endif + uv__io_poll(loop, timeout); #if defined(__wasi__)