posix_mq RubyGem user+dev discussion/patches/pulls/bugs/help
 help / color / mirror / code / Atom feed
From: Christopher Lord <christopher@lord.ac>
To: ruby.posix.mq@librelist.com
Subject: [PATCH] Ability to adopt file descriptors
Date: Sat,  3 Jan 2015 10:15:58 -0700	[thread overview]
Message-ID: <1420305358-21019-1-git-send-email-christopher@lord.ac> (raw)
In-Reply-To: <1420305358-21019-1-git-send-email-christopher@lord.ac>

This patch adds support for adopting an existing file descriptor, together
with testcases. The need for this comes up when we use systemd with the
ListenMessageQueue directive. For socket activation, systemd opens the POSIX
message queue and expects user code to begin using the file descriptor
without opening it in-process.

To support the systemd model in the `posix_mq` gem, this patch suggests
imitating the behavior on the Socket class, which uses `#for_fd` to create a
socket class from a descriptor.

One confounding factor exists. POSIX queues have a name but it is difficult
to get access to this name in a safe manner from the file descriptor. One
option would be to `readlink(2)` on `/proc/self/fd/N` to get the name[1], but
note that if the descriptor is unlinked we wouldn't get anything usable.
Rather than risk incorrect behavior and extra complexity, I've decided to
just raise an `ArgumentError` if `#name` is called on adopted descriptors.
Typically one wouldn't need the actual name in a systemd socket-activated
situation, anyway.

[1]: http://stackoverflow.com/questions/1188757/getting-filename-from-file-descriptor-in-c
---
 ext/posix_mq/posix_mq.c | 63 ++++++++++++++++++++++++++++++++++++++++++++++++-
 test/test_posix_mq.rb   | 13 ++++++++++
 2 files changed, 75 insertions(+), 1 deletion(-)

diff --git a/ext/posix_mq/posix_mq.c b/ext/posix_mq/posix_mq.c
index cbc32b9..901b6ce 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
@@ -66,7 +67,7 @@ static int MQ_IO_CLOSE(struct posix_mq *mq)
 # define PMQ_TRY       (1<<1)
 
 static VALUE cAttr;
-static ID id_new, id_kill, id_fileno, id_divmod;
+static ID id_new, id_kill, id_fileno, id_divmod, id_to_i;
 static ID id_flags, id_maxmsg, id_msgsize, id_curmsgs;
 static VALUE sym_r, sym_w, sym_rw;
 static const mqd_t MQD_INVALID = (mqd_t)-1;
@@ -365,6 +366,51 @@ 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;
+
+	if (rb_respond_to(socket, id_to_i)) {
+		VALUE fd_num = rb_funcall(socket, id_to_i, 0);
+		mq->des = FD_TO_MQD(NUM2INT(fd_num));
+	}
+	else {
+		rb_raise(rb_eArgError, "provided argument must be (or convertable to) an integer file descriptor");
+	}
+
+	if (mq->des < 0) {
+		rb_raise(rb_eArgError, "provided argument must be a valid file descriptor");
+	}
+
+	if (mq_getattr(mq->des, &mq->attr) < 0) {
+		if (errno) {
+			rb_raise(rb_eArgError, "provided file descriptor is not a POSIX message queue");
+		}
+	}
+	return mqv;
+}
+
+
+#endif
+
 /*
  * call-seq:
  *	POSIX_MQ.new(name [, flags [, mode [, mq_attr]])	=> mq
@@ -507,6 +553,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 +857,12 @@ 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/getting-filename-from-file-descriptor-in-c  */
+		rb_raise(rb_eArgError, "can not get name of an adopted socket");
+	}
+
 	return rb_str_dup(mq->name);
 }
 
@@ -1114,6 +1170,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");
@@ -1122,6 +1182,7 @@ void Init_posix_mq_ext(void)
 	id_maxmsg = rb_intern("maxmsg");
 	id_msgsize = rb_intern("msgsize");
 	id_curmsgs = rb_intern("curmsgs");
+	id_to_i = rb_intern("to_i");
 	sym_r = ID2SYM(rb_intern("r"));
 	sym_w = ID2SYM(rb_intern("w"));
 	sym_rw = ID2SYM(rb_intern("rw"));
diff --git a/test/test_posix_mq.rb b/test/test_posix_mq.rb
index 1cc24aa..ceb9f7c 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.class.method_defined?(:for_fd) or
+    warn "POSIX_MQ::for_fd not supported on this platform: #{RUBY_PLATFORM}"
 
   def setup
     @mq = nil
@@ -244,6 +246,17 @@ 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
+    @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(ArgumentError) { POSIX_MQ.for_fd(1) }
+  end if POSIX_MQ.class.method_defined?(:for_fd) && POSIX_MQ.method_defined(:to_io)
+
   def test_notify
     rd, wr = IO.pipe
     orig = trap(:USR1) { wr.syswrite('.') }
-- 
1.8.3.1



       reply	other threads:[~2015-01-04  0:15 UTC|newest]

Thread overview: 4+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2015-01-03 17:15 Christopher Lord [this message]
2015-01-04  1:16 ` [PATCH] Ability to adopt file descriptors Eric Wong
2015-01-04  3:34   ` Christopher Lord
2015-01-05  5:34     ` Eric Wong

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

  List information: https://yhbt.net/ruby_posix_mq/

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=1420305358-21019-1-git-send-email-christopher@lord.ac \
    --to=christopher@lord.ac \
    --cc=ruby.posix.mq@librelist.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
Code repositories for project(s) associated with this public inbox

	https://yhbt.net/ruby_posix_mq.git/

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).