Add support for receiving fds in replies
authorKeith Packard <keithp@keithp.com>
Fri, 18 Jan 2013 09:29:40 +0000 (01:29 -0800)
committerKeith Packard <keithp@keithp.com>
Thu, 7 Nov 2013 22:02:37 +0000 (14:02 -0800)
Requests signal which replies will have fds, and the replies report
how many fds they expect in byte 1.

Signed-off-by: Keith Packard <keithp@keithp.com>
Reviewed-By: Uli Schlachter <psychon@znc.in>
src/c_client.py
src/xcb_in.c
src/xcbext.h
src/xcbint.h

index cc26f72..7280004 100644 (file)
@@ -303,6 +303,7 @@ def _c_type_setup(self, name, postfix):
     self.c_reply_name = _n(name + ('reply',))
     self.c_reply_type = _t(name + ('reply',))
     self.c_cookie_type = _t(name + ('cookie',))
+    self.c_reply_fds_name = _n(name + ('reply_fds',))
 
     self.need_aux = False
     self.need_serialize = False
@@ -1835,7 +1836,7 @@ def c_union(self, name):
     _c_complex(self)
     _c_iterator(self, name)
 
-def _c_request_helper(self, name, cookie_type, void, regular, aux=False):
+def _c_request_helper(self, name, cookie_type, void, regular, aux=False, reply_fds=False):
     '''
     Declares a request function.
     '''
@@ -1864,6 +1865,12 @@ def _c_request_helper(self, name, cookie_type, void, regular, aux=False):
     # What flag is passed to xcb_request
     func_flags = '0' if (void and regular) or (not void and not regular) else 'XCB_REQUEST_CHECKED'
 
+    if reply_fds:
+        if func_flags == '0':
+            func_flags = 'XCB_REQUEST_REPLY_FDS'
+        else:
+            func_flags = func_flags + '|XCB_REQUEST_REPLY_FDS'
+
     # Global extension id variable or NULL for xproto
     func_ext_global = '&' + _ns.c_ext_global_name if _ns.is_ext else '0'
 
@@ -2256,6 +2263,51 @@ def _c_reply(self, name):
 
     _c('}')
 
+def _c_reply_has_fds(self):
+    for field in self.fields:
+        if field.isfd:
+            return True
+    return False
+
+def _c_reply_fds(self, name):
+    '''
+    Declares the function that returns fds related to the reply.
+    '''
+    spacing1 = ' ' * (len(self.c_reply_type) - len('xcb_connection_t'))
+    spacing3 = ' ' * (len(self.c_reply_fds_name) + 2)
+    _h('')
+    _h('/**')
+    _h(' * Return the reply fds')
+    _h(' * @param c      The connection')
+    _h(' * @param reply  The reply')
+    _h(' *')
+    _h(' * Returns the array of reply fds of the request asked by')
+    _h(' * ')
+    _h(' * The returned value must be freed by the caller using free().')
+    _h(' */')
+    _c('')
+    _hc('')
+    _hc('/*****************************************************************************')
+    _hc(' **')
+    _hc(' ** int * %s', self.c_reply_fds_name)
+    _hc(' ** ')
+    _hc(' ** @param xcb_connection_t%s  *c', spacing1)
+    _hc(' ** @param %s  *reply', self.c_reply_type)
+    _hc(' ** @returns int *')
+    _hc(' **')
+    _hc(' *****************************************************************************/')
+    _hc(' ')
+    _hc('int *')
+    _hc('%s (xcb_connection_t%s  *c  /**< */,', self.c_reply_fds_name, spacing1)
+    _h('%s%s  *reply  /**< */);', spacing3, self.c_reply_type)
+    _c('%s%s  *reply  /**< */)', spacing3, self.c_reply_type)
+    _c('{')
+    
+    _c('    return xcb_get_reply_fds(c, reply, sizeof(%s) + 4 * reply->length);', self.c_reply_type)
+
+    _c('}')
+    
+
 def _c_opcode(name, opcode):
     '''
     Declares the opcode define for requests, events, and errors.
@@ -2816,14 +2868,17 @@ def c_request(self, name):
         # Reply structure definition
         _c_complex(self.reply)
         # Request prototypes
-        _c_request_helper(self, name, self.c_cookie_type, False, True)
-        _c_request_helper(self, name, self.c_cookie_type, False, False)
+        has_fds = _c_reply_has_fds(self.reply)
+        _c_request_helper(self, name, self.c_cookie_type, False, True, False, has_fds)
+        _c_request_helper(self, name, self.c_cookie_type, False, False, False, has_fds)
         if self.need_aux:
-            _c_request_helper(self, name, self.c_cookie_type, False, True, True)
-            _c_request_helper(self, name, self.c_cookie_type, False, False, True)
+            _c_request_helper(self, name, self.c_cookie_type, False, True, True, has_fds)
+            _c_request_helper(self, name, self.c_cookie_type, False, False, True, has_fds)
         # Reply accessors
         _c_accessors(self.reply, name + ('reply',), name)
         _c_reply(self, name)
+        if has_fds:
+            _c_reply_fds(self, name)
     else:
         # Request prototypes
         _c_request_helper(self, name, 'xcb_void_cookie_t', True, False)
index 8a7af92..b161e93 100644 (file)
@@ -90,11 +90,26 @@ static void remove_finished_readers(reader_list **prev_reader, uint64_t complete
     }
 }
 
+#if HAVE_SENDMSG
+static int read_fds(xcb_connection_t *c, int *fds, int nfd)
+{
+    int *ifds = &c->in.in_fd.fd[c->in.in_fd.ifd];
+    int infd = c->in.in_fd.nfd - c->in.in_fd.ifd;
+
+    if (nfd > infd)
+        return 0;
+    memcpy(fds, ifds, nfd * sizeof (int));
+    c->in.in_fd.ifd += nfd;
+    return 1;
+}
+#endif
+
 static int read_packet(xcb_connection_t *c)
 {
     xcb_generic_reply_t genrep;
     uint64_t length = 32;
     uint64_t eventlength = 0; /* length after first 32 bytes for GenericEvents */
+    int nfd = 0;         /* Number of file descriptors attached to the reply */
     uint64_t bufsize;
     void *buf;
     pending_reply *pend = 0;
@@ -164,13 +179,18 @@ static int read_packet(xcb_connection_t *c)
             genrep.length = p[2] * p[3] * 2;
         }
         length += genrep.length * 4;
+
+        /* XXX a bit of a hack -- we "know" that all FD replys place
+         * the number of fds in the pad0 byte */
+        if (pend && pend->flags & XCB_REQUEST_REPLY_FDS)
+            nfd = genrep.pad0;
     }
 
     /* XGE events may have sizes > 32 */
     if ((genrep.response_type & 0x7f) == XCB_XGE_EVENT)
         eventlength = genrep.length * 4;
 
-    bufsize = length + eventlength +
+    bufsize = length + eventlength + nfd * sizeof(int)  +
         (genrep.response_type == XCB_REPLY ? 0 : sizeof(uint32_t));
     if (bufsize < INT32_MAX)
         buf = malloc((size_t) bufsize);
@@ -198,6 +218,17 @@ static int read_packet(xcb_connection_t *c)
         }
     }
 
+#if HAVE_SENDMSG
+    if (nfd)
+    {
+        if (!read_fds(c, (int *) &((char *) buf)[length], nfd))
+        {
+            free(buf);
+            return 0;
+        }
+    }
+#endif
+
     if(pend && (pend->flags & XCB_REQUEST_DISCARD_REPLY))
     {
         free(buf);
@@ -431,6 +462,11 @@ void *xcb_wait_for_reply(xcb_connection_t *c, unsigned int request, xcb_generic_
     return ret;
 }
 
+int *xcb_get_reply_fds(xcb_connection_t *c, void *reply, size_t reply_size)
+{
+    return (int *) (&((char *) reply)[reply_size]);
+}
+
 static void insert_pending_discard(xcb_connection_t *c, pending_reply **prev_next, uint64_t seq)
 {
     pending_reply *pend;
@@ -577,6 +613,120 @@ xcb_generic_error_t *xcb_request_check(xcb_connection_t *c, xcb_void_cookie_t co
     return ret;
 }
 
+static xcb_generic_event_t *get_special_event(xcb_connection_t *c,
+                                              xcb_special_event_t *se)
+{
+    xcb_generic_event_t *event = NULL;
+    struct event_list *events;
+
+    if ((events = se->events) != NULL) {
+        event = events->event;
+        if (!(se->events = events->next))
+            se->events_tail = &se->events;
+        free (events);
+    }
+    return event;
+}
+
+xcb_generic_event_t *xcb_poll_for_special_event(xcb_connection_t *c,
+                                                xcb_special_event_t *se)
+{
+    xcb_generic_event_t *event;
+
+    if(c->has_error)
+        return 0;
+    pthread_mutex_lock(&c->iolock);
+    event = get_special_event(c, se);
+    pthread_mutex_unlock(&c->iolock);
+    return event;
+}
+
+xcb_generic_event_t *xcb_wait_for_special_event(xcb_connection_t *c,
+                                                xcb_special_event_t *se)
+{
+    xcb_generic_event_t *event;
+
+    if(c->has_error)
+        return 0;
+    pthread_mutex_lock(&c->iolock);
+
+    /* get_special_event returns 0 on empty list. */
+    while(!(event = get_special_event(c, se)))
+        if(!_xcb_conn_wait(c, &se->special_event_cond, 0, 0))
+            break;
+
+    pthread_mutex_unlock(&c->iolock);
+    return event;
+}
+
+xcb_special_event_t *
+xcb_register_for_special_xge(xcb_connection_t *c,
+                             uint8_t extension,
+                             uint32_t eid,
+                             uint32_t *stamp)
+{
+    xcb_special_event_t *se;
+
+    if(c->has_error)
+        return NULL;
+    pthread_mutex_lock(&c->iolock);
+    for (se = c->in.special_events; se; se = se->next) {
+        if (se->extension == extension &&
+            se->eid == eid) {
+            pthread_mutex_unlock(&c->iolock);
+            return NULL;
+        }
+    }
+    se = calloc(1, sizeof(xcb_special_event_t));
+    if (!se) {
+        pthread_mutex_unlock(&c->iolock);
+        return NULL;
+    }
+            
+    se->extension = extension;
+    se->eid = eid;
+
+    se->events = NULL;
+    se->events_tail = &se->events;
+    se->stamp = stamp;
+
+    pthread_cond_init(&se->special_event_cond, 0);
+
+    se->next = c->in.special_events;
+    c->in.special_events = se;
+
+    pthread_mutex_unlock(&c->iolock);
+    return se;
+}
+
+void
+xcb_unregister_for_special_event(xcb_connection_t *c,
+                                 xcb_special_event_t *se)
+{
+    xcb_special_event_t *s, **prev;
+    struct event_list   *events, *next;
+
+    if (c->has_error)
+        return;
+
+    pthread_mutex_lock(&c->iolock);
+
+    for (prev = &c->in.special_events; (s = *prev) != NULL; prev = &(s->next)) {
+        if (s == se) {
+            *prev = se->next;
+            for (events = se->events; events; events = next) {
+                next = events->next;
+                free (events->event);
+                free (events);
+            }
+            pthread_cond_destroy(&se->special_event_cond);
+            free (se);
+            break;
+        }
+    }
+    pthread_mutex_unlock(&c->iolock);
+}
+
 /* Private interface */
 
 int _xcb_in_init(_xcb_in *in)
@@ -665,11 +815,79 @@ void _xcb_in_replies_done(xcb_connection_t *c)
 
 int _xcb_in_read(xcb_connection_t *c)
 {
-    int n = recv(c->fd, c->in.queue + c->in.queue_len, sizeof(c->in.queue) - c->in.queue_len, 0);
-    if(n > 0)
+    int n;
+
+#if HAVE_SENDMSG
+    struct iovec    iov = {
+        .iov_base = c->in.queue + c->in.queue_len,
+        .iov_len = sizeof(c->in.queue) - c->in.queue_len,
+    };
+    struct {
+        struct cmsghdr  cmsghdr;
+        int fd[XCB_MAX_PASS_FD];
+    } fds;
+    struct msghdr msg = {
+        .msg_name = NULL,
+        .msg_namelen = 0,
+        .msg_iov = &iov,
+        .msg_iovlen = 1,
+        .msg_control = &fds,
+        .msg_controllen = sizeof (struct cmsghdr) + sizeof(int) * (XCB_MAX_PASS_FD - c->in.in_fd.nfd),
+    };
+    n = recvmsg(c->fd, &msg, 0);
+
+    /* Check for truncation errors. Only MSG_CTRUNC is
+     * probably possible here, which would indicate that
+     * the sender tried to transmit more than XCB_MAX_PASS_FD
+     * file descriptors.
+     */
+    if (msg.msg_flags & (MSG_TRUNC|MSG_CTRUNC)) {
+        _xcb_conn_shutdown(c, XCB_CONN_CLOSED_FDPASSING_FAILED);
+        return 0;
+    }
+#else
+    n = recv(c->fd, c->in.queue + c->in.queue_len, sizeof(c->in.queue) - c->in.queue_len, 0);
+#endif
+    if(n > 0) {
+#if HAVE_SENDMSG
+        if (msg.msg_controllen > sizeof (struct cmsghdr))
+        {
+            if (fds.cmsghdr.cmsg_level == SOL_SOCKET &&
+                fds.cmsghdr.cmsg_type == SCM_RIGHTS)
+            {
+                int nfd = (msg.msg_controllen - sizeof (struct cmsghdr)) / sizeof (int);
+                memmove(&c->in.in_fd.fd[c->in.in_fd.nfd],
+                        fds.fd,
+                        nfd);
+                c->in.in_fd.nfd += nfd;
+            }
+        }
+#endif
         c->in.queue_len += n;
+    }
     while(read_packet(c))
         /* empty */;
+#if HAVE_SENDMSG
+    if (c->in.in_fd.nfd) {
+        c->in.in_fd.nfd -= c->in.in_fd.ifd;
+        memmove(&c->in.in_fd.fd[0],
+                &c->in.in_fd.fd[c->in.in_fd.ifd],
+                c->in.in_fd.nfd * sizeof (int));
+        c->in.in_fd.ifd = 0;
+
+        /* If we have any left-over file descriptors after emptying
+         * the input buffer, then the server sent some that we weren't
+         * expecting.  Close them and mark the connection as broken;
+         */
+        if (c->in.queue_len == 0 && c->in.in_fd.nfd != 0) {
+            int i;
+            for (i = 0; i < c->in.in_fd.nfd; i++)
+                close(c->in.in_fd.fd[i]);
+            _xcb_conn_shutdown(c, XCB_CONN_CLOSED_FDPASSING_FAILED);
+            return 0;
+        }
+    }
+#endif
 #ifndef _WIN32
     if((n > 0) || (n < 0 && errno == EAGAIN))
 #else
index 44030c3..1eb1be7 100644 (file)
@@ -54,7 +54,8 @@ typedef struct {
 enum xcb_send_request_flags_t {
     XCB_REQUEST_CHECKED = 1 << 0,
     XCB_REQUEST_RAW = 1 << 1,
-    XCB_REQUEST_DISCARD_REPLY = 1 << 2
+    XCB_REQUEST_DISCARD_REPLY = 1 << 2,
+    XCB_REQUEST_REPLY_FDS = 1 << 3
 };
 
 unsigned int xcb_send_request(xcb_connection_t *c, int flags, struct iovec *vector, const xcb_protocol_request_t *request);
@@ -91,6 +92,7 @@ int xcb_writev(xcb_connection_t *c, struct iovec *vector, int count, uint64_t re
 
 void *xcb_wait_for_reply(xcb_connection_t *c, unsigned int request, xcb_generic_error_t **e);
 int xcb_poll_for_reply(xcb_connection_t *c, unsigned int request, void **reply, xcb_generic_error_t **error);
+int *xcb_get_reply_fds(xcb_connection_t *c, void *reply, size_t replylen);
 
 
 /* xcb_util.c */
index 391a4e1..4a01f6f 100644 (file)
@@ -92,6 +92,7 @@ typedef struct _xcb_fd {
     struct cmsghdr cmsghdr;
     int fd[XCB_MAX_PASS_FD];
     int nfd;
+    int ifd;
 } _xcb_fd;
 #endif
 
@@ -150,6 +151,9 @@ typedef struct _xcb_in {
 
     struct pending_reply *pending_replies;
     struct pending_reply **pending_replies_tail;
+#if HAVE_SENDMSG
+    _xcb_fd in_fd;
+#endif
 } _xcb_in;
 
 int _xcb_in_init(_xcb_in *in);