1
0
Fork 0
mirror of https://github.com/ruby/ruby.git synced 2022-11-09 12:17:21 -05:00
ruby--ruby/ext/socket/init.c
shugo 139d06c34f * ext/socket/init.c (wait_connectable): break if the socket is
writable to avoid infinite loops on FreeBSD and other platforms
  which conforms to SUSv3.  This problem cannot be reproduced with
  loopback interfaces, so it's hard to write test code.
  rsock_connect() and wait_connectable() are overly complicated, so
  they should be refactored, but I commit this fix as a workaround
  for the release of Ruby 1.9.3 scheduled on Feb 24.
  [ruby-core:60940] [Bug #9547]

git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@45084 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
2014-02-21 14:59:44 +00:00

677 lines
15 KiB
C

/************************************************
init.c -
created at: Thu Mar 31 12:21:29 JST 1994
Copyright (C) 1993-2007 Yukihiro Matsumoto
************************************************/
#include "rubysocket.h"
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;
void
rsock_raise_socket_error(const char *reason, int error)
{
#ifdef EAI_SYSTEM
if (error == EAI_SYSTEM) rb_sys_fail(reason);
#endif
rb_raise(rb_eSocket, "%s: %s", reason, gai_strerror(error));
}
VALUE
rsock_init_sock(VALUE sock, int fd)
{
rb_io_t *fp;
#ifndef _WIN32
struct stat sbuf;
if (fstat(fd, &sbuf) < 0)
rb_sys_fail("fstat(2)");
rb_update_max_fd(fd);
if (!S_ISSOCK(sbuf.st_mode))
rb_raise(rb_eArgError, "not a socket file descriptor");
#else
rb_update_max_fd(fd);
if (!rb_w32_is_socket(fd))
rb_raise(rb_eArgError, "not a socket file descriptor");
#endif
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;
return (VALUE)sendto(arg->fd, RSTRING_PTR(mesg), RSTRING_LEN(mesg),
arg->flags, arg->to, arg->tolen);
}
VALUE
rsock_send_blocking(void *data)
{
struct rsock_send_arg *arg = data;
VALUE mesg = arg->mesg;
return (VALUE)send(arg->fd, RSTRING_PTR(mesg), RSTRING_LEN(mesg),
arg->flags);
}
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;
}
VALUE
rsock_s_recvfrom(VALUE sock, int argc, VALUE *argv, enum sock_recv_type from)
{
rb_io_t *fptr;
VALUE str, klass;
struct recvfrom_arg arg;
VALUE len, flg;
long buflen;
long slen;
rb_scan_args(argc, argv, "11", &len, &flg);
if (flg == Qnil) arg.flags = 0;
else arg.flags = NUM2INT(flg);
buflen = NUM2INT(len);
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 = rb_tainted_str_new(0, buflen);
klass = RBASIC(str)->klass;
rb_obj_hide(str);
while (rb_io_check_closed(fptr),
rsock_maybe_wait_fd(arg.fd),
(slen = BLOCKING_REGION_FD(recvfrom_blocking, &arg)) < 0) {
if (!rb_io_wait_readable(fptr->fd)) {
rb_sys_fail("recvfrom(2)");
}
if (RBASIC(str)->klass || RSTRING_LEN(str) != buflen) {
rb_raise(rb_eRuntimeError, "buffer string modified");
}
}
rb_obj_reveal(str, klass);
if (slen < RSTRING_LEN(str)) {
rb_str_set_len(str, slen);
}
rb_obj_taint(str);
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, int argc, VALUE *argv, enum sock_recv_type from)
{
rb_io_t *fptr;
VALUE str;
union_sockaddr buf;
socklen_t alen = (socklen_t)sizeof buf;
VALUE len, flg;
long buflen;
long slen;
int fd, flags;
VALUE addr = Qnil;
socklen_t len0;
rb_scan_args(argc, argv, "11", &len, &flg);
if (flg == Qnil) flags = 0;
else flags = NUM2INT(flg);
buflen = NUM2INT(len);
#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;
str = rb_tainted_str_new(0, buflen);
rb_io_check_closed(fptr);
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) {
switch (errno) {
case EAGAIN:
#if defined(EWOULDBLOCK) && EWOULDBLOCK != EAGAIN
case EWOULDBLOCK:
#endif
rb_readwrite_sys_fail(RB_IO_WAIT_READABLE, "recvfrom(2) would block");
}
rb_sys_fail("recvfrom(2)");
}
if (slen < RSTRING_LEN(str)) {
rb_str_set_len(str, slen);
}
rb_obj_taint(str);
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);
}
/* 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, 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, 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);
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);
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 (errno == EMFILE || errno == ENFILE) {
rb_gc();
fd = rsock_socket0(domain, type, proto);
}
}
if (0 <= fd)
rb_update_max_fd(fd);
return fd;
}
static int
wait_connectable(int fd)
{
int sockerr;
socklen_t sockerrlen;
int revents;
int ret;
for (;;) {
/*
* Stevens book says, successful finish turn on RB_WAITFD_OUT and
* failure finish turn on both RB_WAITFD_IN and RB_WAITFD_OUT.
*/
revents = rb_wait_for_single_fd(fd, RB_WAITFD_IN|RB_WAITFD_OUT, NULL);
if (revents & (RB_WAITFD_IN|RB_WAITFD_OUT)) {
sockerrlen = (socklen_t)sizeof(sockerr);
ret = getsockopt(fd, SOL_SOCKET, SO_ERROR, (void *)&sockerr, &sockerrlen);
/*
* Solaris getsockopt(SO_ERROR) return -1 and set errno
* in getsockopt(). Let's return immediately.
*/
if (ret < 0)
break;
if (sockerr == 0) {
if (revents & RB_WAITFD_OUT)
break;
else
continue; /* workaround for winsock */
}
/* BSD and Linux use sockerr. */
errno = sockerr;
ret = -1;
break;
}
if ((revents & (RB_WAITFD_IN|RB_WAITFD_OUT)) == RB_WAITFD_OUT) {
ret = 0;
break;
}
}
return ret;
}
#ifdef __CYGWIN__
#define WAIT_IN_PROGRESS 10
#endif
#ifdef __APPLE__
#define WAIT_IN_PROGRESS 10
#endif
#ifdef __linux__
/* returns correct error */
#define WAIT_IN_PROGRESS 0
#endif
#ifndef WAIT_IN_PROGRESS
/* BSD origin code apparently has a problem */
#define WAIT_IN_PROGRESS 1
#endif
struct connect_arg {
int fd;
const struct sockaddr *sockaddr;
socklen_t len;
};
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;
#if WAIT_IN_PROGRESS > 0
int wait_in_progress = -1;
int sockerr;
socklen_t sockerrlen;
#endif
arg.fd = fd;
arg.sockaddr = sockaddr;
arg.len = len;
#if defined(SOCKS) && !defined(SOCKS5)
if (socks) func = socks_connect_blocking;
#endif
for (;;) {
status = (int)BLOCKING_REGION_FD(func, &arg);
if (status < 0) {
switch (errno) {
case EINTR:
#if defined(ERESTART)
case ERESTART:
#endif
continue;
case EAGAIN:
#ifdef EINPROGRESS
case EINPROGRESS:
#endif
#if WAIT_IN_PROGRESS > 0
sockerrlen = (socklen_t)sizeof(sockerr);
status = getsockopt(fd, SOL_SOCKET, SO_ERROR, (void *)&sockerr, &sockerrlen);
if (status) break;
if (sockerr) {
status = -1;
errno = sockerr;
break;
}
#endif
#ifdef EALREADY
case EALREADY:
#endif
#if WAIT_IN_PROGRESS > 0
wait_in_progress = WAIT_IN_PROGRESS;
#endif
status = wait_connectable(fd);
if (status) {
break;
}
errno = 0;
continue;
#if WAIT_IN_PROGRESS > 0
case EINVAL:
if (wait_in_progress-- > 0) {
/*
* connect() after EINPROGRESS returns EINVAL on
* some platforms, need to check true error
* status.
*/
sockerrlen = (socklen_t)sizeof(sockerr);
status = getsockopt(fd, SOL_SOCKET, SO_ERROR, (void *)&sockerr, &sockerrlen);
if (!status && !sockerr) {
struct timeval tv = {0, 100000};
rb_thread_wait_for(tv);
continue;
}
status = -1;
errno = sockerr;
}
break;
#endif
#ifdef EISCONN
case EISCONN:
status = 0;
errno = 0;
break;
#endif
default:
break;
}
}
return status;
}
}
static void
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 ret;
socklen_t len0 = 0;
#ifdef HAVE_ACCEPT4
static int try_accept4 = 1;
#endif
if (address_len) len0 = *address_len;
#ifdef HAVE_ACCEPT4
if (try_accept4) {
int flags = 0;
#ifdef SOCK_CLOEXEC
flags |= SOCK_CLOEXEC;
#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);
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);
return ret;
}
VALUE
rsock_s_accept_nonblock(VALUE klass, rb_io_t *fptr, struct sockaddr *sockaddr, socklen_t *len)
{
int fd2;
rb_secure(3);
rb_io_set_nonblock(fptr);
fd2 = cloexec_accept(fptr->fd, (struct sockaddr*)sockaddr, len);
if (fd2 < 0) {
switch (errno) {
case EAGAIN:
#if defined(EWOULDBLOCK) && EWOULDBLOCK != EAGAIN
case EWOULDBLOCK:
#endif
case ECONNABORTED:
#if defined EPROTO
case EPROTO:
#endif
rb_readwrite_sys_fail(RB_IO_WAIT_READABLE, "accept(2) would block");
}
rb_sys_fail("accept(2)");
}
rb_update_max_fd(fd2);
make_fd_nonblock(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);
}
VALUE
rsock_s_accept(VALUE klass, int fd, struct sockaddr *sockaddr, socklen_t *len)
{
int fd2;
int retry = 0;
struct accept_arg arg;
rb_secure(3);
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) {
switch (errno) {
case EMFILE:
case ENFILE:
if (retry) break;
rb_gc();
retry = 1;
goto retry;
default:
if (!rb_io_wait_readable(fd)) break;
retry = 0;
goto retry;
}
rb_sys_fail("accept(2)");
}
rb_update_max_fd(fd2);
if (!klass) return INT2NUM(fd2);
return rsock_init_sock(rb_obj_alloc(klass), fd2);
}
int
rsock_getfamily(int sockfd)
{
union_sockaddr ss;
socklen_t sslen = (socklen_t)sizeof(ss);
ss.addr.sa_family = AF_UNSPEC;
if (getsockname(sockfd, &ss.addr, &sslen) < 0)
return AF_UNSPEC;
return ss.addr.sa_family;
}
void
rsock_init_socket_init()
{
/*
* 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();
}