From e2e64a6ec50bbfbbeb1aa926ea6de037f66a111b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20F?= Date: Mon, 23 Jun 2025 20:58:10 +0200 Subject: [PATCH] windows support --- Makefile | 2 + examples/http_client.js | 7 +- examples/http_server.js | 4 +- qjs.c | 5 ++ quickjs-libc.c | 153 +++++++++++++++++++++++++++++----------- quickjs.c | 18 +++++ 6 files changed, 142 insertions(+), 47 deletions(-) diff --git a/Makefile b/Makefile index 3b1c745..8216020 100644 --- a/Makefile +++ b/Makefile @@ -247,6 +247,8 @@ HOST_LIBS=-lm -ldl -lpthread LIBS=-lm -lpthread ifndef CONFIG_WIN32 LIBS+=-ldl +else +LIBS+=-lws2_32 endif LIBS+=$(EXTRA_LIBS) diff --git a/examples/http_client.js b/examples/http_client.js index 1cd9e1b..35bf4fe 100644 --- a/examples/http_client.js +++ b/examples/http_client.js @@ -10,9 +10,10 @@ function must(result) { } const sockfd = must(os.socket(os.AF_INET, os.SOCK_STREAM)); -await os.connect(sockfd, os.getaddrinfo("bellard.org",'80')[0]); -const httpReq = ["GET / HTTP/1.0", "", ""].join('\r\n') -must(await os.send(sockfd, Uint8Array.from(httpReq, c => c.charCodeAt(0)).buffer) > 0); +const addr = os.getaddrinfo("bellard.org",'80').find(a => a.family == os.AF_INET); +await os.connect(sockfd, addr); +const httpReq = Uint8Array.from("GET / HTTP/1.0\r\n\r\n", c => c.charCodeAt(0)) +must(await os.send(sockfd, httpReq.buffer) > 0); const chunk = new Uint8Array(512); const recvd = await os.recv(sockfd, chunk.buffer); console.log([...chunk.slice(0,recvd)].map(c => String.fromCharCode(c)).join('')); diff --git a/examples/http_server.js b/examples/http_server.js index e2d0899..a4d63aa 100755 --- a/examples/http_server.js +++ b/examples/http_server.js @@ -40,7 +40,7 @@ function sendLines(fd, lines) { } //USAGE: qjs http_server.js [PORT=8080 [HOST=localhost]] const [port = "8080", host = "localhost"] = scriptArgs.slice(1); -const [ai] = os.getaddrinfo(host, port); +const ai = os.getaddrinfo(host, port).find(a => a.family == os.AF_INET); //if (!ai.length) throw `Unable to getaddrinfo(${host}, ${port})`; const sock_srv = must(os.socket(os.AF_INET, os.SOCK_STREAM)); must(os.setsockopt(sock_srv, os.SO_REUSEADDR, new Uint32Array([1]).buffer)); @@ -49,7 +49,7 @@ must(os.listen(sock_srv)); //os.signal(os.SIGINT, ()=>os.close(sock_srv)); // don't work console.log(`Listening on http://${host}:${port} (${ai.addr}:${ai.port}) ...`); const openCmd = { linux: "xdg-open", darwin: "open", win32: "start" }[os.platform]; -if (openCmd) os.exec([openCmd, `http://${host}:${port}`]); +if (openCmd && os.exec) os.exec([openCmd, `http://${host}:${port}`]); while (true) { // TODO: break on SIG* const [sock_cli] = await os.accept(sock_srv); diff --git a/qjs.c b/qjs.c index a88e39a..016feb2 100644 --- a/qjs.c +++ b/qjs.c @@ -39,6 +39,11 @@ #elif defined(__FreeBSD__) #include #endif +#if defined(_WIN32) +#include +#include +#include +#endif #include "cutils.h" #include "quickjs-libc.h" diff --git a/quickjs-libc.c b/quickjs-libc.c index 95163e7..8786157 100644 --- a/quickjs-libc.c +++ b/quickjs-libc.c @@ -38,10 +38,11 @@ #include #include #if defined(_WIN32) +#include +#include #include #include #include -#include #else #include #include @@ -113,6 +114,9 @@ typedef struct { struct sockaddr_storage sockaddr; // for sendto() JSValue resolve; JSValue reject; +#ifdef _WIN32 + WSAEVENT event; // so os_pool can wait on it +#endif } JSOSSockHandler; typedef struct { @@ -249,7 +253,7 @@ static int JS_ToSockaddrStruct(JSContext *ctx, JSValue addr, static JSValue JS_ToSockaddrObj(JSContext *ctx, struct sockaddr_storage *sockaddr) { JSValue obj, prop; - char ip_str[INET_ADDRSTRLEN]; + char ip_str[INET6_ADDRSTRLEN]; // max(INET6_ADDRSTRLEN, INET_ADDRSTRLEN) const char *ip_ptr; struct sockaddr_in *sockaddr4 = (struct sockaddr_in *)sockaddr; struct sockaddr_in6 *sockaddr6 = (struct sockaddr_in6 *)sockaddr; @@ -268,7 +272,9 @@ static JSValue JS_ToSockaddrObj(JSContext *ctx, struct sockaddr_storage *sockadd void* sin_addr = sockaddr->ss_family == AF_INET ? (void*)&sockaddr4->sin_addr : sockaddr->ss_family == AF_INET6 ? (void*)&sockaddr6->sin6_addr : NULL; - ip_ptr = inet_ntop(AF_INET, sin_addr, ip_str, sizeof(ip_str)); + size_t sin_len = sockaddr->ss_family == AF_INET ? INET_ADDRSTRLEN: + sockaddr->ss_family == AF_INET6 ? INET6_ADDRSTRLEN: 0 ; + ip_ptr = inet_ntop(sockaddr->ss_family, sin_addr, ip_str, sin_len); prop = ip_ptr ? JS_NewString(ctx, ip_ptr) : JS_NULL; JS_DefinePropertyValueStr(ctx, obj, "addr", prop, JS_PROP_C_W_E); return obj; @@ -1039,6 +1045,18 @@ static ssize_t js_get_errno(ssize_t ret) return ret; } +static ssize_t js_get_sockerrno(ssize_t ret) +{ + #if defined(_WIN32) + if (ret == -1 || ret == INVALID_SOCKET) + ret = -WSAGetLastError(); + #else + if (ret == -1) + ret = -errno; + #endif + return ret; +} + static JSValue js_std_strerror(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { @@ -1785,11 +1803,6 @@ static int js_std_init(JSContext *ctx, JSModuleDef *m) { JSValue proto; - #if defined(_WIN32) - WSADATA wsa_data; - WSAStartup(0x0202, &wsa_data); - #endif - /* FILE class */ /* the class ID is created once */ JS_NewClassID(&js_std_file_class_id); @@ -2517,28 +2530,41 @@ static int handle_posted_message(JSContext *ctx, JSWorkerMessageHandler *port) static void handle_socket_message(JSContext *ctx, JSOSSockHandler *sh) { + #ifdef _WIN32 + WSANETWORKEVENTS netEvents; + WSAEnumNetworkEvents(sh->sockfd, sh->event, &netEvents); + #endif + int err = 0; struct sockaddr_storage sockaddr; socklen_t addr_len = sizeof(sockaddr); - socklen_t len = sizeof(err); + if (sh->magic == MAGIC_SOCKET_RECV) { - err = js_get_errno(recv(sh->sockfd, sh->buffer, sh->length, 0)); + err = js_get_sockerrno(recv(sh->sockfd, (char*) sh->buffer, sh->length, 0)); } else if (sh->magic == MAGIC_SOCKET_SEND) { - err = js_get_errno(send(sh->sockfd, sh->buffer, sh->length, 0)); + err = js_get_sockerrno(send(sh->sockfd, (char*) sh->buffer, sh->length, 0)); } else if (sh->magic == MAGIC_SOCKET_RECVFROM) { - err = js_get_errno(recvfrom(sh->sockfd, sh->buffer, sh->length, 0, (struct sockaddr *)&sockaddr, &addr_len)); + err = js_get_sockerrno(recvfrom(sh->sockfd, (char*) sh->buffer, sh->length, 0, (struct sockaddr *)&sockaddr, &addr_len)); } else if (sh->magic == MAGIC_SOCKET_SENDTO) { - err = js_get_errno(sendto(sh->sockfd, sh->buffer, sh->length, 0, (const struct sockaddr *)&sh->sockaddr, addr_len)); + err = js_get_sockerrno(sendto(sh->sockfd, (char*) sh->buffer, sh->length, 0, (const struct sockaddr *)&sh->sockaddr, addr_len)); } else if (sh->magic == MAGIC_SOCKET_CONNECT) { - err = getsockopt(sh->sockfd, SOL_SOCKET, SO_ERROR, &err, &len) ? -errno : -err; + #ifdef _WIN32 + err = 0; + #else + socklen_t len = sizeof(err); + err = getsockopt(sh->sockfd, SOL_SOCKET, SO_ERROR, (char*) &err, &len) ? -errno : -err; + #endif } else if (sh->magic == MAGIC_SOCKET_ACCEPT) { - err = js_get_errno(accept(sh->sockfd, (struct sockaddr *)&sockaddr, &addr_len)); + err = js_get_sockerrno(accept(sh->sockfd, (struct sockaddr *)&sockaddr, &addr_len)); } - +#ifdef _WIN32 + if (err == -WSAEWOULDBLOCK) + return; +#else if (err == -EAGAIN || err == -EWOULDBLOCK) return; - +#endif JSValue promiseval = JS_UNDEFINED; if (sh->magic == MAGIC_SOCKET_ACCEPT) { promiseval = JS_NewArray(ctx); @@ -2589,8 +2615,10 @@ static int js_os_poll(JSContext *ctx) /* XXX: handle signals if useful */ - if (list_empty(&ts->os_rw_handlers) && list_empty(&ts->os_timers) && - list_empty(&ts->port_list)) { + if (list_empty(&ts->os_rw_handlers) && + list_empty(&ts->os_timers) && + list_empty(&ts->port_list) && + list_empty(&ts->os_sock_handlers)) { return -1; /* no more events */ } @@ -2620,13 +2648,21 @@ static int js_os_poll(JSContext *ctx) count = 0; list_for_each(el, &ts->os_rw_handlers) { rh = list_entry(el, JSOSRWHandler, link); - if (rh->fd == 0 && !JS_IsNull(rh->rw_func[0])) { + if (rh->fd == 0 && !JS_IsNull(rh->r_func)) { handles[count++] = (HANDLE)_get_osfhandle(rh->fd); // stdin if (count == (int)countof(handles)) break; } } + list_for_each(el, &ts->os_sock_handlers) { + JSOSSockHandler *sh = list_entry(el, JSOSSockHandler, link); + //TODO: socket readability don't seems to be a winsock event => trigger manually + handles[count++] = sh->event; + if (count == (int)countof(handles)) + break; + } + list_for_each(el, &ts->port_list) { JSWorkerMessageHandler *port = list_entry(el, JSWorkerMessageHandler, link); if (JS_IsNull(port->on_message_func)) @@ -2642,16 +2678,26 @@ static int js_os_poll(JSContext *ctx) timeout = min_delay; ret = WaitForMultipleObjects(count, handles, FALSE, timeout); + // why iterate on every list instead of just handles[ret] ? if (ret < count) { list_for_each(el, &ts->os_rw_handlers) { rh = list_entry(el, JSOSRWHandler, link); - if (rh->fd == 0 && !JS_IsNull(rh->rw_func[0])) { - call_handler(ctx, rh->rw_func[0]); + if (rh->fd == 0 && !JS_IsNull(rh->r_func)) { + call_handler(ctx, rh->r_func); /* must stop because the list may have been modified */ goto done; } } + list_for_each(el, &ts->os_sock_handlers) { + JSOSSockHandler *sh = list_entry(el, JSOSSockHandler, link); + if (sh->event == handles[ret]) { + handle_socket_message(ctx, sh); + WSAResetEvent(sh->event); // WSACloseEvent(sh->event); + goto done; + } + } + list_for_each(el, &ts->port_list) { JSWorkerMessageHandler *port = list_entry(el, JSWorkerMessageHandler, link); if (!JS_IsNull(port->on_message_func)) { @@ -3613,7 +3659,7 @@ static JSValue js_os_dup2(JSContext *ctx, JSValueConst this_val, static JSValue js_os_socket(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { - int domain, type, protocol = 0, ret; + int domain, type, protocol = 0; if (JS_ToInt32(ctx, &domain, argv[0])) return JS_EXCEPTION; @@ -3622,11 +3668,19 @@ static JSValue js_os_socket(JSContext *ctx, JSValueConst this_val, if (argc >= 3 && JS_ToInt32(ctx, &protocol, argv[2])) return JS_EXCEPTION; - ret = js_get_errno(socket(domain, type, protocol)); - - if (ret >= 0 && !(type & SOCK_NONBLOCK)) // JS flag `os.SOCK_BLOCKING` - fcntl(ret, F_SETFL, fcntl(ret, F_GETFL, 0) | O_NONBLOCK); - return JS_NewInt32(ctx, ret); + int socketfd = socket(domain, type, protocol); + int ret = js_get_sockerrno(socketfd); + if (ret < 0) + return JS_NewInt32(ctx, ret); +#if defined(_WIN32) + u_long mode = 1; + ret = ioctlsocket(ret, FIONBIO, &mode); +#else + ret = fcntl(ret, F_SETFL, fcntl(ret, F_GETFL, 0) | O_NONBLOCK); +#endif + if (ret < 0) + return JS_NewInt32(ctx, ret); + return JS_NewInt32(ctx, socketfd); } static JSValue js_os_get_setsockopt(JSContext *ctx, JSValueConst this_val, @@ -3647,9 +3701,9 @@ static JSValue js_os_get_setsockopt(JSContext *ctx, JSValueConst this_val, optlen = buflen; if (magic == 0) - ret = js_get_errno(getsockopt(sock, SOL_SOCKET, optname, optval, &optlen)); + ret = js_get_sockerrno(getsockopt(sock, SOL_SOCKET, optname, (char*)optval, &optlen)); else - ret = js_get_errno(setsockopt(sock, SOL_SOCKET, optname, &optval, optlen)); + ret = js_get_sockerrno(setsockopt(sock, SOL_SOCKET, optname, (char*)optval, optlen)); return JS_NewInt32(ctx, ret); } @@ -3670,7 +3724,7 @@ static JSValue js_os_getaddrinfo(JSContext *ctx, JSValueConst this_val, if (!service) goto fail; - ret = js_get_errno(getaddrinfo(node, service, NULL, &ai)); + ret = js_get_sockerrno(getaddrinfo(node, service, NULL, &ai)); if (ret) goto fail; @@ -3703,7 +3757,7 @@ static JSValue js_os_bind(JSContext *ctx, JSValueConst this_val, return JS_EXCEPTION; socklen_t addr_len = sockaddr.ss_family == AF_INET ? sizeof(struct sockaddr_in) : sockaddr.ss_family == AF_INET6 ? sizeof(struct sockaddr_in6) : 0; - ret = js_get_errno(bind(sockfd, (struct sockaddr *)&sockaddr, addr_len)); + ret = js_get_sockerrno(bind(sockfd, (struct sockaddr *)&sockaddr, addr_len)); return JS_NewInt32(ctx, ret); } @@ -3717,7 +3771,7 @@ static JSValue js_os_listen(JSContext *ctx, JSValueConst this_val, if (argc >= 2 && JS_ToInt32(ctx, &backlog, argv[1])) return JS_EXCEPTION; - ret = js_get_errno(listen(sockfd, backlog)); + ret = js_get_sockerrno(listen(sockfd, backlog)); return JS_NewInt32(ctx, ret); } @@ -3737,10 +3791,14 @@ static JSValue js_os_connect_accept(JSContext *ctx, JSValueConst this_val, } socklen_t addr_len = sockaddr.ss_family == AF_INET ? sizeof(struct sockaddr_in) : sockaddr.ss_family == AF_INET6 ? sizeof(struct sockaddr_in6) : 0; - - sockret = js_get_errno(connect(sockfd, (struct sockaddr *)&sockaddr, addr_len)); - if (sockret != 0 && sockret != -EINPROGRESS) { - JS_ThrowTypeError(ctx, "connect failed"); + sockret = js_get_sockerrno(connect(sockfd, (struct sockaddr *)&sockaddr, addr_len)); + #if defined(_WIN32) + if (sockret != -WSAEWOULDBLOCK) + #else + if (sockret != -EINPROGRESS) + #endif + { + JS_ThrowTypeError(ctx, "connect failed (%i)", sockret); return JS_EXCEPTION; } } @@ -3757,10 +3815,13 @@ static JSValue js_os_connect_accept(JSContext *ctx, JSValueConst this_val, sh->magic = magic; sh->resolve = JS_DupValue(ctx, resolving_funcs[0]); sh->reject = JS_DupValue(ctx, resolving_funcs[1]); + #if defined(_WIN32) + sh->event = WSACreateEvent(); + WSAEventSelect(sh->sockfd, sh->event, magic == MAGIC_SOCKET_CONNECT ? FD_CONNECT : FD_ACCEPT); + #endif list_add_tail(&sh->link, &ts->os_sock_handlers); JS_FreeValue(ctx, resolving_funcs[0]); JS_FreeValue(ctx, resolving_funcs[1]); - return promise; } @@ -3804,7 +3865,11 @@ static JSValue js_os_recv_send(JSContext *ctx, JSValueConst this_val, } sh->length = length; } - + #if defined(_WIN32) + sh->event = WSACreateEvent(); + int flags = (magic == MAGIC_SOCKET_SENDTO || magic == MAGIC_SOCKET_SEND) ? FD_WRITE : FD_READ; + WSAEventSelect(sh->sockfd, sh->event, flags); + #endif sh->bufval = JS_DupValue(ctx, argv[bufArgvIdx]); sh->resolve = JS_DupValue(ctx, resolving_funcs[0]); sh->reject = JS_DupValue(ctx, resolving_funcs[1]); @@ -3831,7 +3896,7 @@ static JSValue js_os_shutdown(JSContext *ctx, JSValueConst this_val, if (JS_ToInt32(ctx, &how, argv[1])) return JS_EXCEPTION; - ret = js_get_errno(shutdown(sockfd, how)); + ret = js_get_sockerrno(shutdown(sockfd, how)); return JS_NewInt32(ctx, ret); } @@ -4389,14 +4454,18 @@ static const JSCFunctionListEntry js_os_funcs[] = { OS_FLAG(SOCK_STREAM), OS_FLAG(SOCK_DGRAM), OS_FLAG(SOCK_RAW), - // SOCK_NONBLOCK is set by default so reuse it value for our imaginary nemsis - JS_PROP_INT32_DEF("SOCK_BLOCK", SOCK_NONBLOCK, JS_PROP_CONFIGURABLE ), OS_FLAG(SO_REUSEADDR), OS_FLAG(SO_RCVBUF), OS_FLAG(SO_ERROR), +#if defined(_WIN32) + JS_PROP_INT32_DEF("SHUT_RD", SD_RECEIVE, JS_PROP_CONFIGURABLE), + JS_PROP_INT32_DEF("SHUT_WR", SD_SEND, JS_PROP_CONFIGURABLE), + JS_PROP_INT32_DEF("SHUT_RDWR", SD_BOTH, JS_PROP_CONFIGURABLE), +#else OS_FLAG(SHUT_RD), OS_FLAG(SHUT_WR), OS_FLAG(SHUT_RDWR), +#endif }; static int js_os_init(JSContext *ctx, JSModuleDef *m) diff --git a/quickjs.c b/quickjs.c index 475232c..dda07e6 100644 --- a/quickjs.c +++ b/quickjs.c @@ -39,6 +39,11 @@ #elif defined(__FreeBSD__) #include #endif +#if defined(_WIN32) +#include +#include +#include +#endif #include "cutils.h" #include "list.h" @@ -302,6 +307,9 @@ struct JSRuntime { int shape_hash_count; /* number of hashed shapes */ JSShape **shape_hash; void *user_opaque; + #if defined(_WIN32) + WSADATA wsa_data; + #endif }; struct JSClass { @@ -475,6 +483,9 @@ struct JSContext { const char *input, size_t input_len, const char *filename, int flags, int scope_idx); void *user_opaque; + #if defined(_WIN32) + WSADATA wsa_data; + #endif }; typedef union JSFloat64Union { @@ -1626,6 +1637,10 @@ JSRuntime *JS_NewRuntime2(const JSMallocFunctions *mf, void *opaque) rt->current_exception = JS_UNINITIALIZED; +#if defined(_WIN32) + WSAStartup(MAKEWORD(2, 2), &rt->wsa_data); +#endif + return rt; fail: JS_FreeRuntime(rt); @@ -2077,6 +2092,9 @@ void JS_FreeRuntime(JSRuntime *rt) JSMallocState ms = rt->malloc_state; rt->mf.js_free(&ms, rt); } +#if defined(_WIN32) + WSACleanup(); +#endif } JSContext *JS_NewContextRaw(JSRuntime *rt)