diff options
Diffstat (limited to 'ext/socket/init.c')
| -rw-r--r-- | ext/socket/init.c | 115 |
1 files changed, 78 insertions, 37 deletions
diff --git a/ext/socket/init.c b/ext/socket/init.c index e9dc6f8483..6091385561 100644 --- a/ext/socket/init.c +++ b/ext/socket/init.c @@ -27,6 +27,7 @@ VALUE rb_cSocket; VALUE rb_cAddrinfo; VALUE rb_eSocket; +VALUE rb_eResolution; #ifdef SOCKS VALUE rb_cSOCKSSocket; @@ -34,9 +35,10 @@ VALUE rb_cSOCKSSocket; int rsock_do_not_reverse_lookup = 1; static VALUE sym_wait_readable; +static ID id_error_code; void -rsock_raise_socket_error(const char *reason, int error) +rsock_raise_resolution_error(const char *reason, int error) { #ifdef EAI_SYSTEM int e; @@ -48,10 +50,14 @@ rsock_raise_socket_error(const char *reason, int error) 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)); + VALUE msg = rb_sprintf("%s: %s", reason, gai_strerror(error)); #endif + + StringValue(msg); + VALUE self = rb_class_new_instance(1, &msg, rb_eResolution); + rb_ivar_set(self, id_error_code, INT2NUM(error)); + rb_exc_raise(self); } #if defined __APPLE__ @@ -101,6 +107,7 @@ rsock_send_blocking(void *data) } struct recvfrom_arg { + rb_io_t *fptr; int fd, flags; VALUE str; size_t length; @@ -145,7 +152,7 @@ recvfrom_locktmp(VALUE v) { struct recvfrom_arg *arg = (struct recvfrom_arg *)v; - return rb_thread_io_blocking_region(recvfrom_blocking, arg, arg->fd); + return rb_io_blocking_region(arg->fptr, recvfrom_blocking, arg); } int @@ -186,6 +193,7 @@ rsock_s_recvfrom(VALUE socket, int argc, VALUE *argv, enum sock_recv_type from) rb_raise(rb_eIOError, "recv for buffered IO"); } + arg.fptr = fptr; arg.fd = fptr->fd; arg.alen = (socklen_t)sizeof(arg.buf); arg.str = str; @@ -198,7 +206,8 @@ rsock_s_recvfrom(VALUE socket, int argc, VALUE *argv, enum sock_recv_type from) rb_io_wait(fptr->self, RB_INT2NUM(RUBY_IO_READABLE), Qnil); #endif - slen = (long)rb_str_locktmp_ensure(str, recvfrom_locktmp, (VALUE)&arg); + rb_str_locktmp(str); + slen = (long)rb_ensure(recvfrom_locktmp, (VALUE)&arg, rb_str_unlocktmp, str); if (slen == 0 && !rsock_is_dgram(fptr)) { return Qnil; @@ -412,7 +421,7 @@ rsock_write_nonblock(VALUE sock, VALUE str, VALUE ex) if (e == EWOULDBLOCK || e == EAGAIN) { if (ex == Qfalse) return sym_wait_writable; rb_readwrite_syserr_fail(RB_IO_WAIT_WRITABLE, e, - "write would block"); + "write would block"); } rb_syserr_fail_path(e, fptr->pathv); } @@ -453,9 +462,9 @@ rsock_socket(int domain, int type, int proto) fd = rsock_socket0(domain, type, proto); if (fd < 0) { - if (rb_gc_for_fd(errno)) { - fd = rsock_socket0(domain, type, proto); - } + if (rb_gc_for_fd(errno)) { + fd = rsock_socket0(domain, type, proto); + } } if (0 <= fd) rb_update_max_fd(fd); @@ -464,10 +473,11 @@ rsock_socket(int domain, int type, int proto) /* emulate blocking connect behavior on EINTR or non-blocking socket */ static int -wait_connectable(int fd, struct timeval *timeout) +wait_connectable(VALUE self, VALUE timeout, const struct sockaddr *sockaddr, int len) { - int sockerr, revents; + int sockerr; socklen_t sockerrlen; + int fd = rb_io_descriptor(self); sockerrlen = (socklen_t)sizeof(sockerr); if (getsockopt(fd, SOL_SOCKET, SO_ERROR, (void *)&sockerr, &sockerrlen) < 0) @@ -501,7 +511,15 @@ wait_connectable(int fd, struct timeval *timeout) * * Note: rb_wait_for_single_fd already retries on EINTR/ERESTART */ - revents = rb_wait_for_single_fd(fd, RB_WAITFD_IN|RB_WAITFD_OUT, timeout); + VALUE result = rb_io_wait(self, RB_INT2NUM(RUBY_IO_READABLE|RUBY_IO_WRITABLE), timeout); + + if (result == Qfalse) { + VALUE rai = rsock_addrinfo_new((struct sockaddr *)sockaddr, len, PF_UNSPEC, 0, 0, Qnil, Qnil); + VALUE addr_str = rsock_addrinfo_inspect_sockaddr(rai); + rb_raise(rb_eIOTimeoutError, "user specified timeout for %" PRIsVALUE, addr_str); + } + + int revents = RB_NUM2INT(result); if (revents < 0) return -1; @@ -512,16 +530,10 @@ wait_connectable(int fd, struct timeval *timeout) switch (sockerr) { case 0: - /* - * be defensive in case some platforms set SO_ERROR on the original, - * interrupted connect() - */ - - /* when the connection timed out, no errno is set and revents is 0. */ - if (timeout && revents == 0) { - errno = ETIMEDOUT; - return -1; - } + /* + * be defensive in case some platforms set SO_ERROR on the original, + * interrupted connect() + */ case EINTR: #ifdef ERESTART case ERESTART: @@ -569,19 +581,19 @@ socks_connect_blocking(void *data) #endif int -rsock_connect(int fd, const struct sockaddr *sockaddr, int len, int socks, struct timeval *timeout) +rsock_connect(VALUE self, const struct sockaddr *sockaddr, int len, int socks, VALUE timeout) { - int status; + int descriptor = rb_io_descriptor(self); rb_blocking_function_t *func = connect_blocking; - struct connect_arg arg; + struct connect_arg arg = {.fd = descriptor, .sockaddr = sockaddr, .len = len}; + + rb_io_t *fptr; + RB_IO_POINTER(self, fptr); - 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); + int status = (int)rb_io_blocking_region(fptr, func, &arg); if (status < 0) { switch (errno) { @@ -593,7 +605,7 @@ rsock_connect(int fd, const struct sockaddr *sockaddr, int len, int socks, struc #ifdef EINPROGRESS case EINPROGRESS: #endif - return wait_connectable(fd, timeout); + return wait_connectable(self, timeout, sockaddr, len); } } return status; @@ -701,9 +713,9 @@ rsock_s_accept(VALUE klass, VALUE io, struct sockaddr *sockaddr, socklen_t *len) RB_IO_POINTER(io, fptr); struct accept_arg accept_arg = { - .fd = fptr->fd, - .sockaddr = sockaddr, - .len = len + .fd = fptr->fd, + .sockaddr = sockaddr, + .len = len }; int retry = 0, peer; @@ -712,7 +724,7 @@ rsock_s_accept(VALUE klass, VALUE io, struct sockaddr *sockaddr, socklen_t *len) #ifdef RSOCK_WAIT_BEFORE_BLOCKING rb_io_wait(fptr->self, RB_INT2NUM(RUBY_IO_READABLE), Qnil); #endif - peer = (int)BLOCKING_REGION_FD(accept_blocking, &accept_arg); + peer = (int)rb_io_blocking_region(fptr, accept_blocking, &accept_arg); if (peer < 0) { int error = errno; @@ -750,10 +762,10 @@ rsock_getfamily(rb_io_t *fptr) if (cached) { switch (cached) { #ifdef AF_UNIX - case FMODE_UNIX: return AF_UNIX; + case FMODE_UNIX: return AF_UNIX; #endif - case FMODE_INET: return AF_INET; - case FMODE_INET6: return AF_INET6; + case FMODE_INET: return AF_INET; + case FMODE_INET6: return AF_INET6; } } @@ -772,6 +784,28 @@ rsock_getfamily(rb_io_t *fptr) return ss.addr.sa_family; } +/* + * call-seq: + * error_code -> integer + * + * Returns the raw error code indicating the cause of the hostname resolution failure. + * + * begin + * Addrinfo.getaddrinfo("ruby-lang.org", nil) + * rescue Socket::ResolutionError => e + * if e.error_code == Socket::EAI_AGAIN + * puts "Temporary failure in name resolution." + * end + * end + * + * Note that error codes depend on the operating system. + */ +static VALUE +sock_resolv_error_code(VALUE self) +{ + return rb_attr_get(self, id_error_code); +} + void rsock_init_socket_init(void) { @@ -779,6 +813,11 @@ rsock_init_socket_init(void) * SocketError is the error class for socket. */ rb_eSocket = rb_define_class("SocketError", rb_eStandardError); + /* + * Socket::ResolutionError is the error class for hostname resolution. + */ + rb_eResolution = rb_define_class_under(rb_cSocket, "ResolutionError", rb_eSocket); + rb_define_method(rb_eResolution, "error_code", sock_resolv_error_code, 0); rsock_init_ipsocket(); rsock_init_tcpsocket(); rsock_init_tcpserver(); @@ -792,6 +831,8 @@ rsock_init_socket_init(void) rsock_init_sockifaddr(); rsock_init_socket_constants(); + id_error_code = rb_intern_const("error_code"); + #undef rb_intern sym_wait_readable = ID2SYM(rb_intern("wait_readable")); |
