diff options
-rw-r--r-- | ext/posix_mq/posix_mq.c | 47 | ||||
-rw-r--r-- | test/test_posix_mq.rb | 14 |
2 files changed, 61 insertions, 0 deletions
diff --git a/ext/posix_mq/posix_mq.c b/ext/posix_mq/posix_mq.c index cbc32b9..5e8122e 100644 --- a/ext/posix_mq/posix_mq.c +++ b/ext/posix_mq/posix_mq.c @@ -26,6 +26,7 @@ #if defined(__linux__) # define MQD_TO_FD(mqd) (int)(mqd) +# define FD_TO_MQD(fd) (mqd_t)(fd) #elif defined(HAVE___MQ_OSHANDLE) /* FreeBSD */ # define MQD_TO_FD(mqd) __mq_oshandle(mqd) #else @@ -365,6 +366,35 @@ static void rstruct2mqattr(struct mq_attr *attr, VALUE astruct, int all) attr->mq_curmsgs = NUM2LONG(tmp); } +#ifdef FD_TO_MQD +/* + * call-seq: + * POSIX_MQ.for_fd(socket) => mq + * + * Adopts a socket as a POSIX message queue. Argument will be + * checked to ensure it is a POSIX message queue socket. + * + * This is useful for adopting systemd sockets passed via the + * ListenMessageQueue directive. + * Returns a +POSIX_MQ+ instance. This method is only available + * under Linux and FreeBSD and is not intended to be portable. + * + */ +static VALUE for_fd(VALUE klass, VALUE socket) +{ + VALUE mqv = alloc(klass); + struct posix_mq *mq = get(mqv, 0); + + mq->name = Qnil; + mq->des = FD_TO_MQD(NUM2INT(socket)); + + if (mq_getattr(mq->des, &mq->attr) < 0) + rb_sys_fail("provided file descriptor is not a POSIX MQ"); + + return mqv; +} +#endif /* FD_TO_MQD */ + /* * call-seq: * POSIX_MQ.new(name [, flags [, mode [, mq_attr]]) => mq @@ -507,6 +537,10 @@ static VALUE _unlink(VALUE self) struct posix_mq *mq = get(self, 0); int rv; + if (NIL_P(mq->name)) { + rb_raise(rb_eArgError, "can not unlink an adopted socket"); + } + assert(TYPE(mq->name) == T_STRING && "mq->name is not a string"); rv = mq_unlink(RSTRING_PTR(mq->name)); @@ -807,6 +841,15 @@ static VALUE name(VALUE self) { struct posix_mq *mq = get(self, 0); + if (NIL_P(mq->name)) { + /* + * We could use readlink(2) on /proc/self/fd/N, but lots of + * care required. + * http://stackoverflow.com/questions/1188757/ + */ + rb_raise(rb_eArgError, "can not get name of an adopted socket"); + } + return rb_str_dup(mq->name); } @@ -1114,6 +1157,10 @@ void Init_posix_mq_ext(void) rb_define_method(cPOSIX_MQ, "to_io", to_io, 0); #endif +#ifdef FD_TO_MQD + rb_define_module_function(cPOSIX_MQ, "for_fd", for_fd, 1); +#endif + id_new = rb_intern("new"); id_kill = rb_intern("kill"); id_fileno = rb_intern("fileno"); diff --git a/test/test_posix_mq.rb b/test/test_posix_mq.rb index 1cc24aa..54c7223 100644 --- a/test/test_posix_mq.rb +++ b/test/test_posix_mq.rb @@ -17,6 +17,8 @@ class Test_POSIX_MQ < Test::Unit::TestCase warn "POSIX_MQ#to_io not supported on this platform: #{RUBY_PLATFORM}" POSIX_MQ.method_defined?(:notify) or warn "POSIX_MQ#notify not supported on this platform: #{RUBY_PLATFORM}" + POSIX_MQ.respond_to?(:for_fd) or + warn "POSIX_MQ::for_fd not supported on this platform: #{RUBY_PLATFORM}" def setup @mq = nil @@ -244,6 +246,18 @@ class Test_POSIX_MQ < Test::Unit::TestCase assert_nothing_raised { IO.select([@mq], nil, nil, 0) } end if POSIX_MQ.method_defined?(:to_io) + def test_for_fd + buf = "" + @mq = POSIX_MQ.new @path, IO::CREAT|IO::RDWR, 0666 + @alt = POSIX_MQ.for_fd(@mq.to_io.to_i) + assert_equal true, @mq.send("hello", 0) + assert_equal [ "hello", 0 ], @alt.receive(buf) + assert_equal "hello", buf + assert_equal @mq.to_io.to_i, @alt.to_io.to_i + assert_raises(ArgumentError) { @alt.name } + assert_raises(Errno::EBADF) { POSIX_MQ.for_fd(1) } + end if POSIX_MQ.respond_to?(:for_fd) && POSIX_MQ.method_defined?(:to_io) + def test_notify rd, wr = IO.pipe orig = trap(:USR1) { wr.syswrite('.') } |