Feature #4195

option for Socket#sendmsg

Added by Nobuyoshi Nakada over 3 years ago. Updated about 1 year ago.

[ruby-dev:42869]
Status:Rejected
Priority:Low
Assignee:Akira Tanaka
Category:ext
Target version:next minor

Description

=begin
なかだです。

Socket#recvmsgは scm_rights: true を指定するだけでメインのデータだけで
なく簡単にIOを受け取ることができますが、一方でSocket#sendmsg側には対応
する指定ができません。以下のようなオプションを追加するのはどうでしょう
か。

s.sendmsg("foo", scmrights: 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

+static ID symscmrights;
+
#if defined(HAVESTMSGCONTROL)
static VALUE rb
cAncillaryData;

@@ -1126,17 +1128,63 @@ rbsendmsg(int fd, const struct msghdr *msg, int flags)
return rb
threadblockingregion(nogvlsendmsgfunc, &args, RUBYUBFIO, 0);
}

+#if defined(HAVESTMSGCONTROL)
+static size
t
+iotofd(VALUE io)
+{
+ VALUE fnum = rbchecktointeger(io, "toint");
+ if (NILP(fnum))
+ fnum = rb
converttype(io, TFIXNUM, "Fixnum", "fileno");
+ return NUM2UINT(fnum);
+}
+
+static char *
+preparemsghdr(VALUE controlsstr, int level, int type, long clen)
+{
+ struct cmsghdr cmh;
+ char cmsg;
+ sizet cspace;
+ long oldlen = RSTRING
LEN(controlsstr);
+ cspace = CMSG
SPACE(clen);
+ rbstrresize(controlsstr, oldlen + cspace);
+ cmsg = RSTRING
PTR(controlsstr)+oldlen;
+ memset((char *)cmsg, 0, cspace);
+ memset((char *)&cmh, 0, sizeof(cmh));
+ cmh.cmsg
level = level;
+ cmh.cmsgtype = type;
+ cmh.cmsg
len = (socklent)CMSGLEN(clen);
+ MEMCPY(cmsg, &cmh, char, sizeof(cmh));
+ return cmsg+((char
)CMSGDATA(&cmh)-(char*)&cmh);
+}
+
+# if defined(
NetBSD)
+# define TRIM
PADDING 1
+# endif
+# if TRIMPADDING
+# define prepare
msghdr(controlsstr, level, type, clen) \
+ (last
pad = CMSGSPACE(clen) - CMSGLEN(clen), \
+ preparemsghdr((controlsstr), \
+ lastlevel = (level), lasttype = (type), \
+ (clen)))
+# endif
+#endif
+
static VALUE
bsocksendmsginternal(int argc, VALUE *argv, VALUE sock, int nonblock)
{
rbiot *fptr;
- VALUE data, vflags, destsockaddr;
+ VALUE data, vflags, dest
sockaddr, vopts = Qnil;
VALUE *controlsptr;
int controls
num;
struct msghdr mh;
struct iovec iov;
#if defined(HAVESTMSGCONTROL)
volatile VALUE controls
str = 0;
+# if TRIMPADDING
+ size
t lastpad = 0;
+ int last
level = 0;
+ int lasttype = 0;
+# endif
#endif
int flags;
ssize
t ss;
@@ -1152,6 +1200,8 @@ bsocksendmsginternal(int argc, VALUE *argv, VALUE sock, int nonblock)

  if (argc == 0)
      rb_raise(rb_eArgError, "mesg argument required");
  • if (1 < argc && RBTYPEP(argv[argc-1], T_HASH))
  • vopts = argv[--argc]; data = argv[0]; if (1 < argc) vflags = argv[1]; if (2 < argc) destsockaddr = argv[2]; @@ -1162,19 +1212,13 @@ bsocksendmsginternal(int argc, VALUE *argv, VALUE sock, int nonblock) if (controlsnum) { #if defined(HAVESTMSG_CONTROL) int i;
  • sizet lastpad = 0;
  • int last_level = 0;
  • int lasttype = 0; controlsstr = rbstrtmpnew(0); for (i = 0; i < controlsnum; i++) { VALUE elt = controls_ptr[i], v; VALUE vlevel, vtype; int level, type; VALUE cdata;
  • long oldlen;
  • struct cmsghdr cmh; char *cmsg;
  • sizet cspace; v = rbcheckconverttype(elt, TARRAY, "Array", "toary"); if (!NILP(v)) { elt = v; @@ -1192,21 +1236,46 @@ bsocksendmsginternal(int argc, VALUE *argv, VALUE sock, int nonblock) level = rsocklevelarg(family, vlevel); type = rsockcmsgtypearg(family, level, vtype); StringValue(cdata);
  • oldlen = RSTRINGLEN(controlsstr);
  • cspace = CMSGSPACE(RSTRINGLEN(cdata));
  • rbstrresize(controls_str, oldlen + cspace);
  • cmsg = RSTRINGPTR(controlsstr)+oldlen;
  • memset((char *)cmsg, 0, cspace);
  • memset((char *)&cmh, 0, sizeof(cmh));
  • cmh.cmsg_level = level;
  • cmh.cmsg_type = type;
  • cmh.cmsglen = (socklent)CMSGLEN(RSTRINGLEN(cdata));
  • MEMCPY(cmsg, &cmh, char, sizeof(cmh));
  • MEMCPY(cmsg+((char)CMSG_DATA(&cmh)-(char)&cmh), RSTRINGPTR(cdata), char, RSTRINGLEN(cdata));
  • lastlevel = cmh.cmsglevel;
  • lasttype = cmh.cmsgtype;
  • lastpad = cspace - cmh.cmsglen;
  • cmsg = preparemsghdr(controlsstr, level, type, RSTRING_LEN(cdata));
  • MEMCPY(cmsg, RSTRINGPTR(cdata), char, RSTRINGLEN(cdata)); } +#else
  • nomsgcontrol:
  • rbraise(rbeNotImpError, "control message for sendmsg is unimplemented"); +#endif
  • }
  • if (!NIL_P(vopts)) {
  • VALUE rights = rbhasharef(vopts, symscmrights);
  • if (!NILP(rights)) { +#if defined(HAVESTMSGCONTROL)
  • VALUE tmp = rbcheckarray_type(rights);
  • long count = NILP(tmp) ? 1 : RARRAYLEN(tmp);
  • char *cmsg;
  • int fd;
  • if (!controlsstr) controlsstr = rbstrtmp_new(0);
  • cmsg = preparemsghdr(controlsstr, SOLSOCKET, SCMRIGHTS,
  • count * sizeof(int));
  • if (NIL_P(tmp)) {
  • fd = iotofd(rights);
  • MEMCPY(cmsg, &fd, int, 1);
  • }
  • else {
  • long i;
  • rights = tmp;
  • for (i = 0; i < count && i < RARRAY_LEN(rights); ++i) {
  • fd = iotofd(RARRAY_PTR(rights)[i]);
  • MEMCPY(cmsg, &fd, int, 1);
  • cmsg += sizeof(int);
  • }
  • } +#else
  • goto nomsgcontrol; +#endif
  • }
  • } +#if defined(HAVESTMSG_CONTROL)
  • { +# if TRIMPADDING if (lastpad) { /* * This code removes the last padding from msgcontrollen. @@ -1228,15 +1297,12 @@ bsocksendmsginternal(int argc, VALUE *argv, VALUE sock, int nonblock) * Basically, msgcontrollen should contains the padding. * So the padding is removed only if a problem really exists. */ -#if defined(NetBSD) if (lastlevel == SOLSOCKET && lasttype == SCMRIGHTS) rbstrsetlen(controlsstr, RSTRINGLEN(controlsstr)-last_pad); -#endif } -#else
  • rbraise(rbeNotImpError, "control message for sendmsg is unimplemented");
    -#endif
    +# endif
    }
    +#endif

    flags = NILP(vflags) ? 0 : NUM2INT(vflags);
    #ifdef MSG
    DONTWAIT
    @@ -1492,7 +1558,7 @@ bsockrecvmsginternal(int argc, VALUE *argv, VALUE sock, int nonblock)
    growbuffer = NILP(vmaxdatlen) || NIL_P(vmaxctllen);

    requestscmrights = 0;

  • if (!NILP(vopts) && RTEST(rbhasharef(vopts, ID2SYM(rbintern("scm_rights")))))

  • if (!NILP(vopts) && RTEST(rbhasharef(vopts, symscmrights)))
    request
    scm_rights = 1;

    GetOpenFile(sock, fptr);
    @@ -1795,5 +1861,7 @@ rsockinitancdata(void)
    rbdefinemethod(rbcAncillaryData, "ipv6pktinfo", ancillaryipv6pktinfo, 0);
    rbdefinemethod(rbcAncillaryData, "ipv6pktinfoaddr", ancillaryipv6pktinfoaddr, 0);
    rbdefinemethod(rbcAncillaryData, "ipv6pktinfoifindex", ancillaryipv6pktinfoifindex, 0);
    +

  • symscmrights = ID2SYM(rbintern("scmrights"));
    #endif
    }
    diff --git i/test/socket/testunix.rb w/test/socket/testunix.rb
    index bde17cf..e9db22e 100644
    --- i/test/socket/testunix.rb
    +++ w/test/socket/test
    unix.rb
    @@ -31,7 +31,7 @@ class TestSocket_UNIXSocket < Test::Unit::TestCase
    end
    end

  • def testfdpassing_n

  • def fdpassingtest
    ioary = []
    return if !defined?(Socket::SCM
    RIGHTS)
    ioary.concat IO.pipe
    @@ -42,8 +42,7 @@ class TestSocket
    UNIXSocket < Test::Unit::TestCase
    sendioary << 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 TestSocketUNIXSocket < Test::Unit::TestCase
    io
    ary.each {|io| io.close if !io.closed? }
    end

  • def testfdpassing_n

  • fdpassingtest do |s, ios|

  •  s.sendmsg("\0", 0, nil,
    
  •            [Socket::SOL_SOCKET, Socket::SCM_RIGHTS, ios.map(&:fileno).pack("i!*")])
    
  • end

  • end
    +
    def testfdpassing_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

  • sendioary = []

  • 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? }

  • fdpassingtest do |s, ios|

  •  ancdata = Socket::AncillaryData.unix_rights(*ios)
    
  •  s.sendmsg("\0", 0, nil, ancdata)
    
  • end

  • end
    +

  • def testfdpassing_n3

  • fdpassingtest do |s, ios|

  •  s.sendmsg("\0", 0, nil, scm_rights: ios.map(&:fileno))
    
  • end

  • end
    +

  • def testfdpassing_n4

  • fdpassingtest do |s, ios|

  •  s.sendmsg("\0", 0, nil, scm_rights: ios)
    
  • end
    end

  • def test_sendmsg

  • def sendmsgtest
    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 testsendmsg1

  • sendmsg_test do |s, r|

  •  s.sendmsg("\0", 0, nil, [Socket::SOL_SOCKET, Socket::SCM_RIGHTS, [r.fileno].pack("i!")])
    
  • end

  • end
    +

  • def testsendmsg2

  • sendmsg_test do |s, r|

  •  s.sendmsg("\0", 0, nil, scm_rights: r.fileno)
    
  • end

  • end
    +

  • def testsendmsg3

  • sendmsg_test do |s, r|

  •  s.sendmsg("\0", 0, nil, scm_rights: r)
    
  • end

  • end
    +
    def testsendmsgancillarydataint
    return if !defined?(Socket::SCM
    RIGHTS)
    return if !defined?(Socket::AncillaryData)

    --- 僕の前にBugはない。
    --- 僕の後ろにBugはできる。
    中田 伸悦
    =end

History

#1 Updated by Nobuyoshi Nakada over 3 years ago

  • Category set to ext
  • Assignee set to Akira Tanaka

=begin

=end

#2 Updated by Yukihiro Matsumoto over 3 years ago

=begin
まつもと ゆきひろです

In message "Re: [feature:trunk] option for Socket#sendmsg"
on Thu, 23 Dec 2010 21:01:19 +0900, Nobuyoshi Nakada nobu@ruby-lang.org writes:

|Socket#recvmsgは scmrights: true を指定するだけでメインのデータだけで
|なく簡単にIOを受け取ることができますが、一方でSocket#sendmsg側には対応
|する指定ができません。以下のようなオプションを追加するのはどうでしょう
|か。
|
| s.sendmsg("foo", scm
rights: STDIN)
| s.sendmsg("foo", scm_rights: [STDIN, STDOUT])

いいんじゃないでしょうか。反対意見のある方は早めに申告してく
ださい。

=end

#3 Updated by Akira Tanaka over 3 years ago

=begin
2010年12月23日21:01 Nobuyoshi Nakada nobu@ruby-lang.org:

Socket#recvmsgは scm_rights: true を指定するだけでメインのデータだけで
なく簡単にIOを受け取ることができますが、一方でSocket#sendmsg側には対応
する指定ができません。以下のようなオプションを追加するのはどうでしょう
か。

s.sendmsg("foo", scmrights: STDIN)
s.sendmsg("foo", scm
rights: [STDIN, STDOUT])

まず、現在でも以下のように可能です。

% ./ruby -rsocket -e '
s1, s2 = Socket.pair(:UNIX, :DGRAM)
s1.sendmsg "stdin", 0, nil, Socket::AncillaryData.unixrights(STDIN)
_, _, _, ctl = s2.recvmsg(:scm
rights=>true)
p ctl
p ctl.unix_rights
'
#
[#]

recvmsg 側に scm_rights: true が必要なのは
fd が leak するのを防ぐためで、
sendmsg 側に対応するものが必要とは考えたことがありませんでした。

追加したい意図は何でしょう?
--
[田中 哲][たなか あきら][Tanaka Akira]

=end

#4 Updated by Yukihiro Matsumoto over 3 years ago

=begin
まつもと ゆきひろです

In message "Re: Re: [feature:trunk] option for Socket#sendmsg"
on Thu, 23 Dec 2010 23:02:09 +0900, Tanaka Akira akr@fsij.org writes:

|まず、現在でも以下のように可能です。
|
|% ./ruby -rsocket -e '
|s1, s2 = Socket.pair(:UNIX, :DGRAM)
|s1.sendmsg "stdin", 0, nil, Socket::AncillaryData.unixrights(STDIN)
|
, , _, ctl = s2.recvmsg(:scmrights=>true)
|p ctl
|p ctl.unix_rights
|'
|#
|[#]

知りませんでした。

|追加したい意図は何でしょう?

純粋に楽に書きたいということでは。

=end

#5 Updated by Akira Tanaka over 3 years ago

=begin
2010年12月23日23:26 Yukihiro Matsumoto matz@ruby-lang.org:

|まず、現在でも以下のように可能です。

知りませんでした。

提案からそれがわからないのはちょっと舌足らずですよね。

|追加したい意図は何でしょう?

純粋に楽に書きたいということでは。

たしかに消去法で推測するとそうだろうとは思うのですが、
推測の上に展開するより、まず尋ねたほうが確実なので。
--
[田中 哲][たなか あきら][Tanaka Akira]

=end

#6 Updated by Nobuyoshi Nakada over 3 years ago

=begin
なかだです。

At Fri, 24 Dec 2010 00:31:09 +0900,
Tanaka Akira wrote in :

2010年12月23日23:26 Yukihiro Matsumoto matz@ruby-lang.org:

|まず、現在でも以下のように可能です。

それはテストを見て気づきました。最初はsend_ioにデータを追加することを考
えていたのですが。

純粋に楽に書きたいということでは。

たしかに消去法で推測するとそうだろうとは思うのですが、
推測の上に展開するより、まず尋ねたほうが確実なので。

ということです。

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

という書き方は、Socket::AncillaryDataという指定が長ったらしいことと、こ
れがなければ省略できるflagsやdest_sockaddrが省略できなくなることが嬉し
くないと思います。

--
--- 僕の前にBugはない。
--- 僕の後ろにBugはできる。
中田 伸悦

=end

#7 Updated by Akira Tanaka over 3 years ago

=begin
2010年12月24日7:42 Nobuyoshi Nakada nobu@ruby-lang.org:

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

という書き方は、Socket::AncillaryDataという指定が長ったらしいことと、こ
れがなければ省略できるflagsやdest_sockaddrが省略できなくなることが嬉し
くないと思います。

補助データは (枠組みとしては) SCMRIGHTS 以外にもあるので、
SCM
RIGHTS だけ特別扱いすることに違和感があります。

頻繁に使うものでもないですし。
--
[田中 哲][たなか あきら][Tanaka Akira]

=end

#8 Updated by Shyouhei Urabe about 3 years ago

  • Status changed from Open to Assigned

#9 Updated by Yusuke Endoh over 1 year ago

  • Description updated (diff)
  • Target version set to next minor

#10 Updated by Akira Tanaka about 1 year ago

  • Status changed from Assigned to Rejected

頻度から考えて、とくに簡単にしなくてもいいんじゃないかと思います。
やりかたが複数になるのも気に入りませんし。

Also available in: Atom PDF