mirror of
https://github.com/mruby/mruby
synced 2026-06-08 16:11:16 +00:00
f267925d45
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>
278 lines
6.3 KiB
C
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();
|
|
}
|