about summary refs log tree commit homepage
diff options
context:
space:
mode:
-rw-r--r--ext/posix_mq/posix_mq.c47
-rw-r--r--test/test_posix_mq.rb14
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('.') }