[feature:trunk] option for Socket#sendmsg

$B$J$+$@$G$9!#(B

Socket#recvmsg$B$O(B scm_rights: true
$B$r;XDj$9$k$@$1$G%a%$%s$N%G!<%?$@$1$G(B
$B$J$/4JC1$K(BIO$B$r<u$1<h$k$3$H$,$G$-$^$9$,!"0lJ}$G(BSocket#sendmsg$BB&$K$OBP1~(B
$B$9$k;XDj$,$G$-$^$;$s!#0J2<$N$h$&$J%*%W%7%g%s$rDI2C$9$k$N$O$I$&$G$7$g$&(B
$B$+!#(B

s.sendmsg(“foo”, scm_rights: STDIN)
s.sendmsg(“foo”, scm_rights: [STDIN, STDOUT])

diff --git i/ext/socket/ancdata.c w/ext/socket/ancdata.c
index abaf19d…c329e0a 100644
— i/ext/socket/ancdata.c
+++ w/ext/socket/ancdata.c
@@ -2,6 +2,8 @@

#include <time.h>

+static ID sym_scm_rights;
+
#if defined(HAVE_ST_MSG_CONTROL)
static VALUE rb_cAncillaryData;

@@ -1126,17 +1128,63 @@ rb_sendmsg(int fd, const struct msghdr *msg, int
flags)
return rb_thread_blocking_region(nogvl_sendmsg_func, &args,
RUBY_UBF_IO, 0);
}

+#if defined(HAVE_ST_MSG_CONTROL)
+static size_t
+io_to_fd(VALUE io)
+{

  • VALUE fnum = rb_check_to_integer(io, “to_int”);
  • if (NIL_P(fnum))
  • fnum = rb_convert_type(io, T_FIXNUM, “Fixnum”, “fileno”);
  • return NUM2UINT(fnum);
    +}

+static char *
+prepare_msghdr(VALUE controls_str, int level, int type, long clen)
+{

  • struct cmsghdr cmh;
  • char *cmsg;
  • size_t cspace;
  • long oldlen = RSTRING_LEN(controls_str);
  • cspace = CMSG_SPACE(clen);
  • rb_str_resize(controls_str, oldlen + cspace);
  • cmsg = RSTRING_PTR(controls_str)+oldlen;
  • memset((char *)cmsg, 0, cspace);
  • memset((char *)&cmh, 0, sizeof(cmh));
  • cmh.cmsg_level = level;
  • cmh.cmsg_type = type;
  • cmh.cmsg_len = (socklen_t)CMSG_LEN(clen);
  • MEMCPY(cmsg, &cmh, char, sizeof(cmh));
  • return cmsg+((char*)CMSG_DATA(&cmh)-(char*)&cmh);
    +}

+# if defined(NetBSD)
+# define TRIM_PADDING 1
+# endif
+# if TRIM_PADDING
+# define prepare_msghdr(controls_str, level, type, clen) \

  • (last_pad = CMSG_SPACE(clen) - CMSG_LEN(clen), \
  • prepare_msghdr((controls_str), \
    
  •    last_level = (level), last_type = (type), \
    
  •    (clen)))
    

+# endif
+#endif
+
static VALUE
bsock_sendmsg_internal(int argc, VALUE *argv, VALUE sock, int nonblock)
{
rb_io_t *fptr;

  • VALUE data, vflags, dest_sockaddr;
  • VALUE data, vflags, dest_sockaddr, vopts = Qnil;
    VALUE *controls_ptr;
    int controls_num;
    struct msghdr mh;
    struct iovec iov;
    #if defined(HAVE_ST_MSG_CONTROL)
    volatile VALUE controls_str = 0;
    +# if TRIM_PADDING

  • size_t last_pad = 0;

  • int last_level = 0;

  • int last_type = 0;
    +# endif
    #endif
    int flags;
    ssize_t ss;
    @@ -1152,6 +1200,8 @@ bsock_sendmsg_internal(int argc, VALUE *argv,
    VALUE sock, int nonblock)

    if (argc == 0)
    rb_raise(rb_eArgError, “mesg argument required”);

  • if (1 < argc && RB_TYPE_P(argv[argc-1], T_HASH))

  •    vopts = argv[--argc];
    

    data = argv[0];
    if (1 < argc) vflags = argv[1];
    if (2 < argc) dest_sockaddr = argv[2];
    @@ -1162,19 +1212,13 @@ bsock_sendmsg_internal(int argc, VALUE *argv,
    VALUE sock, int nonblock)
    if (controls_num) {
    #if defined(HAVE_ST_MSG_CONTROL)
    int i;

  • size_t last_pad = 0;
  •    int last_level = 0;
    
  •    int last_type = 0;
       controls_str = rb_str_tmp_new(0);
       for (i = 0; i < controls_num; i++) {
           VALUE elt = controls_ptr[i], v;
           VALUE vlevel, vtype;
           int level, type;
           VALUE cdata;
    
  •        long oldlen;
    
  •        struct cmsghdr cmh;
           char *cmsg;
    
  •        size_t cspace;
           v = rb_check_convert_type(elt, T_ARRAY, "Array", "to_ary");
           if (!NIL_P(v)) {
               elt = v;
    

@@ -1192,21 +1236,46 @@ bsock_sendmsg_internal(int argc, VALUE *argv,
VALUE sock, int nonblock)
level = rsock_level_arg(family, vlevel);
type = rsock_cmsg_type_arg(family, level, vtype);
StringValue(cdata);

  •        oldlen = RSTRING_LEN(controls_str);
    
  •        cspace = CMSG_SPACE(RSTRING_LEN(cdata));
    
  •        rb_str_resize(controls_str, oldlen + cspace);
    
  •        cmsg = RSTRING_PTR(controls_str)+oldlen;
    
  •        memset((char *)cmsg, 0, cspace);
    
  •        memset((char *)&cmh, 0, sizeof(cmh));
    
  •        cmh.cmsg_level = level;
    
  •        cmh.cmsg_type = type;
    
  •        cmh.cmsg_len = (socklen_t)CMSG_LEN(RSTRING_LEN(cdata));
    
  •        MEMCPY(cmsg, &cmh, char, sizeof(cmh));
    
  •        MEMCPY(cmsg+((char*)CMSG_DATA(&cmh)-(char*)&cmh), 
    

RSTRING_PTR(cdata), char, RSTRING_LEN(cdata));

  •        last_level = cmh.cmsg_level;
    
  •        last_type = cmh.cmsg_type;
    
  •  last_pad = cspace - cmh.cmsg_len;
    
  •        cmsg = prepare_msghdr(controls_str, level, type, 
    

RSTRING_LEN(cdata));

  •        MEMCPY(cmsg, RSTRING_PTR(cdata), char, RSTRING_LEN(cdata));
       }
    

+#else

  •  no_msg_control:
    
  • rb_raise(rb_eNotImpError, “control message for sendmsg is
    unimplemented”);
    +#endif
  • }
  • if (!NIL_P(vopts)) {
  • VALUE rights = rb_hash_aref(vopts, sym_scm_rights);
  • if (!NIL_P(rights)) {
    +#if defined(HAVE_ST_MSG_CONTROL)
  •  VALUE tmp = rb_check_array_type(rights);
    
  •        long count = NIL_P(tmp) ? 1 : RARRAY_LEN(tmp);
    
  •        char *cmsg;
    
  •  int fd;
    
  •  if (!controls_str) controls_str = rb_str_tmp_new(0);
    
  •        cmsg = prepare_msghdr(controls_str, SOL_SOCKET, SCM_RIGHTS,
    
  •                              count * sizeof(int));
    
  •        if (NIL_P(tmp)) {
    
  •            fd = io_to_fd(rights);
    
  •            MEMCPY(cmsg, &fd, int, 1);
    
  •        }
    
  •        else {
    
  •            long i;
    
  •            rights = tmp;
    
  •            for (i = 0; i < count && i < RARRAY_LEN(rights); ++i) {
    
  •                fd = io_to_fd(RARRAY_PTR(rights)[i]);
    
  •                MEMCPY(cmsg, &fd, int, 1);
    
  •                cmsg += sizeof(int);
    
  •            }
    
  •        }
    

+#else

  •  goto no_msg_control;
    

+#endif

  • }
  • }
    +#if defined(HAVE_ST_MSG_CONTROL)
  • {
    +# if TRIM_PADDING
    if (last_pad) {
    /*
    * This code removes the last padding from msg_controllen.
    @@ -1228,15 +1297,12 @@ bsock_sendmsg_internal(int argc, VALUE *argv,
    VALUE sock, int nonblock)
    * Basically, msg_controllen should contains the padding.
    * So the padding is removed only if a problem really
    exists.
    */
    -#if defined(NetBSD)
    if (last_level == SOL_SOCKET && last_type == SCM_RIGHTS)
    rb_str_set_len(controls_str,
    RSTRING_LEN(controls_str)-last_pad);
    -#endif
    }
    -#else
  • rb_raise(rb_eNotImpError, “control message for sendmsg is
    unimplemented”);
    -#endif
    +# endif
    }
    +#endif

    flags = NIL_P(vflags) ? 0 : NUM2INT(vflags);
    #ifdef MSG_DONTWAIT
    @@ -1492,7 +1558,7 @@ bsock_recvmsg_internal(int argc, VALUE *argv,
    VALUE sock, int nonblock)
    grow_buffer = NIL_P(vmaxdatlen) || NIL_P(vmaxctllen);

    request_scm_rights = 0;

  • if (!NIL_P(vopts) && RTEST(rb_hash_aref(vopts,
    ID2SYM(rb_intern(“scm_rights”)))))

  • if (!NIL_P(vopts) && RTEST(rb_hash_aref(vopts, sym_scm_rights)))
    request_scm_rights = 1;

    GetOpenFile(sock, fptr);
    @@ -1795,5 +1861,7 @@ rsock_init_ancdata(void)
    rb_define_method(rb_cAncillaryData, “ipv6_pktinfo”,
    ancillary_ipv6_pktinfo, 0);
    rb_define_method(rb_cAncillaryData, “ipv6_pktinfo_addr”,
    ancillary_ipv6_pktinfo_addr, 0);
    rb_define_method(rb_cAncillaryData, “ipv6_pktinfo_ifindex”,
    ancillary_ipv6_pktinfo_ifindex, 0);

  • sym_scm_rights = ID2SYM(rb_intern(“scm_rights”));
    #endif
    }
    diff --git i/test/socket/test_unix.rb w/test/socket/test_unix.rb
    index bde17cf…e9db22e 100644
    — i/test/socket/test_unix.rb
    +++ w/test/socket/test_unix.rb
    @@ -31,7 +31,7 @@ class TestSocket_UNIXSocket < Test::Unit::TestCase
    end
    end

  • def test_fd_passing_n
  • def fd_passing_test
    io_ary = []
    return if !defined?(Socket::SCM_RIGHTS)
    io_ary.concat IO.pipe
    @@ -42,8 +42,7 @@ class TestSocket_UNIXSocket < Test::Unit::TestCase
    send_io_ary << io
    UNIXSocket.pair {|s1, s2|
    begin
  •      ret = s1.sendmsg("\0", 0, nil, [Socket::SOL_SOCKET, 
    

Socket::SCM_RIGHTS,

  •                                      send_io_ary.map {|io2| 
    

io2.fileno }.pack(“i!*”)])

  •      ret = yield(s1, send_io_ary)
       rescue NotImplementedError
         return
       end
    

@@ -66,48 +65,38 @@ class TestSocket_UNIXSocket < Test::Unit::TestCase
io_ary.each {|io| io.close if !io.closed? }
end

  • def test_fd_passing_n
  • fd_passing_test do |s, ios|
  •  s.sendmsg("\0", 0, nil,
    
  •            [Socket::SOL_SOCKET, Socket::SCM_RIGHTS, 
    

ios.map(&:fileno).pack(“i!*”)])

  • end
  • end
  • def test_fd_passing_n2
  • io_ary = []
  • return if !defined?(Socket::SCM_RIGHTS)
  • return if !defined?(Socket::AncillaryData)
  • io_ary.concat IO.pipe
  • io_ary.concat IO.pipe
  • io_ary.concat IO.pipe
  • send_io_ary = []
  • io_ary.each {|io|
  •  send_io_ary << io
    
  •  UNIXSocket.pair {|s1, s2|
    
  •    begin
    
  •      ancdata = Socket::AncillaryData.unix_rights(*send_io_ary)
    
  •      ret = s1.sendmsg("\0", 0, nil, ancdata)
    
  •    rescue NotImplementedError
    
  •      return
    
  •    end
    
  •    assert_equal(1, ret)
    
  •    ret = s2.recvmsg(:scm_rights=>true)
    
  •    data, srcaddr, flags, *ctls = ret
    
  •    recv_io_ary = []
    
  •    ctls.each {|ctl|
    
  •      next if ctl.level != Socket::SOL_SOCKET || ctl.type != 
    

Socket::SCM_RIGHTS

  •      recv_io_ary.concat ctl.unix_rights
    
  •    }
    
  •    assert_equal(send_io_ary.length, recv_io_ary.length)
    
  •    send_io_ary.length.times {|i|
    
  •      assert_not_equal(send_io_ary[i].fileno, 
    

recv_io_ary[i].fileno)

  •      assert(File.identical?(send_io_ary[i], recv_io_ary[i]))
    
  •    }
    
  •  }
    
  • }
  • ensure
  • io_ary.each {|io| io.close if !io.closed? }
  • fd_passing_test do |s, ios|
  •  ancdata = Socket::AncillaryData.unix_rights(*ios)
    
  •  s.sendmsg("\0", 0, nil, ancdata)
    
  • end
  • end
  • def test_fd_passing_n3
  • fd_passing_test do |s, ios|
  •  s.sendmsg("\0", 0, nil, scm_rights: ios.map(&:fileno))
    
  • end
  • end
  • def test_fd_passing_n4
  • fd_passing_test do |s, ios|
  •  s.sendmsg("\0", 0, nil, scm_rights: ios)
    
  • end
    end
  • def test_sendmsg
  • def sendmsg_test
    return if !defined?(Socket::SCM_RIGHTS)
    IO.pipe {|r1, w|
    UNIXSocket.pair {|s1, s2|
    begin
  •      ret = s1.sendmsg("\0", 0, nil, [Socket::SOL_SOCKET, 
    

Socket::SCM_RIGHTS, [r1.fileno].pack(“i!”)])

  •      ret = yield(s1, r1)
       rescue NotImplementedError
         return
       end
    

@@ -122,6 +111,24 @@ class TestSocket_UNIXSocket < Test::Unit::TestCase
}
end

  • def test_sendmsg_1
  • sendmsg_test do |s, r|
  •  s.sendmsg("\0", 0, nil, [Socket::SOL_SOCKET, Socket::SCM_RIGHTS, 
    

[r.fileno].pack(“i!”)])

  • end
  • end
  • def test_sendmsg_2
  • sendmsg_test do |s, r|
  •  s.sendmsg("\0", 0, nil, scm_rights: r.fileno)
    
  • end
  • end
  • def test_sendmsg_3
  • sendmsg_test do |s, r|
  •  s.sendmsg("\0", 0, nil, scm_rights: r)
    
  • end
  • end
  • def test_sendmsg_ancillarydata_int
    return if !defined?(Socket::SCM_RIGHTS)
    return if !defined?(Socket::AncillaryData)

$B$^$D$b$H(B $B$f$-$R$m$G$9(B

In message “Re: [ruby-dev:42869] [feature:trunk] option for
Socket#sendmsg”
on Thu, 23 Dec 2010 21:01:19 +0900, Nobuyoshi N.
[email protected] writes:

|Socket#recvmsg$B$O(B scm_rights: true $B$r;XDj$9$k$@$1$G%a%$%s$N%G!<%?$@$1$G(B
|$B$J$/4JC1$K(BIO$B$r<u$1<h$k$3$H$,$G$-$^$9$,!"0lJ}$G(BSocket#sendmsg$BB&$K$OBP1~(B
|$B$9$k;XDj$,$G$-$^$;$s!#0J2<$N$h$&$J%*%W%7%g%s$rDI2C$9$k$N$O$I$&$G$7$g$&(B
|$B$+!#(B
|
| s.sendmsg(“foo”, scm_rights: STDIN)
| s.sendmsg(“foo”, scm_rights: [STDIN, STDOUT])

$B$$$$$s$8$c$J$$$G$7$g$&$+!#H?BP0U8+$N$"$kJ}$OAa$a$K?=9p$7$F$/(B
$B$@$5$$!#(B

2010$BG/(B12$B7n(B23$BF|(B21:01 Nobuyoshi N. [email protected]:

Socket#recvmsg$B$O(B scm_rights: true $B$r;XDj$9$k$@$1$G%a%$%s$N%G!<%?$@$1$G(B

$B$J$/4JC1$K(BIO$B$r<u$1<h$k$3$H$,$G$-$^$9$,!"0lJ}$G(BSocket#sendmsg$BB&$K$OBP1~(B

$B$9$k;XDj$,$G$-$^$;$s!#0J2<$N$h$&$J%*%W%7%g%s$rDI2C$9$k$N$O$I$&$G$7$g$&(B
$B$+!#(B

s.sendmsg(“foo”, scm_rights: STDIN)
s.sendmsg(“foo”, scm_rights: [STDIN, STDOUT])

$B$^$:!"8=:_$G$b0J2<$N$h$&$K2DG=$G$9!#(B

% ./ruby -rsocket -e ’
s1, s2 = Socket.pair(:UNIX, :DGRAM)
s1.sendmsg “stdin”, 0, nil, Socket::AncillaryData.unix_rights(STDIN)
_, _, _, ctl = s2.recvmsg(:scm_rights=>true)
p ctl
p ctl.unix_rights

#<Socket::AncillaryData: UNIX SOCKET RIGHTS 6>
[#<IO:fd 6>]

recvmsg $BB&$K(B scm_rights: true $B$,I,MW$J$N$O(B
fd $B$,(B leak $B$9$k$N$rKI$0$?$a$G!“(B
sendmsg $BB&$KBP1~$9$k$b$N$,I,MW$H$O9M$($?$3$H$,$”$j$^$;$s$G$7$?!#(B

$BDI2C$7$?$$0U?^$O2?$G$7$g$&!)(B

$B$^$D$b$H(B $B$f$-$R$m$G$9(B

In message “Re: [ruby-dev:42873] Re: [feature:trunk] option for
Socket#sendmsg”
on Thu, 23 Dec 2010 23:02:09 +0900, Tanaka A. [email protected]
writes:

|$B$^$:!"8=:$G$b0J2<$N$h$&$K2DG=$G$9!#(B
|
|% ./ruby -rsocket -e ’
|s1, s2 = Socket.pair(:UNIX, :DGRAM)
|s1.sendmsg “stdin”, 0, nil, Socket::AncillaryData.unix_rights(STDIN)
|
, _, _, ctl = s2.recvmsg(:scm_rights=>true)
|p ctl
|p ctl.unix_rights
|’
|#<Socket::AncillaryData: UNIX SOCKET RIGHTS 6>
|[#<IO:fd 6>]

$BCN$j$^$;$s$G$7$?!#(B

|$BDI2C$7$?$$0U?^$O2?$G$7$g$&!)(B

$B=c?h$K3Z$K=q$-$?$$$H$$$&$3$H$G$O!#(B

2010$BG/(B12$B7n(B23$BF|(B23:26 Yukihiro M. [email protected]:

|$B$^$:!"8=:_$G$b0J2<$N$h$&$K2DG=$G$9!#(B

$BCN$j$^$;$s$G$7$?!#(B

$BDs0F$+$i$=$l$,$o$+$i$J$$$N$O$A$g$C$H@eB-$i$:$G$9$h$M!#(B

|$BDI2C$7$?$$0U?^$O2?$G$7$g$&!)(B

$B=c?h$K3Z$K=q$-$?$$$H$$$&$3$H$G$O!#(B

$B$?$7$+$K>C5nK!$G?dB,$9$k$H$=$&$@$m$&$H$O;W$&$N$G$9$,!“(B
$B?dB,$N>e$KE83+$9$k$h$j!”$^$:?R$M$?$[$&$,3N<B$J$N$G!#(B

$B$J$+$@$G$9!#(B

At Fri, 24 Dec 2010 00:31:09 +0900,
Tanaka A. wrote in [ruby-dev:42875]:

2010$BG/(B12$B7n(B23$BF|(B23:26 Yukihiro M. [email protected]:

|$B$^$:!"8=:_$G$b0J2<$N$h$&$K2DG=$G$9!#(B

$B$=$l$O%F%9%H$r8+$F5$$E$-$^$7$?!#:G=i$O(Bsend_io$B$K%G!<%?$rDI2C$9$k$3$H$r9M(B
$B$($F$$$?$N$G$9$,!#(B

$B=c?h$K3Z$K=q$-$?$$$H$$$&$3$H$G$O!#(B

$B$?$7$+$K>C5nK!$G?dB,$9$k$H$=$&$@$m$&$H$O;W$&$N$G$9$,!“(B
$B?dB,$N>e$KE83+$9$k$h$j!”$^$:?R$M$?$[$&$,3N<B$J$N$G!#(B

$B$H$$$&$3$H$G$9!#(B

s1.sendmsg “stdin”, 0, nil, Socket::AncillaryData.unix_rights(STDIN)

$B$H$$$&=q$-J}$O!“(BSocket::AncillaryData$B$H$$$&;XDj$,D9$C$?$i$7$$$3$H$H!”$3(B
$B$l$,$J$1$l$P>JN,$G$-$k(Bflags$B$d(Bdest_sockaddr$B$,>JN,$G$-$J$/$J$k$3$H$,4r$7(B
$B$/$J$$$H;W$$$^$9!#(B

2010$BG/(B12$B7n(B24$BF|(B7:42 Nobuyoshi N. [email protected]:

s1.sendmsg “stdin”, 0, nil, Socket::AncillaryData.unix_rights(STDIN)

$B$H$$$&=q$-J}$O!“(BSocket::AncillaryData$B$H$$$&;XDj$,D9$C$?$i$7$$$3$H$H!”$3(B

$B$l$,$J$1$l$P>JN,$G$-$k(Bflags$B$d(Bdest_sockaddr$B$,>JN,$G$-$J$/$J$k$3$H$,4r$7(B

$B$/$J$$$H;W$$$^$9!#(B

$BJd=u%G!<%?$O(B ($BOHAH$_$H$7$F$O(B) SCM_RIGHTS $B0J30$K$b$“$k$N$G!”(B
SCM_RIGHTS $B$@$1FCJL07$$$9$k$3$H$K0cOB46$,$"$j$^$9!#(B

$BIQHK$K;H$&$b$N$G$b$J$$$G$9$7!#(B