Files
mruby-mruby/mrbgems/mruby-socket/ports/win/socket_hal.c
T
Yukihiro "Matz" Matsumoto f267925d45 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 <noreply@anthropic.com>
2026-05-07 11:01:46 +09:00

278 lines
6.3 KiB
C

/*
** socket_hal.c - Windows HAL implementation for mruby-socket
**
** See Copyright Notice in mruby.h
**
** Windows implementation for socket operations using Winsock APIs.
** Supported platforms: Windows, MinGW
*/
#ifndef _WIN32_WINNT
#define _WIN32_WINNT 0x0501 // need Windows XP or later
#endif
#include <mruby.h>
#include <mruby/string.h>
#include <mruby/class.h>
#include <mruby/error.h>
#include "socket_hal.h"
#include <winsock2.h>
#include <ws2tcpip.h>
#include <windows.h>
#include <errno.h>
#include <string.h>
/*
* Socket HAL Initialization/Finalization
*/
void
mrb_hal_socket_init(mrb_state *mrb)
{
WSADATA wsaData;
int result = WSAStartup(MAKEWORD(2, 2), &wsaData);
if (result != NO_ERROR) {
mrb_raise(mrb, mrb_class_get_id(mrb, MRB_SYM(RuntimeError)), "WSAStartup failed");
}
}
void
mrb_hal_socket_final(mrb_state *mrb)
{
(void)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
*/
int
mrb_hal_socket_set_nonblock(mrb_state *mrb, int fd, int nonblock)
{
(void)mrb;
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;
}
/*
* Address Conversion Functions
*/
const char*
mrb_hal_socket_inet_ntop(int af, const void *src, char *dst, size_t size)
{
if (af == AF_INET) {
struct sockaddr_in in = {0};
in.sin_family = AF_INET;
memcpy(&in.sin_addr, src, sizeof(struct in_addr));
if (getnameinfo((struct sockaddr*)&in, sizeof(struct sockaddr_in),
dst, (DWORD)size, NULL, 0, NI_NUMERICHOST) == 0) {
return dst;
}
return NULL;
}
else if (af == AF_INET6) {
struct sockaddr_in6 in = {0};
in.sin6_family = AF_INET6;
memcpy(&in.sin6_addr, src, sizeof(struct in6_addr));
if (getnameinfo((struct sockaddr*)&in, sizeof(struct sockaddr_in6),
dst, (DWORD)size, NULL, 0, NI_NUMERICHOST) == 0) {
return dst;
}
return NULL;
}
return NULL;
}
int
mrb_hal_socket_inet_pton(int af, const char *src, void *dst)
{
struct addrinfo hints = {0};
hints.ai_family = af;
hints.ai_flags = AI_NUMERICHOST;
struct addrinfo *res;
if (getaddrinfo(src, NULL, &hints, &res) != 0) {
return 0; /* Invalid address */
}
if (res == NULL) {
return 0;
}
if (af == AF_INET && res->ai_family == AF_INET) {
memcpy(dst, &((struct sockaddr_in*)res->ai_addr)->sin_addr, sizeof(struct in_addr));
freeaddrinfo(res);
return 1;
}
else if (af == AF_INET6 && res->ai_family == AF_INET6) {
memcpy(dst, &((struct sockaddr_in6*)res->ai_addr)->sin6_addr, sizeof(struct in6_addr));
freeaddrinfo(res);
return 1;
}
freeaddrinfo(res);
return 0;
}
/*
* Platform-Specific Socket Features
*/
mrb_value
mrb_hal_socket_sockaddr_un(mrb_state *mrb, const char *path, size_t pathlen)
{
(void)path;
(void)pathlen;
mrb_raise(mrb, mrb_class_get_id(mrb, MRB_SYM(NotImplementedError)),
"sockaddr_un unsupported on Windows");
return mrb_nil_value();
}
int
mrb_hal_socket_socketpair(mrb_state *mrb, int domain, int type, int protocol, int sv[2])
{
(void)mrb;
(void)domain;
(void)type;
(void)protocol;
(void)sv;
/* socketpair is not supported on Windows */
errno = ENOSYS;
return -1;
}
mrb_value
mrb_hal_socket_unix_path(mrb_state *mrb, const char *sockaddr, size_t socklen)
{
(void)sockaddr;
(void)socklen;
mrb_raise(mrb, mrb_class_get_id(mrb, MRB_SYM(NotImplementedError)),
"unix_path unsupported on Windows");
return mrb_nil_value();
}