mirror of
https://github.com/ruby/ruby.git
synced 2022-11-09 12:17:21 -05:00
ffd0820ab3
This removes the related tests, and puts the related specs behind version guards. This affects all code in lib, including some libraries that may want to support older versions of Ruby.
844 lines
19 KiB
C
844 lines
19 KiB
C
/************************************************
|
|
|
|
init.c -
|
|
|
|
created at: Thu Mar 31 12:21:29 JST 1994
|
|
|
|
Copyright (C) 1993-2007 Yukihiro Matsumoto
|
|
|
|
************************************************/
|
|
|
|
#include "rubysocket.h"
|
|
|
|
#ifdef _WIN32
|
|
VALUE rb_w32_conv_from_wchar(const WCHAR *wstr, rb_encoding *enc);
|
|
#endif
|
|
|
|
VALUE rb_cBasicSocket;
|
|
VALUE rb_cIPSocket;
|
|
VALUE rb_cTCPSocket;
|
|
VALUE rb_cTCPServer;
|
|
VALUE rb_cUDPSocket;
|
|
#ifdef AF_UNIX
|
|
VALUE rb_cUNIXSocket;
|
|
VALUE rb_cUNIXServer;
|
|
#endif
|
|
VALUE rb_cSocket;
|
|
VALUE rb_cAddrinfo;
|
|
|
|
VALUE rb_eSocket;
|
|
|
|
#ifdef SOCKS
|
|
VALUE rb_cSOCKSSocket;
|
|
#endif
|
|
|
|
int rsock_do_not_reverse_lookup = 1;
|
|
static VALUE sym_wait_readable;
|
|
|
|
void
|
|
rsock_raise_socket_error(const char *reason, int error)
|
|
{
|
|
#ifdef EAI_SYSTEM
|
|
int e;
|
|
if (error == EAI_SYSTEM && (e = errno) != 0)
|
|
rb_syserr_fail(e, reason);
|
|
#endif
|
|
#ifdef _WIN32
|
|
rb_encoding *enc = rb_default_internal_encoding();
|
|
VALUE msg = rb_sprintf("%s: ", reason);
|
|
if (!enc) enc = rb_default_internal_encoding();
|
|
rb_str_concat(msg, rb_w32_conv_from_wchar(gai_strerrorW(error), enc));
|
|
rb_exc_raise(rb_exc_new_str(rb_eSocket, msg));
|
|
#else
|
|
rb_raise(rb_eSocket, "%s: %s", reason, gai_strerror(error));
|
|
#endif
|
|
}
|
|
|
|
#ifdef _WIN32
|
|
#define is_socket(fd) rb_w32_is_socket(fd)
|
|
#else
|
|
static int
|
|
is_socket(int fd)
|
|
{
|
|
struct stat sbuf;
|
|
|
|
if (fstat(fd, &sbuf) < 0)
|
|
rb_sys_fail("fstat(2)");
|
|
return S_ISSOCK(sbuf.st_mode);
|
|
}
|
|
#endif
|
|
|
|
#if defined __APPLE__
|
|
# define do_write_retry(code) do {ret = code;} while (ret == -1 && errno == EPROTOTYPE)
|
|
#else
|
|
# define do_write_retry(code) ret = code
|
|
#endif
|
|
|
|
VALUE
|
|
rsock_init_sock(VALUE sock, int fd)
|
|
{
|
|
rb_io_t *fp;
|
|
|
|
if (!is_socket(fd) || rb_reserved_fd_p(fd)) {
|
|
rb_syserr_fail(EBADF, "not a socket file descriptor");
|
|
}
|
|
|
|
rb_update_max_fd(fd);
|
|
MakeOpenFile(sock, fp);
|
|
fp->fd = fd;
|
|
fp->mode = FMODE_READWRITE|FMODE_DUPLEX;
|
|
rb_io_ascii8bit_binmode(sock);
|
|
if (rsock_do_not_reverse_lookup) {
|
|
fp->mode |= FMODE_NOREVLOOKUP;
|
|
}
|
|
rb_io_synchronized(fp);
|
|
|
|
return sock;
|
|
}
|
|
|
|
VALUE
|
|
rsock_sendto_blocking(void *data)
|
|
{
|
|
struct rsock_send_arg *arg = data;
|
|
VALUE mesg = arg->mesg;
|
|
ssize_t ret;
|
|
do_write_retry(sendto(arg->fd, RSTRING_PTR(mesg), RSTRING_LEN(mesg),
|
|
arg->flags, arg->to, arg->tolen));
|
|
return (VALUE)ret;
|
|
}
|
|
|
|
VALUE
|
|
rsock_send_blocking(void *data)
|
|
{
|
|
struct rsock_send_arg *arg = data;
|
|
VALUE mesg = arg->mesg;
|
|
ssize_t ret;
|
|
do_write_retry(send(arg->fd, RSTRING_PTR(mesg), RSTRING_LEN(mesg),
|
|
arg->flags));
|
|
return (VALUE)ret;
|
|
}
|
|
|
|
struct recvfrom_arg {
|
|
int fd, flags;
|
|
VALUE str;
|
|
socklen_t alen;
|
|
union_sockaddr buf;
|
|
};
|
|
|
|
static VALUE
|
|
recvfrom_blocking(void *data)
|
|
{
|
|
struct recvfrom_arg *arg = data;
|
|
socklen_t len0 = arg->alen;
|
|
ssize_t ret;
|
|
ret = recvfrom(arg->fd, RSTRING_PTR(arg->str), RSTRING_LEN(arg->str),
|
|
arg->flags, &arg->buf.addr, &arg->alen);
|
|
if (ret != -1 && len0 < arg->alen)
|
|
arg->alen = len0;
|
|
return (VALUE)ret;
|
|
}
|
|
|
|
static VALUE
|
|
rsock_strbuf(VALUE str, long buflen)
|
|
{
|
|
long len;
|
|
|
|
if (NIL_P(str)) return rb_str_new(0, buflen);
|
|
|
|
StringValue(str);
|
|
len = RSTRING_LEN(str);
|
|
if (len >= buflen) {
|
|
rb_str_modify(str);
|
|
} else {
|
|
rb_str_modify_expand(str, buflen - len);
|
|
}
|
|
rb_str_set_len(str, buflen);
|
|
return str;
|
|
}
|
|
|
|
static VALUE
|
|
recvfrom_locktmp(VALUE v)
|
|
{
|
|
struct recvfrom_arg *arg = (struct recvfrom_arg *)v;
|
|
|
|
return rb_thread_io_blocking_region(recvfrom_blocking, arg, arg->fd);
|
|
}
|
|
|
|
VALUE
|
|
rsock_s_recvfrom(VALUE sock, int argc, VALUE *argv, enum sock_recv_type from)
|
|
{
|
|
rb_io_t *fptr;
|
|
VALUE str;
|
|
struct recvfrom_arg arg;
|
|
VALUE len, flg;
|
|
long buflen;
|
|
long slen;
|
|
|
|
rb_scan_args(argc, argv, "12", &len, &flg, &str);
|
|
|
|
if (flg == Qnil) arg.flags = 0;
|
|
else arg.flags = NUM2INT(flg);
|
|
buflen = NUM2INT(len);
|
|
str = rsock_strbuf(str, buflen);
|
|
|
|
GetOpenFile(sock, fptr);
|
|
if (rb_io_read_pending(fptr)) {
|
|
rb_raise(rb_eIOError, "recv for buffered IO");
|
|
}
|
|
arg.fd = fptr->fd;
|
|
arg.alen = (socklen_t)sizeof(arg.buf);
|
|
arg.str = str;
|
|
|
|
while (rb_io_check_closed(fptr),
|
|
rsock_maybe_wait_fd(arg.fd),
|
|
(slen = (long)rb_str_locktmp_ensure(str, recvfrom_locktmp,
|
|
(VALUE)&arg)) < 0) {
|
|
if (!rb_io_wait_readable(fptr->fd)) {
|
|
rb_sys_fail("recvfrom(2)");
|
|
}
|
|
}
|
|
|
|
if (slen != RSTRING_LEN(str)) {
|
|
rb_str_set_len(str, slen);
|
|
}
|
|
switch (from) {
|
|
case RECV_RECV:
|
|
return str;
|
|
case RECV_IP:
|
|
#if 0
|
|
if (arg.alen != sizeof(struct sockaddr_in)) {
|
|
rb_raise(rb_eTypeError, "sockaddr size differs - should not happen");
|
|
}
|
|
#endif
|
|
if (arg.alen && arg.alen != sizeof(arg.buf)) /* OSX doesn't return a from result for connection-oriented sockets */
|
|
return rb_assoc_new(str, rsock_ipaddr(&arg.buf.addr, arg.alen, fptr->mode & FMODE_NOREVLOOKUP));
|
|
else
|
|
return rb_assoc_new(str, Qnil);
|
|
|
|
#ifdef HAVE_SYS_UN_H
|
|
case RECV_UNIX:
|
|
return rb_assoc_new(str, rsock_unixaddr(&arg.buf.un, arg.alen));
|
|
#endif
|
|
case RECV_SOCKET:
|
|
return rb_assoc_new(str, rsock_io_socket_addrinfo(sock, &arg.buf.addr, arg.alen));
|
|
default:
|
|
rb_bug("rsock_s_recvfrom called with bad value");
|
|
}
|
|
}
|
|
|
|
VALUE
|
|
rsock_s_recvfrom_nonblock(VALUE sock, VALUE len, VALUE flg, VALUE str,
|
|
VALUE ex, enum sock_recv_type from)
|
|
{
|
|
rb_io_t *fptr;
|
|
union_sockaddr buf;
|
|
socklen_t alen = (socklen_t)sizeof buf;
|
|
long buflen;
|
|
long slen;
|
|
int fd, flags;
|
|
VALUE addr = Qnil;
|
|
socklen_t len0;
|
|
|
|
flags = NUM2INT(flg);
|
|
buflen = NUM2INT(len);
|
|
str = rsock_strbuf(str, buflen);
|
|
|
|
#ifdef MSG_DONTWAIT
|
|
/* MSG_DONTWAIT avoids the race condition between fcntl and recvfrom.
|
|
It is not portable, though. */
|
|
flags |= MSG_DONTWAIT;
|
|
#endif
|
|
|
|
GetOpenFile(sock, fptr);
|
|
if (rb_io_read_pending(fptr)) {
|
|
rb_raise(rb_eIOError, "recvfrom for buffered IO");
|
|
}
|
|
fd = fptr->fd;
|
|
|
|
rb_io_check_closed(fptr);
|
|
|
|
if (!MSG_DONTWAIT_RELIABLE)
|
|
rb_io_set_nonblock(fptr);
|
|
|
|
len0 = alen;
|
|
slen = recvfrom(fd, RSTRING_PTR(str), buflen, flags, &buf.addr, &alen);
|
|
if (slen != -1 && len0 < alen)
|
|
alen = len0;
|
|
|
|
if (slen < 0) {
|
|
int e = errno;
|
|
switch (e) {
|
|
case EAGAIN:
|
|
#if defined(EWOULDBLOCK) && EWOULDBLOCK != EAGAIN
|
|
case EWOULDBLOCK:
|
|
#endif
|
|
if (ex == Qfalse)
|
|
return sym_wait_readable;
|
|
rb_readwrite_syserr_fail(RB_IO_WAIT_READABLE, e, "recvfrom(2) would block");
|
|
}
|
|
rb_syserr_fail(e, "recvfrom(2)");
|
|
}
|
|
if (slen != RSTRING_LEN(str)) {
|
|
rb_str_set_len(str, slen);
|
|
}
|
|
switch (from) {
|
|
case RECV_RECV:
|
|
return str;
|
|
|
|
case RECV_IP:
|
|
if (alen && alen != sizeof(buf)) /* connection-oriented socket may not return a from result */
|
|
addr = rsock_ipaddr(&buf.addr, alen, fptr->mode & FMODE_NOREVLOOKUP);
|
|
break;
|
|
|
|
case RECV_SOCKET:
|
|
addr = rsock_io_socket_addrinfo(sock, &buf.addr, alen);
|
|
break;
|
|
|
|
default:
|
|
rb_bug("rsock_s_recvfrom_nonblock called with bad value");
|
|
}
|
|
return rb_assoc_new(str, addr);
|
|
}
|
|
|
|
#if MSG_DONTWAIT_RELIABLE
|
|
static VALUE sym_wait_writable;
|
|
|
|
/* copied from io.c :< */
|
|
static long
|
|
read_buffered_data(char *ptr, long len, rb_io_t *fptr)
|
|
{
|
|
int n = fptr->rbuf.len;
|
|
|
|
if (n <= 0) return 0;
|
|
if (n > len) n = (int)len;
|
|
MEMMOVE(ptr, fptr->rbuf.ptr+fptr->rbuf.off, char, n);
|
|
fptr->rbuf.off += n;
|
|
fptr->rbuf.len -= n;
|
|
return n;
|
|
}
|
|
|
|
/* :nodoc: */
|
|
VALUE
|
|
rsock_read_nonblock(VALUE sock, VALUE length, VALUE buf, VALUE ex)
|
|
{
|
|
rb_io_t *fptr;
|
|
long n;
|
|
long len = NUM2LONG(length);
|
|
VALUE str = rsock_strbuf(buf, len);
|
|
char *ptr;
|
|
|
|
GetOpenFile(sock, fptr);
|
|
|
|
if (len == 0) {
|
|
return str;
|
|
}
|
|
|
|
ptr = RSTRING_PTR(str);
|
|
n = read_buffered_data(ptr, len, fptr);
|
|
if (n <= 0) {
|
|
n = (long)recv(fptr->fd, ptr, len, MSG_DONTWAIT);
|
|
if (n < 0) {
|
|
int e = errno;
|
|
if ((e == EWOULDBLOCK || e == EAGAIN)) {
|
|
if (ex == Qfalse) return sym_wait_readable;
|
|
rb_readwrite_syserr_fail(RB_IO_WAIT_READABLE,
|
|
e, "read would block");
|
|
}
|
|
rb_syserr_fail_path(e, fptr->pathv);
|
|
}
|
|
}
|
|
if (len != n) {
|
|
rb_str_modify(str);
|
|
rb_str_set_len(str, n);
|
|
if (str != buf) {
|
|
rb_str_resize(str, n);
|
|
}
|
|
}
|
|
if (n == 0) {
|
|
if (ex == Qfalse) return Qnil;
|
|
rb_eof_error();
|
|
}
|
|
|
|
return str;
|
|
}
|
|
|
|
/* :nodoc: */
|
|
VALUE
|
|
rsock_write_nonblock(VALUE sock, VALUE str, VALUE ex)
|
|
{
|
|
rb_io_t *fptr;
|
|
long n;
|
|
|
|
if (!RB_TYPE_P(str, T_STRING))
|
|
str = rb_obj_as_string(str);
|
|
|
|
sock = rb_io_get_write_io(sock);
|
|
GetOpenFile(sock, fptr);
|
|
rb_io_check_writable(fptr);
|
|
|
|
/*
|
|
* As with IO#write_nonblock, we may block if somebody is relying on
|
|
* buffered I/O; but nobody actually hits this because pipes and sockets
|
|
* are not userspace-buffered in Ruby by default.
|
|
*/
|
|
if (fptr->wbuf.len > 0) {
|
|
rb_io_flush(sock);
|
|
}
|
|
|
|
#ifdef __APPLE__
|
|
again:
|
|
#endif
|
|
n = (long)send(fptr->fd, RSTRING_PTR(str), RSTRING_LEN(str), MSG_DONTWAIT);
|
|
if (n < 0) {
|
|
int e = errno;
|
|
|
|
#ifdef __APPLE__
|
|
if (e == EPROTOTYPE) {
|
|
goto again;
|
|
}
|
|
#endif
|
|
if (e == EWOULDBLOCK || e == EAGAIN) {
|
|
if (ex == Qfalse) return sym_wait_writable;
|
|
rb_readwrite_syserr_fail(RB_IO_WAIT_WRITABLE, e,
|
|
"write would block");
|
|
}
|
|
rb_syserr_fail_path(e, fptr->pathv);
|
|
}
|
|
|
|
return LONG2FIX(n);
|
|
}
|
|
#endif /* MSG_DONTWAIT_RELIABLE */
|
|
|
|
/* returns true if SOCK_CLOEXEC is supported */
|
|
int rsock_detect_cloexec(int fd)
|
|
{
|
|
#ifdef SOCK_CLOEXEC
|
|
int flags = fcntl(fd, F_GETFD);
|
|
|
|
if (flags == -1)
|
|
rb_bug("rsock_detect_cloexec: fcntl(%d, F_GETFD) failed: %s", fd, strerror(errno));
|
|
|
|
if (flags & FD_CLOEXEC)
|
|
return 1;
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
#ifdef SOCK_CLOEXEC
|
|
static int
|
|
rsock_socket0(int domain, int type, int proto)
|
|
{
|
|
int ret;
|
|
static int cloexec_state = -1; /* <0: unknown, 0: ignored, >0: working */
|
|
|
|
if (cloexec_state > 0) { /* common path, if SOCK_CLOEXEC is defined */
|
|
ret = socket(domain, type|SOCK_CLOEXEC|RSOCK_NONBLOCK_DEFAULT, proto);
|
|
if (ret >= 0) {
|
|
if (ret <= 2)
|
|
goto fix_cloexec;
|
|
goto update_max_fd;
|
|
}
|
|
}
|
|
else if (cloexec_state < 0) { /* usually runs once only for detection */
|
|
ret = socket(domain, type|SOCK_CLOEXEC|RSOCK_NONBLOCK_DEFAULT, proto);
|
|
if (ret >= 0) {
|
|
cloexec_state = rsock_detect_cloexec(ret);
|
|
if (cloexec_state == 0 || ret <= 2)
|
|
goto fix_cloexec;
|
|
goto update_max_fd;
|
|
}
|
|
else if (ret == -1 && errno == EINVAL) {
|
|
/* SOCK_CLOEXEC is available since Linux 2.6.27. Linux 2.6.18 fails with EINVAL */
|
|
ret = socket(domain, type, proto);
|
|
if (ret != -1) {
|
|
cloexec_state = 0;
|
|
/* fall through to fix_cloexec */
|
|
}
|
|
}
|
|
}
|
|
else { /* cloexec_state == 0 */
|
|
ret = socket(domain, type, proto);
|
|
}
|
|
if (ret == -1)
|
|
return -1;
|
|
fix_cloexec:
|
|
rb_maygvl_fd_fix_cloexec(ret);
|
|
if (RSOCK_NONBLOCK_DEFAULT) {
|
|
rsock_make_fd_nonblock(ret);
|
|
}
|
|
update_max_fd:
|
|
rb_update_max_fd(ret);
|
|
|
|
return ret;
|
|
}
|
|
#else /* !SOCK_CLOEXEC */
|
|
static int
|
|
rsock_socket0(int domain, int type, int proto)
|
|
{
|
|
int ret = socket(domain, type, proto);
|
|
|
|
if (ret == -1)
|
|
return -1;
|
|
rb_fd_fix_cloexec(ret);
|
|
if (RSOCK_NONBLOCK_DEFAULT) {
|
|
rsock_make_fd_nonblock(ret);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
#endif /* !SOCK_CLOEXEC */
|
|
|
|
int
|
|
rsock_socket(int domain, int type, int proto)
|
|
{
|
|
int fd;
|
|
|
|
fd = rsock_socket0(domain, type, proto);
|
|
if (fd < 0) {
|
|
if (rb_gc_for_fd(errno)) {
|
|
fd = rsock_socket0(domain, type, proto);
|
|
}
|
|
}
|
|
if (0 <= fd)
|
|
rb_update_max_fd(fd);
|
|
return fd;
|
|
}
|
|
|
|
/* emulate blocking connect behavior on EINTR or non-blocking socket */
|
|
static int
|
|
wait_connectable(int fd)
|
|
{
|
|
int sockerr, revents;
|
|
socklen_t sockerrlen;
|
|
|
|
sockerrlen = (socklen_t)sizeof(sockerr);
|
|
if (getsockopt(fd, SOL_SOCKET, SO_ERROR, (void *)&sockerr, &sockerrlen) < 0)
|
|
return -1;
|
|
|
|
/* necessary for non-blocking sockets (at least ECONNREFUSED) */
|
|
switch (sockerr) {
|
|
case 0:
|
|
break;
|
|
#ifdef EALREADY
|
|
case EALREADY:
|
|
#endif
|
|
#ifdef EISCONN
|
|
case EISCONN:
|
|
#endif
|
|
#ifdef ECONNREFUSED
|
|
case ECONNREFUSED:
|
|
#endif
|
|
#ifdef EHOSTUNREACH
|
|
case EHOSTUNREACH:
|
|
#endif
|
|
errno = sockerr;
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
* Stevens book says, successful finish turn on RB_WAITFD_OUT and
|
|
* failure finish turn on both RB_WAITFD_IN and RB_WAITFD_OUT.
|
|
* So it's enough to wait only RB_WAITFD_OUT and check the pending error
|
|
* by getsockopt().
|
|
*
|
|
* Note: rb_wait_for_single_fd already retries on EINTR/ERESTART
|
|
*/
|
|
revents = rb_wait_for_single_fd(fd, RB_WAITFD_IN|RB_WAITFD_OUT, NULL);
|
|
|
|
if (revents < 0)
|
|
return -1;
|
|
|
|
sockerrlen = (socklen_t)sizeof(sockerr);
|
|
if (getsockopt(fd, SOL_SOCKET, SO_ERROR, (void *)&sockerr, &sockerrlen) < 0)
|
|
return -1;
|
|
|
|
switch (sockerr) {
|
|
case 0:
|
|
/*
|
|
* be defensive in case some platforms set SO_ERROR on the original,
|
|
* interrupted connect()
|
|
*/
|
|
case EINTR:
|
|
#ifdef ERESTART
|
|
case ERESTART:
|
|
#endif
|
|
case EAGAIN:
|
|
#ifdef EINPROGRESS
|
|
case EINPROGRESS:
|
|
#endif
|
|
#ifdef EALREADY
|
|
case EALREADY:
|
|
#endif
|
|
#ifdef EISCONN
|
|
case EISCONN:
|
|
#endif
|
|
return 0; /* success */
|
|
default:
|
|
/* likely (but not limited to): ECONNREFUSED, ETIMEDOUT, EHOSTUNREACH */
|
|
errno = sockerr;
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
struct connect_arg {
|
|
int fd;
|
|
socklen_t len;
|
|
const struct sockaddr *sockaddr;
|
|
};
|
|
|
|
static VALUE
|
|
connect_blocking(void *data)
|
|
{
|
|
struct connect_arg *arg = data;
|
|
return (VALUE)connect(arg->fd, arg->sockaddr, arg->len);
|
|
}
|
|
|
|
#if defined(SOCKS) && !defined(SOCKS5)
|
|
static VALUE
|
|
socks_connect_blocking(void *data)
|
|
{
|
|
struct connect_arg *arg = data;
|
|
return (VALUE)Rconnect(arg->fd, arg->sockaddr, arg->len);
|
|
}
|
|
#endif
|
|
|
|
int
|
|
rsock_connect(int fd, const struct sockaddr *sockaddr, int len, int socks)
|
|
{
|
|
int status;
|
|
rb_blocking_function_t *func = connect_blocking;
|
|
struct connect_arg arg;
|
|
|
|
arg.fd = fd;
|
|
arg.sockaddr = sockaddr;
|
|
arg.len = len;
|
|
#if defined(SOCKS) && !defined(SOCKS5)
|
|
if (socks) func = socks_connect_blocking;
|
|
#endif
|
|
status = (int)BLOCKING_REGION_FD(func, &arg);
|
|
|
|
if (status < 0) {
|
|
switch (errno) {
|
|
case EINTR:
|
|
#ifdef ERESTART
|
|
case ERESTART:
|
|
#endif
|
|
case EAGAIN:
|
|
#ifdef EINPROGRESS
|
|
case EINPROGRESS:
|
|
#endif
|
|
return wait_connectable(fd);
|
|
}
|
|
}
|
|
return status;
|
|
}
|
|
|
|
void
|
|
rsock_make_fd_nonblock(int fd)
|
|
{
|
|
int flags;
|
|
#ifdef F_GETFL
|
|
flags = fcntl(fd, F_GETFL);
|
|
if (flags == -1) {
|
|
rb_sys_fail("fnctl(2)");
|
|
}
|
|
#else
|
|
flags = 0;
|
|
#endif
|
|
flags |= O_NONBLOCK;
|
|
if (fcntl(fd, F_SETFL, flags) == -1) {
|
|
rb_sys_fail("fnctl(2)");
|
|
}
|
|
}
|
|
|
|
static int
|
|
cloexec_accept(int socket, struct sockaddr *address, socklen_t *address_len,
|
|
int nonblock)
|
|
{
|
|
int ret;
|
|
socklen_t len0 = 0;
|
|
#ifdef HAVE_ACCEPT4
|
|
static int try_accept4 = 1;
|
|
#endif
|
|
if (RSOCK_NONBLOCK_DEFAULT) {
|
|
nonblock = 1;
|
|
}
|
|
if (address_len) len0 = *address_len;
|
|
#ifdef HAVE_ACCEPT4
|
|
if (try_accept4) {
|
|
int flags = 0;
|
|
#ifdef SOCK_CLOEXEC
|
|
flags |= SOCK_CLOEXEC;
|
|
#endif
|
|
#ifdef SOCK_NONBLOCK
|
|
if (nonblock) {
|
|
flags |= SOCK_NONBLOCK;
|
|
}
|
|
#endif
|
|
ret = accept4(socket, address, address_len, flags);
|
|
/* accept4 is available since Linux 2.6.28, glibc 2.10. */
|
|
if (ret != -1) {
|
|
if (ret <= 2)
|
|
rb_maygvl_fd_fix_cloexec(ret);
|
|
#ifndef SOCK_NONBLOCK
|
|
if (nonblock) {
|
|
rsock_make_fd_nonblock(ret);
|
|
}
|
|
#endif
|
|
if (address_len && len0 < *address_len) *address_len = len0;
|
|
return ret;
|
|
}
|
|
if (errno != ENOSYS) {
|
|
return -1;
|
|
}
|
|
try_accept4 = 0;
|
|
}
|
|
#endif
|
|
ret = accept(socket, address, address_len);
|
|
if (ret == -1) return -1;
|
|
if (address_len && len0 < *address_len) *address_len = len0;
|
|
rb_maygvl_fd_fix_cloexec(ret);
|
|
if (nonblock) {
|
|
rsock_make_fd_nonblock(ret);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
VALUE
|
|
rsock_s_accept_nonblock(VALUE klass, VALUE ex, rb_io_t *fptr,
|
|
struct sockaddr *sockaddr, socklen_t *len)
|
|
{
|
|
int fd2;
|
|
|
|
rb_io_set_nonblock(fptr);
|
|
fd2 = cloexec_accept(fptr->fd, (struct sockaddr*)sockaddr, len, 1);
|
|
if (fd2 < 0) {
|
|
int e = errno;
|
|
switch (e) {
|
|
case EAGAIN:
|
|
#if defined(EWOULDBLOCK) && EWOULDBLOCK != EAGAIN
|
|
case EWOULDBLOCK:
|
|
#endif
|
|
case ECONNABORTED:
|
|
#if defined EPROTO
|
|
case EPROTO:
|
|
#endif
|
|
if (ex == Qfalse)
|
|
return sym_wait_readable;
|
|
rb_readwrite_syserr_fail(RB_IO_WAIT_READABLE, e, "accept(2) would block");
|
|
}
|
|
rb_syserr_fail(e, "accept(2)");
|
|
}
|
|
rb_update_max_fd(fd2);
|
|
return rsock_init_sock(rb_obj_alloc(klass), fd2);
|
|
}
|
|
|
|
struct accept_arg {
|
|
int fd;
|
|
struct sockaddr *sockaddr;
|
|
socklen_t *len;
|
|
};
|
|
|
|
static VALUE
|
|
accept_blocking(void *data)
|
|
{
|
|
struct accept_arg *arg = data;
|
|
return (VALUE)cloexec_accept(arg->fd, arg->sockaddr, arg->len, 0);
|
|
}
|
|
|
|
VALUE
|
|
rsock_s_accept(VALUE klass, int fd, struct sockaddr *sockaddr, socklen_t *len)
|
|
{
|
|
int fd2;
|
|
int retry = 0;
|
|
struct accept_arg arg;
|
|
|
|
arg.fd = fd;
|
|
arg.sockaddr = sockaddr;
|
|
arg.len = len;
|
|
retry:
|
|
rsock_maybe_wait_fd(fd);
|
|
fd2 = (int)BLOCKING_REGION_FD(accept_blocking, &arg);
|
|
if (fd2 < 0) {
|
|
int e = errno;
|
|
switch (e) {
|
|
case EMFILE:
|
|
case ENFILE:
|
|
case ENOMEM:
|
|
if (retry) break;
|
|
rb_gc();
|
|
retry = 1;
|
|
goto retry;
|
|
default:
|
|
if (!rb_io_wait_readable(fd)) break;
|
|
retry = 0;
|
|
goto retry;
|
|
}
|
|
rb_syserr_fail(e, "accept(2)");
|
|
}
|
|
rb_update_max_fd(fd2);
|
|
if (!klass) return INT2NUM(fd2);
|
|
return rsock_init_sock(rb_obj_alloc(klass), fd2);
|
|
}
|
|
|
|
int
|
|
rsock_getfamily(rb_io_t *fptr)
|
|
{
|
|
union_sockaddr ss;
|
|
socklen_t sslen = (socklen_t)sizeof(ss);
|
|
int cached = fptr->mode & FMODE_SOCK;
|
|
|
|
if (cached) {
|
|
switch (cached) {
|
|
#ifdef AF_UNIX
|
|
case FMODE_UNIX: return AF_UNIX;
|
|
#endif
|
|
case FMODE_INET: return AF_INET;
|
|
case FMODE_INET6: return AF_INET6;
|
|
}
|
|
}
|
|
|
|
ss.addr.sa_family = AF_UNSPEC;
|
|
if (getsockname(fptr->fd, &ss.addr, &sslen) < 0)
|
|
return AF_UNSPEC;
|
|
|
|
switch (ss.addr.sa_family) {
|
|
#ifdef AF_UNIX
|
|
case AF_UNIX: fptr->mode |= FMODE_UNIX; break;
|
|
#endif
|
|
case AF_INET: fptr->mode |= FMODE_INET; break;
|
|
case AF_INET6: fptr->mode |= FMODE_INET6; break;
|
|
}
|
|
|
|
return ss.addr.sa_family;
|
|
}
|
|
|
|
void
|
|
rsock_init_socket_init(void)
|
|
{
|
|
/*
|
|
* SocketError is the error class for socket.
|
|
*/
|
|
rb_eSocket = rb_define_class("SocketError", rb_eStandardError);
|
|
rsock_init_ipsocket();
|
|
rsock_init_tcpsocket();
|
|
rsock_init_tcpserver();
|
|
rsock_init_sockssocket();
|
|
rsock_init_udpsocket();
|
|
rsock_init_unixsocket();
|
|
rsock_init_unixserver();
|
|
rsock_init_sockopt();
|
|
rsock_init_ancdata();
|
|
rsock_init_addrinfo();
|
|
rsock_init_sockifaddr();
|
|
rsock_init_socket_constants();
|
|
|
|
#undef rb_intern
|
|
sym_wait_readable = ID2SYM(rb_intern("wait_readable"));
|
|
|
|
#if MSG_DONTWAIT_RELIABLE
|
|
sym_wait_writable = ID2SYM(rb_intern("wait_writable"));
|
|
#endif
|
|
}
|