summaryrefslogtreecommitdiff
path: root/ext/socket/init.c
diff options
context:
space:
mode:
Diffstat (limited to 'ext/socket/init.c')
-rw-r--r--ext/socket/init.c136
1 files changed, 99 insertions, 37 deletions
diff --git a/ext/socket/init.c b/ext/socket/init.c
index 557d4374a5..b761d601c3 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;
@@ -116,6 +123,7 @@ recvfrom_blocking(void *data)
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;
@@ -144,7 +152,19 @@ 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
+rsock_is_dgram(rb_io_t *fptr)
+{
+ int socktype;
+ socklen_t optlen = (socklen_t)sizeof(socktype);
+ int ret = getsockopt(fptr->fd, SOL_SOCKET, SO_TYPE, (void*)&socktype, &optlen);
+ if (ret == -1) {
+ rb_sys_fail("getsockopt(SO_TYPE)");
+ }
+ return socktype == SOCK_DGRAM;
}
VALUE
@@ -173,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;
@@ -185,8 +206,12 @@ 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;
+ }
if (slen >= 0) break;
if (!rb_io_maybe_wait_readable(errno, socket, RUBY_IO_TIMEOUT_DEFAULT))
@@ -259,6 +284,10 @@ rsock_s_recvfrom_nonblock(VALUE sock, VALUE len, VALUE flg, VALUE str,
if (slen != -1 && len0 < alen)
alen = len0;
+ if (slen == 0 && !rsock_is_dgram(fptr)) {
+ return Qnil;
+ }
+
if (slen < 0) {
int e = errno;
switch (e) {
@@ -392,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);
}
@@ -433,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);
@@ -444,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)
@@ -481,7 +511,16 @@ 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);
+ VALUE message = rb_sprintf("user specified timeout for %" PRIsVALUE, addr_str);
+ rb_raise(rb_eIOTimeoutError, "%" PRIsVALUE, message);
+ }
+
+ int revents = RB_NUM2INT(result);
if (revents < 0)
return -1;
@@ -492,16 +531,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:
@@ -549,19 +582,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) {
@@ -573,7 +606,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;
@@ -681,9 +714,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;
@@ -692,7 +725,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;
@@ -730,10 +763,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;
}
}
@@ -752,6 +785,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)
{
@@ -759,6 +814,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();
@@ -772,6 +832,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"));