ext/kgio/accept.c in kgio-2.3.3 vs ext/kgio/accept.c in kgio-2.4.0

- old
+ new

@@ -14,12 +14,15 @@ static int accept4_flags = SOCK_CLOEXEC | SOCK_NONBLOCK; #endif /* ! linux */ struct accept_args { int fd; + int flags; struct sockaddr *addr; socklen_t *addrlen; + VALUE accept_io; + VALUE accepted_class; }; /* * Sets the default class for newly accepted sockets. This is * legacy behavior, kgio_accept and kgio_tryaccept now take optional @@ -51,19 +54,23 @@ static VALUE get_accepted(VALUE klass) { return cClientSocket; } +/* + * accept() wrapper that'll fall back on accept() if we were built on + * a system with accept4() but run on a system without accept4() + */ static VALUE xaccept(void *ptr) { struct accept_args *a = ptr; int rv; - rv = accept_fn(a->fd, a->addr, a->addrlen, accept4_flags); + rv = accept_fn(a->fd, a->addr, a->addrlen, a->flags); if (rv == -1 && errno == ENOSYS && accept_fn != my_accept4) { accept_fn = my_accept4; - rv = accept_fn(a->fd, a->addr, a->addrlen, accept4_flags); + rv = accept_fn(a->fd, a->addr, a->addrlen, a->flags); } return (VALUE)rv; } @@ -122,100 +129,118 @@ return rv; } #define set_blocking_or_block(fd) (void)rb_io_wait_readable(fd) #endif /* ! HAVE_RB_THREAD_BLOCKING_REGION */ -static VALUE acceptor(int argc, const VALUE *argv) +static void +prepare_accept(struct accept_args *a, VALUE self, int argc, const VALUE *argv) { - if (argc == 0) - return cClientSocket; /* default, legacy behavior */ - else if (argc == 1) - return argv[0]; + a->fd = my_fileno(self); + a->accept_io = self; + switch (argc) { + case 2: + a->flags = NUM2INT(argv[1]); + a->accepted_class = NIL_P(argv[0]) ? cClientSocket : argv[0]; + return; + case 0: /* default, legacy behavior */ + a->flags = accept4_flags; + a->accepted_class = cClientSocket; + return; + case 1: + a->flags = accept4_flags; + a->accepted_class = NIL_P(argv[0]) ? cClientSocket : argv[0]; + return; + } + rb_raise(rb_eArgError, "wrong number of arguments (%d for 1)", argc); } +static VALUE in_addr_set(VALUE io, struct sockaddr_storage *addr, socklen_t len) +{ + VALUE host; + int host_len, rc; + char *host_ptr; + + switch (addr->ss_family) { + case AF_INET: + host_len = (long)INET_ADDRSTRLEN; + break; + case AF_INET6: + host_len = (long)INET6_ADDRSTRLEN; + break; + default: + rb_raise(rb_eRuntimeError, "unsupported address family"); + } + host = rb_str_new(NULL, host_len); + host_ptr = RSTRING_PTR(host); + rc = getnameinfo((struct sockaddr *)addr, len, + host_ptr, host_len, NULL, 0, NI_NUMERICHOST); + if (rc != 0) + rb_raise(rb_eRuntimeError, "getnameinfo: %s", gai_strerror(rc)); + rb_str_set_len(host, strlen(host_ptr)); + return rb_ivar_set(io, iv_kgio_addr, host); +} + #if defined(__linux__) # define post_accept kgio_autopush_accept #else # define post_accept(a,b) for(;0;) #endif static VALUE -my_accept(VALUE accept_io, VALUE klass, - struct sockaddr *addr, socklen_t *addrlen, int nonblock) +my_accept(struct accept_args *a, int force_nonblock) { - int client; + int client_fd; VALUE client_io; - struct accept_args a; + int retried = 0; - a.fd = my_fileno(accept_io); - a.addr = addr; - a.addrlen = addrlen; retry: - client = thread_accept(&a, nonblock); - if (client == -1) { + client_fd = thread_accept(a, force_nonblock); + if (client_fd == -1) { switch (errno) { case EAGAIN: - if (nonblock) + if (force_nonblock) return Qnil; - set_blocking_or_block(a.fd); + a->fd = my_fileno(a->accept_io); + set_blocking_or_block(a->fd); #ifdef ECONNABORTED case ECONNABORTED: #endif /* ECONNABORTED */ #ifdef EPROTO case EPROTO: #endif /* EPROTO */ case EINTR: + a->fd = my_fileno(a->accept_io); goto retry; case ENOMEM: case EMFILE: case ENFILE: #ifdef ENOBUFS case ENOBUFS: #endif /* ENOBUFS */ - errno = 0; - rb_gc(); - client = thread_accept(&a, nonblock); - } - if (client == -1) { - if (errno == EINTR) + if (!retried) { + retried = 1; + errno = 0; + rb_gc(); goto retry; + } + default: rb_sys_fail("accept"); } } - client_io = sock_for_fd(klass, client); - post_accept(accept_io, client_io); + client_io = sock_for_fd(a->accepted_class, client_fd); + post_accept(a->accept_io, client_io); + + if (a->addr) + in_addr_set(client_io, + (struct sockaddr_storage *)a->addr, *a->addrlen); + else + rb_ivar_set(client_io, iv_kgio_addr, localhost); return client_io; } -static VALUE in_addr_set(VALUE io, struct sockaddr_storage *addr, socklen_t len) -{ - VALUE host; - int host_len, rc; - char *host_ptr; - - switch (addr->ss_family) { - case AF_INET: - host_len = (long)INET_ADDRSTRLEN; - break; - case AF_INET6: - host_len = (long)INET6_ADDRSTRLEN; - break; - default: - rb_raise(rb_eRuntimeError, "unsupported address family"); - } - host = rb_str_new(NULL, host_len); - host_ptr = RSTRING_PTR(host); - rc = getnameinfo((struct sockaddr *)addr, len, - host_ptr, host_len, NULL, 0, NI_NUMERICHOST); - if (rc != 0) - rb_raise(rb_eRuntimeError, "getnameinfo: %s", gai_strerror(rc)); - rb_str_set_len(host, strlen(host_ptr)); - return rb_ivar_set(io, iv_kgio_addr, host); -} - /* * call-seq: * * io.kgio_addr! => refreshes the given sock address */ @@ -237,115 +262,143 @@ /* * call-seq: * * server = Kgio::TCPServer.new('0.0.0.0', 80) * server.kgio_tryaccept -> Kgio::Socket or nil + * server.kgio_tryaccept(klass = MySocket) -> MySocket or nil + * server.kgio_tryaccept(nil, flags) -> Kgio::Socket or nil * * Initiates a non-blocking accept and returns a generic Kgio::Socket * object with the kgio_addr attribute set to the IP address of the * connected client on success. * * Returns nil on EAGAIN, and raises on other errors. * - * An optional class argument may be specified to override the - * Kgio::Socket-class return value: + * An optional +klass+ argument may be specified to override the + * Kgio::Socket-class on a successful return value. * - * server.kgio_tryaccept(MySocket) -> MySocket + * An optional +flags+ argument may also be specifed to override the + * value of +Kgio.accept_cloexec+ and +Kgio.accept_nonblock+. +flags+ + * is a bitmask that may contain any combination of: + * + * - Fcntl::FD_CLOEXEC - close-on-exec flag + * - IO::NONBLOCK - non-blocking flag */ -static VALUE tcp_tryaccept(int argc, VALUE *argv, VALUE io) +static VALUE tcp_tryaccept(int argc, VALUE *argv, VALUE self) { struct sockaddr_storage addr; socklen_t addrlen = sizeof(struct sockaddr_storage); - VALUE klass = acceptor(argc, argv); - VALUE rv = my_accept(io, klass, (struct sockaddr *)&addr, &addrlen, 1); + struct accept_args a; - if (!NIL_P(rv)) - in_addr_set(rv, &addr, addrlen); - return rv; + a.addr = (struct sockaddr *)&addr; + a.addrlen = &addrlen; + prepare_accept(&a, self, argc, argv); + return my_accept(&a, 1); } /* * call-seq: * * server = Kgio::TCPServer.new('0.0.0.0', 80) * server.kgio_accept -> Kgio::Socket or nil + * server.kgio_tryaccept -> Kgio::Socket or nil + * server.kgio_tryaccept(klass = MySocket) -> MySocket or nil * * Initiates a blocking accept and returns a generic Kgio::Socket * object with the kgio_addr attribute set to the IP address of * the client on success. * * On Ruby implementations using native threads, this can use a blocking * accept(2) (or accept4(2)) system call to avoid thundering herds. * - * An optional class argument may be specified to override the - * Kgio::Socket-class return value: + * An optional +klass+ argument may be specified to override the + * Kgio::Socket-class on a successful return value. * - * server.kgio_accept(MySocket) -> MySocket + * An optional +flags+ argument may also be specifed to override the + * value of +Kgio.accept_cloexec+ and +Kgio.accept_nonblock+. +flags+ + * is a bitmask that may contain any combination of: + * + * - Fcntl::FD_CLOEXEC - close-on-exec flag + * - IO::NONBLOCK - non-blocking flag */ -static VALUE tcp_accept(int argc, VALUE *argv, VALUE io) +static VALUE tcp_accept(int argc, VALUE *argv, VALUE self) { struct sockaddr_storage addr; socklen_t addrlen = sizeof(struct sockaddr_storage); - VALUE klass = acceptor(argc, argv); - VALUE rv = my_accept(io, klass, (struct sockaddr *)&addr, &addrlen, 0); + struct accept_args a; - in_addr_set(rv, &addr, addrlen); - return rv; + a.addr = (struct sockaddr *)&addr; + a.addrlen = &addrlen; + prepare_accept(&a, self, argc, argv); + return my_accept(&a, 0); } /* * call-seq: * * server = Kgio::UNIXServer.new("/path/to/unix/socket") * server.kgio_tryaccept -> Kgio::Socket or nil + * server.kgio_tryaccept(klass = MySocket) -> MySocket or nil + * server.kgio_tryaccept(nil, flags) -> Kgio::Socket or nil * * Initiates a non-blocking accept and returns a generic Kgio::Socket * object with the kgio_addr attribute set (to the value of * Kgio::LOCALHOST) on success. * - * Returns nil on EAGAIN, and raises on other errors. + * An optional +klass+ argument may be specified to override the + * Kgio::Socket-class on a successful return value. * - * An optional class argument may be specified to override the - * Kgio::Socket-class return value: + * An optional +flags+ argument may also be specifed to override the + * value of +Kgio.accept_cloexec+ and +Kgio.accept_nonblock+. +flags+ + * is a bitmask that may contain any combination of: * - * server.kgio_tryaccept(MySocket) -> MySocket + * - Fcntl::FD_CLOEXEC - close-on-exec flag + * - IO::NONBLOCK - non-blocking flag */ -static VALUE unix_tryaccept(int argc, VALUE *argv, VALUE io) +static VALUE unix_tryaccept(int argc, VALUE *argv, VALUE self) { - VALUE klass = acceptor(argc, argv); - VALUE rv = my_accept(io, klass, NULL, NULL, 1); + struct accept_args a; - if (!NIL_P(rv)) - rb_ivar_set(rv, iv_kgio_addr, localhost); - return rv; + a.addr = NULL; + a.addrlen = NULL; + prepare_accept(&a, self, argc, argv); + return my_accept(&a, 1); } /* * call-seq: * * server = Kgio::UNIXServer.new("/path/to/unix/socket") * server.kgio_accept -> Kgio::Socket or nil + * server.kgio_accept(klass = MySocket) -> MySocket or nil + * server.kgio_accept(nil, flags) -> Kgio::Socket or nil * * Initiates a blocking accept and returns a generic Kgio::Socket * object with the kgio_addr attribute set (to the value of * Kgio::LOCALHOST) on success. * * On Ruby implementations using native threads, this can use a blocking * accept(2) (or accept4(2)) system call to avoid thundering herds. * - * An optional class argument may be specified to override the - * Kgio::Socket-class return value: + * An optional +klass+ argument may be specified to override the + * Kgio::Socket-class on a successful return value. * - * server.kgio_accept(MySocket) -> MySocket + * An optional +flags+ argument may also be specifed to override the + * value of +Kgio.accept_cloexec+ and +Kgio.accept_nonblock+. +flags+ + * is a bitmask that may contain any combination of: + * + * - Fcntl::FD_CLOEXEC - close-on-exec flag + * - IO::NONBLOCK - non-blocking flag */ -static VALUE unix_accept(int argc, VALUE *argv, VALUE io) +static VALUE unix_accept(int argc, VALUE *argv, VALUE self) { - VALUE klass = acceptor(argc, argv); - VALUE rv = my_accept(io, klass, NULL, NULL, 0); + struct accept_args a; - rb_ivar_set(rv, iv_kgio_addr, localhost); - return rv; + a.addr = NULL; + a.addrlen = NULL; + prepare_accept(&a, self, argc, argv); + return my_accept(&a, 0); } /* * call-seq: * @@ -382,10 +435,10 @@ * Sets whether or not Kgio::Socket objects created by * TCPServer#kgio_accept, * TCPServer#kgio_tryaccept, * UNIXServer#kgio_accept, * and UNIXServer#kgio_tryaccept - * are created with the FD_CLOEXEC file descriptor flag. + * default to being created with the FD_CLOEXEC file descriptor flag. * * This is on by default, as there is little reason to deal to enable * it for client sockets on a socket server. */ static VALUE set_cloexec(VALUE mod, VALUE boolean)