mruby-socket: introduce HAL for platform abstraction

separate platform-specific socket operations into HAL implementations
for POSIX (Linux/macOS/BSD/Unix) and Windows platforms to improve
portability and maintainability

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
Yukihiro "Matz" Matsumoto
2025-10-15 17:26:40 +09:00
parent f4dcc3dc3d
commit 0524526e41
7 changed files with 478 additions and 124 deletions
+8
View File
@@ -0,0 +1,8 @@
MRuby::Gem::Specification.new('hal-posix-socket') do |spec|
spec.license = 'MIT'
spec.author = 'mruby developers'
spec.summary = 'POSIX HAL for mruby-socket (Linux, macOS, BSD, Unix)'
# HAL gem depends on feature gem - brings in mruby-socket automatically
spec.add_dependency 'mruby-socket', core: 'mruby-socket'
end
+158
View File
@@ -0,0 +1,158 @@
/*
** socket_hal.c - POSIX HAL implementation for mruby-socket
**
** See Copyright Notice in mruby.h
**
** POSIX implementation for socket operations using standard POSIX APIs.
** Supported platforms: Linux, macOS, BSD, Unix
*/
#include <mruby.h>
#include <mruby/string.h>
#include <mruby/class.h>
#include <mruby/error.h>
#include <mruby/presym.h>
#include <socket_hal.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
/*
* Socket HAL Initialization/Finalization
*/
void
mrb_socket_hal_init(mrb_state *mrb)
{
(void)mrb;
/* No initialization needed for POSIX sockets */
}
void
mrb_socket_hal_final(mrb_state *mrb)
{
(void)mrb;
/* No cleanup needed for POSIX sockets */
}
/*
* Socket Control Operations
*/
int
mrb_socket_hal_set_nonblock(mrb_state *mrb, int fd, int nonblock)
{
(void)mrb;
int flags = fcntl(fd, F_GETFL, 0);
if (flags == -1) {
return -1;
}
if (nonblock) {
flags |= O_NONBLOCK;
}
else {
flags &= ~O_NONBLOCK;
}
if (fcntl(fd, F_SETFL, flags) == -1) {
return -1;
}
return 0;
}
/*
* Address Conversion Functions
*/
const char*
mrb_socket_hal_inet_ntop(int af, const void *src, char *dst, size_t size)
{
return inet_ntop(af, src, dst, (socklen_t)size);
}
int
mrb_socket_hal_inet_pton(int af, const char *src, void *dst)
{
return inet_pton(af, src, dst);
}
/*
* Platform-Specific Socket Features
*/
mrb_value
mrb_socket_hal_sockaddr_un(mrb_state *mrb, const char *path, size_t pathlen)
{
struct sockaddr_un *sunp;
if (pathlen > sizeof(sunp->sun_path) - 1) {
mrb_raisef(mrb, mrb_class_get_id(mrb, MRB_SYM(ArgumentError)),
"too long unix socket path (max: %d bytes)",
(int)sizeof(sunp->sun_path) - 1);
}
mrb_value s = mrb_str_new_capa(mrb, sizeof(struct sockaddr_un));
sunp = (struct sockaddr_un*)RSTRING_PTR(s);
#ifdef HAVE_SA_LEN
sunp->sun_len = sizeof(struct sockaddr_un);
#endif
sunp->sun_family = AF_UNIX;
memcpy(sunp->sun_path, path, pathlen);
sunp->sun_path[pathlen] = '\0';
mrb_str_resize(mrb, s, sizeof(struct sockaddr_un));
return s;
}
int
mrb_socket_hal_socketpair(mrb_state *mrb, int domain, int type, int protocol, int sv[2])
{
(void)mrb;
return socketpair(domain, type, protocol, sv);
}
mrb_value
mrb_socket_hal_unix_path(mrb_state *mrb, const char *sockaddr, size_t socklen)
{
const struct sockaddr *sa = (const struct sockaddr*)sockaddr;
if (sa->sa_family != AF_UNIX) {
mrb_raise(mrb, mrb_class_get_id(mrb, MRB_SYM(SocketError)), "need AF_UNIX address");
}
if (socklen < offsetof(struct sockaddr_un, sun_path) + 1) {
return mrb_str_new(mrb, "", 0);
}
return mrb_str_new_cstr(mrb, ((const struct sockaddr_un*)sockaddr)->sun_path);
}
/*
* Gem initialization
*/
void
mrb_hal_posix_socket_gem_init(mrb_state *mrb)
{
(void)mrb;
/* HAL interface functions are called by mruby-socket gem */
}
void
mrb_hal_posix_socket_gem_final(mrb_state *mrb)
{
(void)mrb;
/* Cleanup handled by mrb_socket_hal_final called from mruby-socket */
}
+12
View File
@@ -0,0 +1,12 @@
MRuby::Gem::Specification.new('hal-win-socket') do |spec|
spec.license = 'MIT'
spec.author = 'mruby developers'
spec.summary = 'Windows HAL for mruby-socket (Windows, MinGW)'
# HAL gem depends on feature gem - brings in mruby-socket automatically
spec.add_dependency 'mruby-socket', core: 'mruby-socket'
# Link Windows socket libraries
spec.linker.libraries << "wsock32"
spec.linker.libraries << "ws2_32"
end
+174
View File
@@ -0,0 +1,174 @@
/*
** 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
*/
#include <mruby.h>
#include <mruby/string.h>
#include <mruby/class.h>
#include <mruby/error.h>
#include <mruby/presym.h>
#include <socket_hal.h>
#define _WIN32_WINNT 0x0501
#include <winsock2.h>
#include <ws2tcpip.h>
#include <windows.h>
#include <string.h>
/*
* Socket HAL Initialization/Finalization
*/
void
mrb_socket_hal_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_socket_hal_final(mrb_state *mrb)
{
(void)mrb;
WSACleanup();
}
/*
* Socket Control Operations
*/
int
mrb_socket_hal_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) {
return -1;
}
return 0;
}
/*
* Address Conversion Functions
*/
const char*
mrb_socket_hal_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_socket_hal_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_socket_hal_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_socket_hal_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_socket_hal_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();
}
/*
* Gem initialization
*/
void
mrb_hal_win_socket_gem_init(mrb_state *mrb)
{
(void)mrb;
/* HAL interface functions are called by mruby-socket gem */
}
void
mrb_hal_win_socket_gem_final(mrb_state *mrb)
{
(void)mrb;
/* Cleanup handled by mrb_socket_hal_final called from mruby-socket */
}
+83
View File
@@ -0,0 +1,83 @@
/*
** socket_hal.h - Socket HAL (Hardware Abstraction Layer) interface
**
** See Copyright Notice in mruby.h
**
** This header defines the platform-independent socket HAL interface.
** Platform-specific implementations are provided by HAL gems:
** - hal-posix-socket: POSIX socket implementation (Linux, macOS, BSD, Unix)
** - hal-win-socket: Windows socket implementation (Windows, MinGW)
*/
#ifndef MRUBY_SOCKET_HAL_H
#define MRUBY_SOCKET_HAL_H
#include <mruby.h>
#ifdef __cplusplus
extern "C" {
#endif
/*
* Socket HAL Initialization/Finalization
*/
/* Initialize socket subsystem (e.g., WSAStartup on Windows) */
void mrb_socket_hal_init(mrb_state *mrb);
/* Finalize socket subsystem (e.g., WSACleanup on Windows) */
void mrb_socket_hal_final(mrb_state *mrb);
/*
* Socket Control Operations
*/
/* Set non-blocking mode on socket
* Returns 0 on success, -1 on error (sets errno) */
int mrb_socket_hal_set_nonblock(mrb_state *mrb, int fd, int nonblock);
/*
* Address Conversion Functions
*/
/* Convert network address to presentation format (string)
* af: address family (AF_INET, AF_INET6)
* src: network address in binary form
* dst: buffer for string result
* size: size of dst buffer
* Returns: dst on success, NULL on error */
const char* mrb_socket_hal_inet_ntop(int af, const void *src, char *dst, size_t size);
/* Convert presentation format (string) to network address
* af: address family (AF_INET, AF_INET6)
* src: string representation of address
* dst: buffer for network address result
* Returns: 1 on success, 0 if src is not valid, -1 on error */
int mrb_socket_hal_inet_pton(int af, const char *src, void *dst);
/*
* Platform-Specific Socket Features
*/
/* Create Unix domain socket address structure
* path: Unix socket path
* Returns: packed sockaddr string, or raises exception if not supported */
mrb_value mrb_socket_hal_sockaddr_un(mrb_state *mrb, const char *path, size_t pathlen);
/* Create a pair of connected sockets
* domain: address family (e.g., AF_UNIX)
* type: socket type (e.g., SOCK_STREAM)
* protocol: protocol (usually 0)
* sv: array to receive the two socket descriptors
* Returns: 0 on success, -1 on error (sets errno) */
int mrb_socket_hal_socketpair(mrb_state *mrb, int domain, int type, int protocol, int sv[2]);
/* Get Unix socket path from sockaddr
* Returns: Unix socket path string, or raises exception if not supported */
mrb_value mrb_socket_hal_unix_path(mrb_state *mrb, const char *sockaddr, size_t socklen);
#ifdef __cplusplus
}
#endif
#endif /* MRUBY_SOCKET_HAL_H */
+26 -6
View File
@@ -5,13 +5,33 @@ MRuby::Gem::Specification.new('mruby-socket') do |spec|
#spec.cc.defines << "HAVE_SA_LEN=0"
# If Windows, use winsock
if spec.for_windows?
spec.linker.libraries << "wsock32"
spec.linker.libraries << "ws2_32"
end
spec.add_dependency('mruby-io', :core => 'mruby-io')
spec.add_dependency('mruby-error', :core => 'mruby-error')
# spec.add_dependency('mruby-mtest')
# Check if HAL gem is loaded
# HAL gems must be explicitly specified in build config (recommended) or via auto-selection below
spec.build.gems.one? { |g| g.name =~ /^hal-.*-socket$/ } or begin
# No HAL found - determine appropriate error message or auto-load
suggested_hal = if spec.for_windows?
'hal-win-socket'
elsif RUBY_PLATFORM =~ /linux|darwin|bsd/
'hal-posix-socket'
else
nil
end
if suggested_hal
# Auto-load HAL gem for convenience (for development)
# This works because HAL gems declare dependency on mruby-socket
warn "mruby-socket: No HAL specified, loading #{suggested_hal} (explicit selection recommended)"
spec.build.gem core: suggested_hal
else
# Unknown platform - fail with helpful message
fail "mruby-socket: No HAL available for platform '#{RUBY_PLATFORM}'.\n" \
"Please specify HAL gem explicitly in your build config:\n" \
" conf.gem core: 'hal-posix-socket' # For Linux/macOS/BSD\n" \
" conf.gem core: 'hal-win-socket' # For Windows"
end
end
end
+17 -118
View File
@@ -6,12 +6,9 @@
#ifdef _WIN32
#define _WIN32_WINNT 0x0501
#include <winsock2.h>
#include <ws2tcpip.h>
#include <windows.h>
#include <winerror.h>
#define SHUT_RDWR SD_BOTH
typedef int fsize_t;
#else
@@ -42,6 +39,7 @@
#include <mruby/presym.h>
#include <mruby/ext/io.h>
#include <socket_hal.h>
/* Address family information for compact lookup table */
typedef struct {
@@ -137,53 +135,6 @@ static inline const char *get_pf_name(int family) {
#define E_SOCKET_ERROR mrb_class_get_id(mrb, MRB_SYM(SocketError))
#ifdef _WIN32
/* Windows implementation of inet_ntop - converts network address to string */
static const char *inet_ntop(int af, const void *src, char *dst, socklen_t cnt)
{
if (af == AF_INET) {
struct sockaddr_in in = {0};
in.sin_family = AF_INET;
memcpy(&in.sin_addr, src, sizeof(struct in_addr));
getnameinfo((struct sockaddr*)&in, sizeof(struct sockaddr_in),
dst, cnt, NULL, 0, NI_NUMERICHOST);
return dst;
}
else if (af == AF_INET6) {
struct sockaddr_in6 in = {0};
in.sin6_family = AF_INET6;
memcpy(&in.sin6_addr, src, sizeof(struct in_addr6));
getnameinfo((struct sockaddr*)&in, sizeof(struct sockaddr_in6),
dst, cnt, NULL, 0, NI_NUMERICHOST);
return dst;
}
return NULL;
}
/* Windows implementation of inet_pton - converts string address to network format */
static int inet_pton(int af, const char *src, void *dst)
{
struct addrinfo hints = {0};
hints.ai_family = af;
struct addrinfo *res;
if (getaddrinfo(src, NULL, &hints, &res) != 0) {
printf("Couldn't resolve host %s\n", src);
return -1;
}
for (struct addrinfo *r = res; r; r = r->ai_next) {
memcpy(dst, r->ai_addr, r->ai_addrlen);
}
freeaddrinfo(res);
return 0;
}
#endif
struct gen_addrinfo_args {
struct RClass *klass;
struct addrinfo *addrinfo;
@@ -306,7 +257,6 @@ mrb_addrinfo_getnameinfo(mrb_state *mrb, mrb_value self)
return ary;
}
#ifndef _WIN32
/*
* call-seq:
* addrinfo.unix_path -> string
@@ -320,16 +270,12 @@ mrb_addrinfo_unix_path(mrb_state *mrb, mrb_value self)
{
mrb_value sastr = mrb_iv_get(mrb, self, MRB_IVSYM(sockaddr));
if (!mrb_string_p(sastr) || ((struct sockaddr*)RSTRING_PTR(sastr))->sa_family != AF_UNIX)
mrb_raise(mrb, E_SOCKET_ERROR, "need AF_UNIX address");
if (RSTRING_LEN(sastr) < (mrb_int)offsetof(struct sockaddr_un, sun_path) + 1) {
return mrb_str_new(mrb, "", 0);
}
else {
return mrb_str_new_cstr(mrb, ((struct sockaddr_un*)RSTRING_PTR(sastr))->sun_path);
if (!mrb_string_p(sastr)) {
mrb_raise(mrb, E_SOCKET_ERROR, "invalid sockaddr");
}
return mrb_socket_hal_unix_path(mrb, RSTRING_PTR(sastr), (size_t)RSTRING_LEN(sastr));
}
#endif
/* Helper to convert sockaddr to address list array [family, port, host, host] */
static mrb_value
@@ -793,27 +739,13 @@ static mrb_value
mrb_basicsocket_setnonblock(mrb_state *mrb, mrb_value self)
{
mrb_bool nonblocking;
#ifdef _WIN32
u_long mode = 1;
#endif
mrb_get_args(mrb, "b", &nonblocking);
int fd = socket_fd(mrb, self);
#ifdef _WIN32
int flags = ioctlsocket(fd, FIONBIO, &mode);
if (flags != NO_ERROR)
mrb_sys_fail(mrb, "ioctlsocket");
#else
int flags = fcntl(fd, F_GETFL, 0);
if (flags == 1)
mrb_sys_fail(mrb, "fcntl");
if (nonblocking)
flags |= O_NONBLOCK;
else
flags &= ~O_NONBLOCK;
if (fcntl(fd, F_SETFL, flags) == -1)
mrb_sys_fail(mrb, "fcntl");
#endif
if (mrb_socket_hal_set_nonblock(mrb, fd, nonblocking) == -1)
mrb_sys_fail(mrb, "set_nonblock");
return mrb_nil_value();
}
@@ -927,7 +859,7 @@ mrb_ipsocket_ntop(mrb_state *mrb, mrb_value klass)
mrb_get_args(mrb, "is", &af, &addr, &n);
if ((af == AF_INET && n != 4) || (af == AF_INET6 && n != 16) ||
inet_ntop((int)af, addr, buf, sizeof(buf)) == NULL)
mrb_socket_hal_inet_ntop((int)af, addr, buf, sizeof(buf)) == NULL)
mrb_raise(mrb, E_ARGUMENT_ERROR, "invalid address");
return mrb_str_new_cstr(mrb, buf);
}
@@ -954,13 +886,13 @@ mrb_ipsocket_pton(mrb_state *mrb, mrb_value klass)
if (af == AF_INET) {
struct in_addr in;
if (inet_pton(AF_INET, buf, (void*)&in.s_addr) != 1)
if (mrb_socket_hal_inet_pton(AF_INET, buf, (void*)&in.s_addr) != 1)
goto invalid;
return mrb_str_new(mrb, (char*)&in.s_addr, 4);
}
else if (af == AF_INET6) {
struct in6_addr in6;
if (inet_pton(AF_INET6, buf, (void*)&in6.s6_addr) != 1)
if (mrb_socket_hal_inet_pton(AF_INET6, buf, (void*)&in6.s6_addr) != 1)
goto invalid;
return mrb_str_new(mrb, (char*)&in6.s6_addr, 16);
}
@@ -1156,28 +1088,10 @@ mrb_socket_sockaddr_family(mrb_state *mrb, mrb_value klass)
static mrb_value
mrb_socket_sockaddr_un(mrb_state *mrb, mrb_value klass)
{
#ifdef _WIN32
mrb_raise(mrb, E_NOTIMP_ERROR, "sockaddr_un unsupported on Windows");
return mrb_nil_value();
#else
mrb_value path;
struct sockaddr_un *sunp;
mrb_get_args(mrb, "S", &path);
if ((size_t)RSTRING_LEN(path) > sizeof(sunp->sun_path) - 1) {
mrb_raisef(mrb, E_ARGUMENT_ERROR, "too long unix socket path (max: %d bytes)", (int)sizeof(sunp->sun_path) - 1);
}
mrb_value s = mrb_str_new_capa(mrb, sizeof(struct sockaddr_un));
sunp = (struct sockaddr_un*)RSTRING_PTR(s);
#if HAVE_SA_LEN
sunp->sun_len = sizeof(struct sockaddr_un);
#endif
sunp->sun_family = AF_UNIX;
memcpy(sunp->sun_path, RSTRING_PTR(path), RSTRING_LEN(path));
sunp->sun_path[RSTRING_LEN(path)] = '\0';
mrb_str_resize(mrb, s, sizeof(struct sockaddr_un));
return s;
#endif
return mrb_socket_hal_sockaddr_un(mrb, RSTRING_PTR(path), (size_t)RSTRING_LEN(path));
}
/*
@@ -1193,24 +1107,19 @@ mrb_socket_sockaddr_un(mrb_state *mrb, mrb_value klass)
static mrb_value
mrb_socket_socketpair(mrb_state *mrb, mrb_value klass)
{
#ifdef _WIN32
mrb_raise(mrb, E_NOTIMP_ERROR, "socketpair unsupported on Windows");
return mrb_nil_value();
#else
mrb_int domain, type, protocol;
int sv[2];
mrb_get_args(mrb, "iii", &domain, &type, &protocol);
mrb_value ary = mrb_ary_new_capa(mrb, 2);
if (socketpair(domain, type, protocol, sv) == -1) {
if (mrb_socket_hal_socketpair(mrb, (int)domain, (int)type, (int)protocol, sv) == -1) {
mrb_sys_fail(mrb, "socketpair");
}
mrb_value ary = mrb_ary_new_capa(mrb, 2);
mrb_ary_push(mrb, ary, mrb_fixnum_value(sv[0]));
mrb_ary_push(mrb, ary, mrb_fixnum_value(sv[1]));
return ary;
#endif
}
/*
@@ -1357,20 +1266,12 @@ mrb_win32_basicsocket_syswrite(mrb_state *mrb, mrb_value self)
void
mrb_mruby_socket_gem_init(mrb_state* mrb)
{
#ifdef _WIN32
WSADATA wsaData;
int result;
result = WSAStartup(MAKEWORD(2,2), &wsaData);
if (result != NO_ERROR)
mrb_raise(mrb, E_RUNTIME_ERROR, "WSAStartup failed");
#endif
mrb_socket_hal_init(mrb);
struct RClass *ainfo = mrb_define_class_id(mrb, MRB_SYM(Addrinfo), mrb->object_class);
mrb_define_class_method_id(mrb, ainfo, MRB_SYM(getaddrinfo), mrb_addrinfo_getaddrinfo, MRB_ARGS_REQ(2)|MRB_ARGS_OPT(4));
mrb_define_method_id(mrb, ainfo, MRB_SYM(getnameinfo), mrb_addrinfo_getnameinfo, MRB_ARGS_OPT(1));
#ifndef _WIN32
mrb_define_method_id(mrb, ainfo, MRB_SYM(unix_path), mrb_addrinfo_unix_path, MRB_ARGS_NONE());
#endif
struct RClass *io = mrb_class_get_id(mrb, MRB_SYM(IO));
@@ -1447,7 +1348,5 @@ mrb_mruby_socket_gem_init(mrb_state* mrb)
void
mrb_mruby_socket_gem_final(mrb_state* mrb)
{
#ifdef _WIN32
WSACleanup();
#endif
mrb_socket_hal_final(mrb);
}