From 6622d115c795d88b99d844ce8b74e979a2b55ae6 Mon Sep 17 00:00:00 2001 From: Christopher Lord Date: Sat, 3 Jan 2015 10:15:58 -0700 Subject: Ability to adopt file descriptors 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 [ew: simplified type checks to be consistent with IO.for_fd, fixed test case] Signed-off-by: Eric Wong --- ext/posix_mq/posix_mq.c | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) (limited to 'ext/posix_mq/posix_mq.c') 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"); -- cgit v1.2.3-24-ge0c7