From f267925d45fc387ae9dd9ae7b65e752961210b69 Mon Sep 17 00:00:00 2001 From: "Yukihiro \"Matz\" Matsumoto" Date: Thu, 7 May 2026 10:41:05 +0900 Subject: [PATCH] mruby-socket: translate Winsock errors to errno on Windows Winsock APIs (socket, bind, connect, accept, recv, send, ...) report errors via WSAGetLastError() and do not set errno, so mrb_sys_fail on Windows was reading a stale or zero errno. Result: every socket failure raised SystemCallError with errno 0 ("Success") instead of the appropriate Errno::* class. Add mrb_hal_socket_set_errno_from_last_error() to the HAL: - POSIX: no-op (failed calls already set errno) - Windows: maps WSAGetLastError() to a POSIX errno via wsa_to_errno() with 32 cases covering the common Winsock error codes; unmapped codes fall back to EIO. Each case is #ifdef-guarded against older MSVC CRTs that lack a particular Exxx. In src/socket.c, route the 22 socket-API failure sites through a new sock_sys_fail() helper that calls the HAL translator before mrb_sys_fail. Also fix mrb_hal_socket_set_nonblock() on Windows, which was returning -1 without setting errno after ioctlsocket failure. POSIX behavior unchanged (verified: TCPSocket connect refused -> Errno::ECONNREFUSED, bind to privileged port -> Errno::EACCES, bad sockopt -> Errno::EOPNOTSUPP). Closes #6819, reported by Asmod4n. Co-authored-by: Claude --- mrbgems/mruby-socket/include/socket_hal.h | 10 ++ mrbgems/mruby-socket/ports/posix/socket_hal.c | 10 ++ mrbgems/mruby-socket/ports/win/socket_hal.c | 119 ++++++++++++++++++ mrbgems/mruby-socket/src/socket.c | 54 ++++---- 4 files changed, 171 insertions(+), 22 deletions(-) diff --git a/mrbgems/mruby-socket/include/socket_hal.h b/mrbgems/mruby-socket/include/socket_hal.h index d92f2079f..89ac95b7c 100644 --- a/mrbgems/mruby-socket/include/socket_hal.h +++ b/mrbgems/mruby-socket/include/socket_hal.h @@ -28,6 +28,16 @@ void mrb_hal_socket_init(mrb_state *mrb); /* Finalize socket subsystem (e.g., WSACleanup on Windows) */ void mrb_hal_socket_final(mrb_state *mrb); +/* + * Error Handling + */ + +/* Translate the most recent socket-API error into a POSIX errno value and + * store it in errno. On Windows, this reads WSAGetLastError() and maps it; + * on POSIX, this is a no-op (errno is already set by the failed call). + * Call this immediately after a socket-API failure, before mrb_sys_fail. */ +void mrb_hal_socket_set_errno_from_last_error(void); + /* * Socket Control Operations */ diff --git a/mrbgems/mruby-socket/ports/posix/socket_hal.c b/mrbgems/mruby-socket/ports/posix/socket_hal.c index 93e7a77ec..6654596c3 100644 --- a/mrbgems/mruby-socket/ports/posix/socket_hal.c +++ b/mrbgems/mruby-socket/ports/posix/socket_hal.c @@ -41,6 +41,16 @@ mrb_hal_socket_final(mrb_state *mrb) /* No cleanup needed for POSIX sockets */ } +/* + * Error Handling + */ + +void +mrb_hal_socket_set_errno_from_last_error(void) +{ + /* POSIX socket calls already set errno on failure; nothing to do. */ +} + /* * Socket Control Operations */ diff --git a/mrbgems/mruby-socket/ports/win/socket_hal.c b/mrbgems/mruby-socket/ports/win/socket_hal.c index 232686b1a..3e4a9583b 100644 --- a/mrbgems/mruby-socket/ports/win/socket_hal.c +++ b/mrbgems/mruby-socket/ports/win/socket_hal.c @@ -43,6 +43,124 @@ mrb_hal_socket_final(mrb_state *mrb) WSACleanup(); } +/* + * Error Handling + */ + +/* Map a Winsock error code to a POSIX errno value. Each case is guarded + * with #ifdef so older MSVC CRTs that lack a particular Exxx still build; + * unknown codes fall back to EIO so mrb_sys_fail still produces a non-zero + * SystemCallError rather than reporting "success." */ +static int +wsa_to_errno(int wsa_err) +{ + switch (wsa_err) { + case 0: return 0; +#ifdef EINTR + case WSAEINTR: return EINTR; +#endif +#ifdef EBADF + case WSAEBADF: return EBADF; +#endif +#ifdef EACCES + case WSAEACCES: return EACCES; +#endif +#ifdef EFAULT + case WSAEFAULT: return EFAULT; +#endif +#ifdef EINVAL + case WSAEINVAL: return EINVAL; +#endif +#ifdef EMFILE + case WSAEMFILE: return EMFILE; +#endif +#ifdef EWOULDBLOCK + case WSAEWOULDBLOCK: return EWOULDBLOCK; +#endif +#ifdef EINPROGRESS + case WSAEINPROGRESS: return EINPROGRESS; +#endif +#ifdef EALREADY + case WSAEALREADY: return EALREADY; +#endif +#ifdef ENOTSOCK + case WSAENOTSOCK: return ENOTSOCK; +#endif +#ifdef EDESTADDRREQ + case WSAEDESTADDRREQ: return EDESTADDRREQ; +#endif +#ifdef EMSGSIZE + case WSAEMSGSIZE: return EMSGSIZE; +#endif +#ifdef EPROTOTYPE + case WSAEPROTOTYPE: return EPROTOTYPE; +#endif +#ifdef ENOPROTOOPT + case WSAENOPROTOOPT: return ENOPROTOOPT; +#endif +#ifdef EPROTONOSUPPORT + case WSAEPROTONOSUPPORT: return EPROTONOSUPPORT; +#endif +#ifdef EOPNOTSUPP + case WSAEOPNOTSUPP: return EOPNOTSUPP; +#endif +#ifdef EAFNOSUPPORT + case WSAEAFNOSUPPORT: return EAFNOSUPPORT; + case WSAEPFNOSUPPORT: return EAFNOSUPPORT; + case WSAESOCKTNOSUPPORT: return EAFNOSUPPORT; +#endif +#ifdef EADDRINUSE + case WSAEADDRINUSE: return EADDRINUSE; +#endif +#ifdef EADDRNOTAVAIL + case WSAEADDRNOTAVAIL: return EADDRNOTAVAIL; +#endif +#ifdef ENETDOWN + case WSAENETDOWN: return ENETDOWN; +#endif +#ifdef ENETUNREACH + case WSAENETUNREACH: return ENETUNREACH; +#endif +#ifdef ENETRESET + case WSAENETRESET: return ENETRESET; +#endif +#ifdef ECONNABORTED + case WSAECONNABORTED: return ECONNABORTED; +#endif +#ifdef ECONNRESET + case WSAECONNRESET: return ECONNRESET; +#endif +#ifdef ENOBUFS + case WSAENOBUFS: return ENOBUFS; +#endif +#ifdef EISCONN + case WSAEISCONN: return EISCONN; +#endif +#ifdef ENOTCONN + case WSAENOTCONN: return ENOTCONN; +#endif +#ifdef ETIMEDOUT + case WSAETIMEDOUT: return ETIMEDOUT; +#endif +#ifdef ECONNREFUSED + case WSAECONNREFUSED: return ECONNREFUSED; +#endif +#ifdef EHOSTUNREACH + case WSAEHOSTUNREACH: return EHOSTUNREACH; +#endif +#ifdef ENAMETOOLONG + case WSAENAMETOOLONG: return ENAMETOOLONG; +#endif + default: return EIO; + } +} + +void +mrb_hal_socket_set_errno_from_last_error(void) +{ + errno = wsa_to_errno(WSAGetLastError()); +} + /* * Socket Control Operations */ @@ -54,6 +172,7 @@ mrb_hal_socket_set_nonblock(mrb_state *mrb, int fd, int nonblock) u_long mode = nonblock ? 1 : 0; int result = ioctlsocket(fd, FIONBIO, &mode); if (result != NO_ERROR) { + mrb_hal_socket_set_errno_from_last_error(); return -1; } return 0; diff --git a/mrbgems/mruby-socket/src/socket.c b/mrbgems/mruby-socket/src/socket.c index 815bc2402..886d9d6f7 100644 --- a/mrbgems/mruby-socket/src/socket.c +++ b/mrbgems/mruby-socket/src/socket.c @@ -134,6 +134,16 @@ static inline const char *get_pf_name(int family) { #define E_SOCKET_ERROR mrb_class_get_id(mrb, MRB_SYM(SocketError)) +/* Raise a SystemCallError for the most recent socket-API failure. + * On Windows the HAL translates WSAGetLastError() into errno first; + * on POSIX errno is already set, so this is just mrb_sys_fail. */ +static mrb_noreturn void +sock_sys_fail(mrb_state *mrb, const char *mesg) +{ + mrb_hal_socket_set_errno_from_last_error(); + mrb_sys_fail(mrb, mesg); +} + struct gen_addrinfo_args { struct RClass *klass; struct addrinfo *addrinfo; @@ -295,7 +305,7 @@ sa2addrlist(mrb_state *mrb, const struct sockaddr *sa, socklen_t salen) port = ntohs(port); mrb_value host = mrb_str_new_capa(mrb, NI_MAXHOST); if (getnameinfo(sa, salen, RSTRING_PTR(host), NI_MAXHOST, NULL, 0, NI_NUMERICHOST) == -1) - mrb_sys_fail(mrb, "getnameinfo"); + sock_sys_fail(mrb, "getnameinfo"); mrb_str_resize(mrb, host, strlen(RSTRING_PTR(host))); mrb_value ary = mrb_ary_new_capa(mrb, 4); @@ -344,7 +354,7 @@ mrb_basicsocket_getpeereid(mrb_state *mrb, mrb_value self) uid_t euid; int s = socket_fd(mrb, self); if (getpeereid(s, &euid, &egid) != 0) - mrb_sys_fail(mrb, "getpeereid"); + sock_sys_fail(mrb, "getpeereid"); mrb_value ary = mrb_ary_new_capa(mrb, 2); mrb_ary_push(mrb, ary, mrb_fixnum_value((mrb_int)euid)); @@ -371,7 +381,7 @@ mrb_basicsocket_getpeername(mrb_state *mrb, mrb_value self) socklen_t salen = sizeof(ss); if (getpeername(socket_fd(mrb, self), (struct sockaddr*)&ss, &salen) != 0) - mrb_sys_fail(mrb, "getpeername"); + sock_sys_fail(mrb, "getpeername"); return mrb_str_new(mrb, (char*)&ss, salen); } @@ -391,7 +401,7 @@ mrb_basicsocket_getsockname(mrb_state *mrb, mrb_value self) socklen_t salen = sizeof(ss); if (getsockname(socket_fd(mrb, self), (struct sockaddr*)&ss, &salen) != 0) - mrb_sys_fail(mrb, "getsockname"); + sock_sys_fail(mrb, "getsockname"); return mrb_str_new(mrb, (char*)&ss, salen); } @@ -636,7 +646,7 @@ mrb_basicsocket_getsockopt(mrb_state *mrb, mrb_value self) socklen_t optlen = sizeof(opt); if (getsockopt(s, (int)level, (int)optname, opt, &optlen) == -1) - mrb_sys_fail(mrb, "getsockopt"); + sock_sys_fail(mrb, "getsockopt"); mrb_int family = socket_family(s); mrb_value data = mrb_str_new(mrb, opt, optlen); mrb_value args[4] = {mrb_fixnum_value(family), mrb_fixnum_value(level), mrb_fixnum_value(optname), data}; @@ -662,7 +672,7 @@ mrb_basicsocket_recv(mrb_state *mrb, mrb_value self) mrb_value buf = mrb_str_new_capa(mrb, maxlen); ssize_t n = recv(socket_fd(mrb, self), RSTRING_PTR(buf), (fsize_t)maxlen, (int)flags); if (n == -1) - mrb_sys_fail(mrb, "recv"); + sock_sys_fail(mrb, "recv"); mrb_str_resize(mrb, buf, (mrb_int)n); return buf; } @@ -686,7 +696,7 @@ mrb_basicsocket_recvfrom(mrb_state *mrb, mrb_value self) mrb_value sa = mrb_str_new_capa(mrb, socklen); ssize_t n = recvfrom(socket_fd(mrb, self), RSTRING_PTR(buf), (fsize_t)maxlen, (int)flags, (struct sockaddr*)RSTRING_PTR(sa), &socklen); if (n == -1) - mrb_sys_fail(mrb, "recvfrom"); + sock_sys_fail(mrb, "recvfrom"); mrb_str_resize(mrb, buf, (mrb_int)n); mrb_str_resize(mrb, sa, (mrb_int)socklen); @@ -721,7 +731,7 @@ mrb_basicsocket_send(mrb_state *mrb, mrb_value self) n = sendto(socket_fd(mrb, self), RSTRING_PTR(mesg), (fsize_t)RSTRING_LEN(mesg), (int)flags, (const struct sockaddr*)RSTRING_PTR(dest), (fsize_t)RSTRING_LEN(dest)); } if (n == -1) - mrb_sys_fail(mrb, "send"); + sock_sys_fail(mrb, "send"); return mrb_fixnum_value((mrb_int)n); } @@ -743,7 +753,7 @@ mrb_basicsocket_setnonblock(mrb_state *mrb, mrb_value self) int fd = socket_fd(mrb, self); if (mrb_hal_socket_set_nonblock(mrb, fd, nonblocking) == -1) - mrb_sys_fail(mrb, "set_nonblock"); + sock_sys_fail(mrb, "set_nonblock"); return mrb_nil_value(); } @@ -801,7 +811,7 @@ mrb_basicsocket_setsockopt(mrb_state *mrb, mrb_value self) int s = socket_fd(mrb, self); if (setsockopt(s, (int)level, (int)optname, RSTRING_PTR(optval), (socklen_t)RSTRING_LEN(optval)) == -1) - mrb_sys_fail(mrb, "setsockopt"); + sock_sys_fail(mrb, "setsockopt"); return mrb_fixnum_value(0); } @@ -822,7 +832,7 @@ mrb_basicsocket_shutdown(mrb_state *mrb, mrb_value self) mrb_get_args(mrb, "|i", &how); if (shutdown(socket_fd(mrb, self), (int)how) != 0) - mrb_sys_fail(mrb, "shutdown"); + sock_sys_fail(mrb, "shutdown"); return mrb_fixnum_value(0); } @@ -935,7 +945,7 @@ mrb_ipsocket_recvfrom(mrb_state *mrb, mrb_value self) ssize_t n = recvfrom(fd, RSTRING_PTR(buf), (fsize_t)maxlen, (int)flags, (struct sockaddr*)&ss, &socklen); if (n == -1) { - mrb_sys_fail(mrb, "recvfrom"); + sock_sys_fail(mrb, "recvfrom"); } mrb_str_resize(mrb, buf, (mrb_int)n); @@ -965,7 +975,7 @@ mrb_socket_gethostname(mrb_state *mrb, mrb_value cls) mrb_value buf = mrb_str_new_capa(mrb, (mrb_int)bufsize); if (gethostname(RSTRING_PTR(buf), (fsize_t)bufsize) != 0) - mrb_sys_fail(mrb, "gethostname"); + sock_sys_fail(mrb, "gethostname"); mrb_str_resize(mrb, buf, (mrb_int)strlen(RSTRING_PTR(buf))); return buf; } @@ -985,7 +995,7 @@ mrb_socket_accept(mrb_state *mrb, mrb_value klass) mrb_get_args(mrb, "i", &s0); int s1 = (int)accept(s0, NULL, NULL); if (s1 == -1) { - mrb_sys_fail(mrb, "accept"); + sock_sys_fail(mrb, "accept"); } return mrb_fixnum_value(s1); } @@ -1003,7 +1013,7 @@ mrb_socket_accept2(mrb_state *mrb, mrb_value klass) int s1 = (int)accept(s0, (struct sockaddr*)RSTRING_PTR(sastr), &socklen); if (s1 == -1) { - mrb_sys_fail(mrb, "accept"); + sock_sys_fail(mrb, "accept"); } mrb_str_resize(mrb, sastr, socklen); @@ -1026,7 +1036,7 @@ mrb_socket_bind(mrb_state *mrb, mrb_value klass) mrb_get_args(mrb, "iS", &s, &sastr); if (bind((int)s, (struct sockaddr*)RSTRING_PTR(sastr), (socklen_t)RSTRING_LEN(sastr)) == -1) { - mrb_sys_fail(mrb, "bind"); + sock_sys_fail(mrb, "bind"); } return mrb_nil_value(); } @@ -1045,7 +1055,7 @@ mrb_socket_connect(mrb_state *mrb, mrb_value klass) mrb_get_args(mrb, "iS", &s, &sastr); if (connect((int)s, (struct sockaddr*)RSTRING_PTR(sastr), (socklen_t)RSTRING_LEN(sastr)) == -1) { - mrb_sys_fail(mrb, "connect"); + sock_sys_fail(mrb, "connect"); } return mrb_nil_value(); } @@ -1063,7 +1073,7 @@ mrb_socket_listen(mrb_state *mrb, mrb_value klass) mrb_get_args(mrb, "ii", &s, &backlog); if (listen((int)s, (int)backlog) == -1) { - mrb_sys_fail(mrb, "listen"); + sock_sys_fail(mrb, "listen"); } return mrb_nil_value(); } @@ -1119,7 +1129,7 @@ mrb_socket_socketpair(mrb_state *mrb, mrb_value klass) mrb_get_args(mrb, "iii", &domain, &type, &protocol); if (mrb_hal_socket_socketpair(mrb, (int)domain, (int)type, (int)protocol, sv) == -1) { - mrb_sys_fail(mrb, "socketpair"); + sock_sys_fail(mrb, "socketpair"); } mrb_value ary = mrb_ary_new_capa(mrb, 2); @@ -1145,7 +1155,7 @@ mrb_socket_socket(mrb_state *mrb, mrb_value klass) int s = (int)socket((int)domain, (int)type, (int)protocol); if (s == -1) - mrb_sys_fail(mrb, "socket"); + sock_sys_fail(mrb, "socket"); return mrb_fixnum_value(s); } @@ -1220,7 +1230,7 @@ mrb_win32_basicsocket_sysread(mrb_state *mrb, mrb_value self) } break; case SOCKET_ERROR: /* Error */ - mrb_sys_fail(mrb, "recv"); + sock_sys_fail(mrb, "recv"); break; default: if (RSTRING_LEN(buf) != ret) { @@ -1263,7 +1273,7 @@ mrb_win32_basicsocket_syswrite(mrb_state *mrb, mrb_value self) int n = send(sd, RSTRING_PTR(str), (int)RSTRING_LEN(str), 0); if (n == SOCKET_ERROR) - mrb_sys_fail(mrb, "send"); + sock_sys_fail(mrb, "send"); return mrb_int_value(mrb, n); }