diff options
Diffstat (limited to 'ext/socket/init.c')
| -rw-r--r-- | ext/socket/init.c | 806 |
1 files changed, 806 insertions, 0 deletions
diff --git a/ext/socket/init.c b/ext/socket/init.c new file mode 100644 index 0000000000..db51cb230e --- /dev/null +++ b/ext/socket/init.c @@ -0,0 +1,806 @@ +/************************************************ + + 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; +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 + rb_raise(rb_eSocket, "%s: %s", reason, gai_strerror(error)); +} + +#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; + size_t length; + 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), arg->length, + 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_tainted_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); + } + 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; + arg.length = buflen; + + 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)"); + } + } + + /* Resize the string to the amount of data received */ + 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, 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); + } + 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); +} + +#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; + + OBJ_TAINT(str); + GetOpenFile(sock, fptr); + + if (len == 0) { + rb_str_set_len(str, 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 (n != RSTRING_LEN(str)) { + rb_str_modify(str); + rb_str_set_len(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, 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 (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; + + /* only to clear pending error */ + sockerrlen = (socklen_t)sizeof(sockerr); + if (getsockopt(fd, SOL_SOCKET, SO_ERROR, (void *)&sockerr, &sockerrlen) < 0) + 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; +} + +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 nonblock) +{ + 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 +#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) { + 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) { + 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 +} |
