From ef9206bbea3b8cb1af66966e74fe8527edab2418 Mon Sep 17 00:00:00 2001 From: akr Date: Mon, 2 Feb 2009 01:53:02 +0000 Subject: [PATCH] * ext/socket/rubysocket.h (rb_cSockOpt): declared. (sockopt_new): ditto. (Init_sockopt): ditto. * ext/socket/init.c (Init_socket_init): call Init_sockopt. * ext/socket/depend: add dependency for option.o * ext/socket/mkconstants.rb: generate intern_level, intern_so_optname, intern_ip_optname, intern_ipv6_optname, intern_tcp_optname, intern_udp_optname and intern_scm_optname. * ext/socket/extconf.rb: add option.o. * ext/socket/basicsocket.c (bsock_setsockopt): accept Socket::Option object. (bsock_getsockopt): return Socket::Option object. git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@21936 b2dd03c8-39d4-4d8f-98ff-823fe69b080e --- ChangeLog | 22 ++ ext/.document | 1 + ext/socket/basicsocket.c | 16 +- ext/socket/depend | 1 + ext/socket/extconf.rb | 1 + ext/socket/init.c | 1 + ext/socket/mkconstants.rb | 7 + ext/socket/option.c | 373 ++++++++++++++++++++++++++++++++ ext/socket/rubysocket.h | 4 + test/socket/test_basicsocket.rb | 10 +- 10 files changed, 428 insertions(+), 8 deletions(-) create mode 100644 ext/socket/option.c diff --git a/ChangeLog b/ChangeLog index 9849711b37..71ec0b58f8 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,25 @@ +Mon Feb 2 10:48:38 2009 Tanaka Akira + + * ext/socket/basicsocket.c (bsock_setsockopt): accept Socket::Option + object. + (bsock_getsockopt): return Socket::Option object. + + * ext/socket/option.c: new file. + + * ext/socket/rubysocket.h (rb_cSockOpt): declared. + (sockopt_new): ditto. + (Init_sockopt): ditto. + + * ext/socket/init.c (Init_socket_init): call Init_sockopt. + + * ext/socket/depend: add dependency for option.o + + * ext/socket/mkconstants.rb: generate intern_level, intern_so_optname, + intern_ip_optname, intern_ipv6_optname, intern_tcp_optname, + intern_udp_optname and intern_scm_optname. + + * ext/socket/extconf.rb: add option.o. + Mon Feb 2 09:49:39 2009 Nobuyoshi Nakada * vm.c (vm_backtrace_each): progname is not available at diff --git a/ext/.document b/ext/.document index 075ee87495..abf2cc7ac1 100644 --- a/ext/.document +++ b/ext/.document @@ -20,6 +20,7 @@ socket/udpsocket.c socket/unixsocket.c socket/unixserver.c socket/socket.c +socket/option.c socket/constants.c socket/lib/socket.rb stringio/stringio.c diff --git a/ext/socket/basicsocket.c b/ext/socket/basicsocket.c index 4b8e94653f..a5c310a5d1 100644 --- a/ext/socket/basicsocket.c +++ b/ext/socket/basicsocket.c @@ -192,14 +192,24 @@ bsock_close_write(VALUE sock) * */ static VALUE -bsock_setsockopt(VALUE sock, VALUE lev, VALUE optname, VALUE val) +bsock_setsockopt(int argc, VALUE *argv, VALUE sock) { + VALUE lev, optname, val; int level, option; rb_io_t *fptr; int i; char *v; int vlen; + if (argc == 1) { + lev = rb_funcall(argv[0], rb_intern("level"), 0); + optname = rb_funcall(argv[0], rb_intern("optname"), 0); + val = rb_funcall(argv[0], rb_intern("data"), 0); + } + else { + rb_scan_args(argc, argv, "30", &lev, &optname, &val); + } + rb_secure(2); level = level_arg(lev); option = optname_arg(level, optname); @@ -290,7 +300,7 @@ bsock_getsockopt(VALUE sock, VALUE lev, VALUE optname) if (getsockopt(fptr->fd, level, option, buf, &len) < 0) rb_sys_fail_path(fptr->pathv); - return rb_str_new(buf, len); + return sockopt_new(level, option, rb_str_new(buf, len)); #else rb_notimplement(); #endif @@ -626,7 +636,7 @@ Init_basicsocket(void) rb_define_method(rb_cBasicSocket, "close_read", bsock_close_read, 0); rb_define_method(rb_cBasicSocket, "close_write", bsock_close_write, 0); rb_define_method(rb_cBasicSocket, "shutdown", bsock_shutdown, -1); - rb_define_method(rb_cBasicSocket, "setsockopt", bsock_setsockopt, 3); + rb_define_method(rb_cBasicSocket, "setsockopt", bsock_setsockopt, -1); rb_define_method(rb_cBasicSocket, "getsockopt", bsock_getsockopt, 2); rb_define_method(rb_cBasicSocket, "getsockname", bsock_getsockname, 0); rb_define_method(rb_cBasicSocket, "getpeername", bsock_getpeername, 0); diff --git a/ext/socket/depend b/ext/socket/depend index f60a1b71f6..24818aa5e1 100644 --- a/ext/socket/depend +++ b/ext/socket/depend @@ -13,6 +13,7 @@ sockssocket.o: sockssocket.c $(SOCK_HEADERS) udpsocket.o: udpsocket.c $(SOCK_HEADERS) unixsocket.o: unixsocket.c $(SOCK_HEADERS) unixserver.o: unixserver.c $(SOCK_HEADERS) +option.o: option.c $(SOCK_HEADERS) raddrinfo.o: raddrinfo.c $(SOCK_HEADERS) getnameinfo.o: getnameinfo.c $(arch_hdrdir)/ruby/config.h addrinfo.h sockport.h diff --git a/ext/socket/extconf.rb b/ext/socket/extconf.rb index b6a303cc48..cbd431fc05 100644 --- a/ext/socket/extconf.rb +++ b/ext/socket/extconf.rb @@ -254,6 +254,7 @@ $objs = [ "udpsocket.#{$OBJEXT}", "unixsocket.#{$OBJEXT}", "unixserver.#{$OBJEXT}", + "option.#{$OBJEXT}", "raddrinfo.#{$OBJEXT}" ] diff --git a/ext/socket/init.c b/ext/socket/init.c index a21c730030..29ea536919 100644 --- a/ext/socket/init.c +++ b/ext/socket/init.c @@ -519,6 +519,7 @@ Init_socket_init() Init_udpsocket(); Init_unixsocket(); Init_unixserver(); + Init_sockopt(); Init_addrinfo(); Init_socket_constants(); } diff --git a/ext/socket/mkconstants.rb b/ext/socket/mkconstants.rb index c7f599beb8..fd1fe3da56 100644 --- a/ext/socket/mkconstants.rb +++ b/ext/socket/mkconstants.rb @@ -254,6 +254,13 @@ def_intern('intern_family', /\AAF_/) def_intern('intern_protocol_family', /\APF_/) def_intern('intern_socktype', /\ASOCK_/) def_intern('intern_ipproto', /\AIPPROTO_/) +def_intern('intern_level', /\A(SOL_SOCKET\z|IPPROTO_)/, /\A(SOL_|IPPROTO_)/) +def_intern('intern_so_optname', /\ASO_/, "SO_") +def_intern('intern_ip_optname', /\AIP_/, "IP_") +def_intern('intern_ipv6_optname', /\AIPV6_/, "IPV6_") +def_intern('intern_tcp_optname', /\ATCP_/, "TCP_") +def_intern('intern_udp_optname', /\AUDP_/, "UDP_") +def_intern('intern_scm_optname', /\ASCM_/, "SCM_") result = ERB.new(<<'EOS', nil, '%').result(binding) /* autogenerated file */ diff --git a/ext/socket/option.c b/ext/socket/option.c new file mode 100644 index 0000000000..f815528fef --- /dev/null +++ b/ext/socket/option.c @@ -0,0 +1,373 @@ +#include "rubysocket.h" + +VALUE rb_cSockOpt; + +static VALUE +constant_to_sym(int constant, ID (*intern_const)(int)) +{ + ID name = intern_const(constant); + if (name) { + return ID2SYM(name); + } + + return INT2NUM(constant); +} + +static VALUE +optname_to_sym(int level, int optname) +{ + switch (level) { + case SOL_SOCKET: + return constant_to_sym(optname, intern_so_optname); + case IPPROTO_IP: + return constant_to_sym(optname, intern_ip_optname); + case IPPROTO_IPV6: + return constant_to_sym(optname, intern_ipv6_optname); + case IPPROTO_TCP: + return constant_to_sym(optname, intern_tcp_optname); + case IPPROTO_UDP: + return constant_to_sym(optname, intern_udp_optname); + default: + return INT2NUM(optname); + } +} + +static VALUE +sockopt_initialize(VALUE self, VALUE vlevel, VALUE voptname, VALUE data) +{ + int level; + StringValue(data); + level = level_arg(vlevel); + rb_ivar_set(self, rb_intern("level"), INT2NUM(level)); + rb_ivar_set(self, rb_intern("optname"), INT2NUM(optname_arg(level, voptname))); + rb_ivar_set(self, rb_intern("data"), data); + return self; +} + +VALUE +sockopt_new(int level, int optname, VALUE data) +{ + NEWOBJ(obj, struct RObject); + OBJSETUP(obj, rb_cSockOpt, T_OBJECT); + StringValue(data); + sockopt_initialize((VALUE)obj, INT2NUM(level), INT2NUM(optname), data); + return (VALUE)obj; +} + +/* + * call-seq: + * sockopt.level => integer + * + * returns the socket level as an integer. + * + * p Socket::Option.new(:IPV6, :RECVPKTINFO, [1].pack("i!")).level + */ +static VALUE +sockopt_level(VALUE self) +{ + return rb_attr_get(self, rb_intern("level")); +} + +/* + * call-seq: + * sockopt.optname => integer + * + * returns the socket option name as an integer. + * + * p Socket::Option.new(:IPV6, :RECVPKTINFO, [1].pack("i!")).optname + */ +static VALUE +sockopt_optname(VALUE self) +{ + return rb_attr_get(self, rb_intern("optname")); +} + +/* + * call-seq: + * sockopt.data => string + * + * returns the socket option data as a string. + * + * p Socket::Option.new(:IPV6, :PKTINFO, [1].pack("i!")).data + */ +static VALUE +sockopt_data(VALUE self) +{ + return rb_attr_get(self, rb_intern("data")); +} + +/* + * call-seq: + * Socket::Option.int(level, optname, integer) => sockopt + * + * Creates a new Socket::Option object which contains an int as data. + * + * The size and endian is dependent on the host. + * + * p Socket::Option.int(:SOCKET, :KEEPALIVE, 1) + * #=> # + */ +static VALUE +sockopt_s_int(VALUE klass, VALUE vlevel, VALUE voptname, VALUE vint) +{ + int level = level_arg(vlevel); + int optname = optname_arg(level, voptname); + int i = NUM2INT(vint); + return sockopt_new(level, optname, rb_str_new((char*)&i, sizeof(i))); +} + +/* + * call-seq: + * sockopt.int => integer + * + * Returns the data in _sockopt_ as an int. + * + * The size and endian is dependent on the host. + * + * sockopt = Socket::Option.int(:SOCKET, :KEEPALIVE, 1) + * p sockopt.int => 1 + */ +static VALUE +sockopt_int(VALUE self) +{ + int i; + VALUE data = sockopt_data(self); + StringValue(data); + if (RSTRING_LEN(data) != sizeof(int)) + rb_raise(rb_eTypeError, "size differ. expected as sizeof(int)=%d but %ld", + (int)sizeof(int), (long)RSTRING_LEN(data)); + memcpy((char*)&i, RSTRING_PTR(data), sizeof(int)); + return INT2NUM(i); +} + +static int +inspect_int(int level, int optname, VALUE data, VALUE ret) +{ + if (RSTRING_LEN(data) == sizeof(int)) { + int i; + memcpy((char*)&i, RSTRING_PTR(data), sizeof(int)); + rb_str_catf(ret, " %d", i); + return 0; + } + else { + return -1; + } +} + +static int +inspect_uint(int level, int optname, VALUE data, VALUE ret) +{ + if (RSTRING_LEN(data) == sizeof(int)) { + unsigned int i; + memcpy((char*)&i, RSTRING_PTR(data), sizeof(unsigned int)); + rb_str_catf(ret, " %u", i); + return 0; + } + else { + return -1; + } +} + +#if defined(SOL_SOCKET) && defined(SO_LINGER) /* POSIX */ +static int +inspect_linger(int level, int optname, VALUE data, VALUE ret) +{ + if (RSTRING_LEN(data) == sizeof(struct linger)) { + struct linger s; + memcpy((char*)&s, RSTRING_PTR(data), sizeof(s)); + rb_str_catf(ret, " onoff:%d linger:%d", s.l_onoff, s.l_linger); + return 0; + } + else { + return -1; + } +} +#endif + +#if defined(SOL_SOCKET) && defined(SO_TYPE) /* POSIX */ +static int +inspect_socktype(int level, int optname, VALUE data, VALUE ret) +{ + if (RSTRING_LEN(data) == sizeof(int)) { + int i; + ID id; + memcpy((char*)&i, RSTRING_PTR(data), sizeof(int)); + id = intern_socktype(i); + if (id) + rb_str_catf(ret, " %s", rb_id2name(id)); + else + rb_str_catf(ret, " %d", i); + return 0; + } + else { + return -1; + } +} +#endif + +static int +inspect_timeval(int level, int optname, VALUE data, VALUE ret) +{ + if (RSTRING_LEN(data) == sizeof(struct linger)) { + struct timeval s; + memcpy((char*)&s, RSTRING_PTR(data), sizeof(s)); + rb_str_catf(ret, " %ld.%06ldsec", (long)s.tv_sec, (long)s.tv_usec); + return 0; + } + else { + return -1; + } +} + +static VALUE +sockopt_inspect(VALUE self) +{ + int level = NUM2INT(sockopt_level(self)); + int optname = NUM2INT(sockopt_optname(self)); + VALUE data = sockopt_data(self); + VALUE v, ret; + ID level_id; + + StringValue(data); + + ret = rb_sprintf("#<%s: ", rb_obj_classname(self)); + + level_id = intern_level(level); + if (level_id) + rb_str_cat2(ret, rb_id2name(level_id)); + else + rb_str_catf(ret, "level:%d", level); + + v = optname_to_sym(level, optname); + if (SYMBOL_P(v)) + rb_str_catf(ret, " %s", rb_id2name(SYM2ID(v))); + else + rb_str_catf(ret, " optname:%d", optname); + + switch (level) { +# if defined(SOL_SOCKET) + case SOL_SOCKET: + switch (optname) { +# if defined(SO_DEBUG) /* POSIX */ + case SO_DEBUG: if (inspect_int(level, optname, data, ret) == -1) goto dump; break; +# endif +# if defined(SO_ERROR) /* POSIX */ + case SO_ERROR: if (inspect_int(level, optname, data, ret) == -1) goto dump; break; +# endif +# if defined(SO_TYPE) /* POSIX */ + case SO_TYPE: if (inspect_socktype(level, optname, data, ret) == -1) goto dump; break; +# endif +# if defined(SO_ACCEPTCONN) /* POSIX */ + case SO_ACCEPTCONN: if (inspect_int(level, optname, data, ret) == -1) goto dump; break; +# endif +# if defined(SO_BROADCAST) /* POSIX */ + case SO_BROADCAST: if (inspect_int(level, optname, data, ret) == -1) goto dump; break; +# endif +# if defined(SO_REUSEADDR) /* POSIX */ + case SO_REUSEADDR: if (inspect_int(level, optname, data, ret) == -1) goto dump; break; +# endif +# if defined(SO_KEEPALIVE) /* POSIX */ + case SO_KEEPALIVE: if (inspect_int(level, optname, data, ret) == -1) goto dump; break; +# endif +# if defined(SO_OOBINLINE) /* POSIX */ + case SO_OOBINLINE: if (inspect_int(level, optname, data, ret) == -1) goto dump; break; +# endif +# if defined(SO_SNDBUF) /* POSIX */ + case SO_SNDBUF: if (inspect_int(level, optname, data, ret) == -1) goto dump; break; +# endif +# if defined(SO_RCVBUF) /* POSIX */ + case SO_RCVBUF: if (inspect_int(level, optname, data, ret) == -1) goto dump; break; +# endif +# if defined(SO_DONTROUTE) /* POSIX */ + case SO_DONTROUTE: if (inspect_int(level, optname, data, ret) == -1) goto dump; break; +# endif +# if defined(SO_RCVLOWAT) /* POSIX */ + case SO_RCVLOWAT: if (inspect_int(level, optname, data, ret) == -1) goto dump; break; +# endif +# if defined(SO_SNDLOWAT) /* POSIX */ + case SO_SNDLOWAT: if (inspect_int(level, optname, data, ret) == -1) goto dump; break; +# endif + +# if defined(SO_LINGER) /* POSIX */ + case SO_LINGER: if (inspect_linger(level, optname, data, ret) == -1) goto dump; break; +# endif +# if defined(SO_RCVTIMEO) /* POSIX */ + case SO_RCVTIMEO: if (inspect_timeval(level, optname, data, ret) == -1) goto dump; break; +# endif +# if defined(SO_SNDTIMEO) /* POSIX */ + case SO_SNDTIMEO: if (inspect_timeval(level, optname, data, ret) == -1) goto dump; break; +# endif + + default: goto dump; + } + break; +# endif + +# if defined(IPPROTO_IPV6) + case IPPROTO_IPV6: + switch (optname) { + /* IPV6_JOIN_GROUP ipv6_mreq, IPV6_LEAVE_GROUP ipv6_mreq */ +# if defined(IPV6_MULTICAST_HOPS) /* POSIX */ + case IPV6_MULTICAST_HOPS: if (inspect_int(level, optname, data, ret) == -1) goto dump; break; +# endif +# if defined(IPV6_MULTICAST_IF) /* POSIX */ + case IPV6_MULTICAST_IF: if (inspect_uint(level, optname, data, ret) == -1) goto dump; break; +# endif +# if defined(IPV6_MULTICAST_LOOP) /* POSIX */ + case IPV6_MULTICAST_LOOP: if (inspect_uint(level, optname, data, ret) == -1) goto dump; break; +# endif +# if defined(IPV6_UNICAST_HOPS) /* POSIX */ + case IPV6_UNICAST_HOPS: if (inspect_int(level, optname, data, ret) == -1) goto dump; break; +# endif +# if defined(IPV6_V6ONLY) /* POSIX */ + case IPV6_V6ONLY: if (inspect_int(level, optname, data, ret) == -1) goto dump; break; +# endif + default: goto dump; + } + break; +# endif + +# if defined(IPPROTO_TCP) + case IPPROTO_TCP: + switch (optname) { +# if defined(TCP_NODELAY) /* POSIX */ + case TCP_NODELAY: if (inspect_int(level, optname, data, ret) == -1) goto dump; break; +# endif + default: goto dump; + } + break; +# endif + + default: + dump: + data = rb_str_dump(data); + rb_str_catf(ret, " %s", StringValueCStr(data)); + } + + rb_str_cat2(ret, ">"); + + return ret; +} + +static VALUE +sockopt_unpack(VALUE self, VALUE template) +{ + return rb_funcall(sockopt_data(self), rb_intern("unpack"), 1, template); +} + +void +Init_sockopt(void) +{ + rb_cSockOpt = rb_define_class_under(rb_cSocket, "Option", rb_cObject); + rb_define_method(rb_cSockOpt, "initialize", sockopt_initialize, 3); + rb_define_method(rb_cSockOpt, "level", sockopt_level, 0); + rb_define_method(rb_cSockOpt, "optname", sockopt_optname, 0); + rb_define_method(rb_cSockOpt, "data", sockopt_data, 0); + rb_define_method(rb_cSockOpt, "inspect", sockopt_inspect, 0); + + rb_define_singleton_method(rb_cSockOpt, "int", sockopt_s_int, 3); + rb_define_method(rb_cSockOpt, "int", sockopt_int, 0); + + rb_define_method(rb_cSockOpt, "unpack", sockopt_unpack, 1); +} + diff --git a/ext/socket/rubysocket.h b/ext/socket/rubysocket.h index 0ba473a216..7aed973be6 100644 --- a/ext/socket/rubysocket.h +++ b/ext/socket/rubysocket.h @@ -159,6 +159,7 @@ extern VALUE rb_cUNIXServer; #endif extern VALUE rb_cSocket; extern VALUE rb_cAddrInfo; +extern VALUE rb_cSockOpt; extern VALUE rb_eSocket; @@ -242,6 +243,8 @@ VALUE sock_listen(VALUE sock, VALUE log); VALUE s_accept(VALUE klass, int fd, struct sockaddr *sockaddr, socklen_t *len); VALUE s_accept_nonblock(VALUE klass, rb_io_t *fptr, struct sockaddr *sockaddr, socklen_t *len); +VALUE sockopt_new(int level, int optname, VALUE data); + void Init_basicsocket(void); void Init_ipsocket(void); void Init_tcpsocket(void); @@ -252,6 +255,7 @@ void Init_unixsocket(void); void Init_unixserver(void); void Init_socket_constants(void); void Init_addrinfo(void); +void Init_sockopt(void); void Init_socket_init(void); #endif diff --git a/test/socket/test_basicsocket.rb b/test/socket/test_basicsocket.rb index 34b4019bd0..bb3efad1e3 100644 --- a/test/socket/test_basicsocket.rb +++ b/test/socket/test_basicsocket.rb @@ -15,19 +15,19 @@ class TestBasicSocket < Test::Unit::TestCase def test_getsockopt inet_stream do |s| n = s.getsockopt(Socket::SOL_SOCKET, Socket::SO_TYPE) - assert_equal([Socket::SOCK_STREAM].pack("i"), n) + assert_equal([Socket::SOCK_STREAM].pack("i"), n.data) n = s.getsockopt("SOL_SOCKET", "SO_TYPE") - assert_equal([Socket::SOCK_STREAM].pack("i"), n) + assert_equal([Socket::SOCK_STREAM].pack("i"), n.data) n = s.getsockopt(:SOL_SOCKET, :SO_TYPE) - assert_equal([Socket::SOCK_STREAM].pack("i"), n) + assert_equal([Socket::SOCK_STREAM].pack("i"), n.data) n = s.getsockopt(:SOCKET, :TYPE) - assert_equal([Socket::SOCK_STREAM].pack("i"), n) + assert_equal([Socket::SOCK_STREAM].pack("i"), n.data) n = s.getsockopt(Socket::SOL_SOCKET, Socket::SO_ERROR) - assert_equal([0].pack("i"), n) + assert_equal([0].pack("i"), n.data) val = Object.new class << val; self end.send(:define_method, :to_int) {