unix: add non block send and receive flavors for fd passing
authorJonathan Rajotte <jonathan.rajotte-julien@efficios.com>
Fri, 10 Jul 2020 12:46:04 +0000 (08:46 -0400)
committerJérémie Galarneau <jeremie.galarneau@efficios.com>
Mon, 27 Jul 2020 20:29:49 +0000 (16:29 -0400)
These will be used by the notification subsystem.

It is important to note that based on our current knowledge the sending
/receiving of fds is an all or nothing scenario. The fds are actually
part of the control message instead of the `payload`. On the receiving
side, a reception of N fds will only yield a "read" count of 1 bytes on
reception.

Albeit we don't have to account for the partial send/receive, we have to
manage the EAGAIN/EWOULDBLOCK scenario off non-blocking socket.

The caller of these function must handle the following scenario:

  ret < 0 -> error
  ret == 0 (Nothing received/sent)
  ret > 0 -> success

Signed-off-by: Jonathan Rajotte <jonathan.rajotte-julien@efficios.com>
Signed-off-by: Jérémie Galarneau <jeremie.galarneau@efficios.com>
Change-Id: Iabf8de876991c9f1c5b46ea609f9af961c4a7ab9

src/common/unix.c
src/common/unix.h

index df92b7a0698ef699ed31a5bbc82b6741362cedfb..6cdc759965ccacd7ef515ba3f4cc5fe87386d9e0 100644 (file)
@@ -436,6 +436,85 @@ ssize_t lttcomm_send_fds_unix_sock(int sock, const int *fds, size_t nb_fd)
        return ret;
 }
 
+/*
+ * Send a message accompanied by fd(s) over a unix socket.
+ * Only use for non blocking socket.
+ *
+ * Returns the size of data sent, or negative error value.
+ */
+LTTNG_HIDDEN
+ssize_t lttcomm_send_fds_unix_sock_non_block(int sock, const int *fds, size_t nb_fd)
+{
+       struct msghdr msg;
+       struct cmsghdr *cmptr;
+       struct iovec iov[1];
+       ssize_t ret = -1;
+       unsigned int sizeof_fds = nb_fd * sizeof(int);
+       char tmp[CMSG_SPACE(sizeof_fds)];
+       char dummy = 0;
+
+       memset(&msg, 0, sizeof(msg));
+       memset(tmp, 0, sizeof(tmp));
+
+       if (nb_fd > LTTCOMM_MAX_SEND_FDS)
+               return -EINVAL;
+
+       msg.msg_control = (caddr_t)tmp;
+       msg.msg_controllen = CMSG_LEN(sizeof_fds);
+
+       cmptr = CMSG_FIRSTHDR(&msg);
+       if (!cmptr) {
+               return -1;
+       }
+
+       cmptr->cmsg_level = SOL_SOCKET;
+       cmptr->cmsg_type = SCM_RIGHTS;
+       cmptr->cmsg_len = CMSG_LEN(sizeof_fds);
+       memcpy(CMSG_DATA(cmptr), fds, sizeof_fds);
+       /* Sum of the length of all control messages in the buffer: */
+       msg.msg_controllen = cmptr->cmsg_len;
+
+       iov[0].iov_base = &dummy;
+       iov[0].iov_len = 1;
+       msg.msg_iov = iov;
+       msg.msg_iovlen = 1;
+
+retry:
+       ret = sendmsg(sock, &msg, 0);
+       if (ret < 0) {
+               if (errno == EINTR) {
+                       goto retry;
+               } else {
+                       /*
+                        * We consider EPIPE and EAGAIN/EWOULDBLOCK as expected.
+                        */
+                       if (errno == EAGAIN || errno == EWOULDBLOCK) {
+                               /*
+                                * This can happen in non blocking mode.
+                                * Nothing was sent.
+                                */
+                               ret = 0;
+                               goto end;
+                       }
+
+                       if (errno == EPIPE) {
+                               /* Expected error, pass error to caller */
+                               DBG3("EPIPE on sendmsg");
+                               ret = -1;
+                               goto end;
+                       }
+
+                       /* Unexpected error */
+                       PERROR("sendmsg");
+                       ret = -1;
+                       goto end;
+               }
+       }
+
+end:
+       return ret;
+}
+
 /*
  * Recv a message accompanied by fd(s) from a unix socket.
  *
@@ -480,14 +559,157 @@ ssize_t lttcomm_recv_fds_unix_sock(int sock, int *fds, size_t nb_fd)
        msg.msg_controllen = CMSG_LEN(sizeof(recv_buf));
        msg.msg_flags = 0;
 
-       do {
-               ret = recvmsg(sock, &msg, 0);
-       } while (ret < 0 && errno == EINTR);
+retry:
+       ret = lttng_recvmsg_nosigpipe(sock, &msg);
        if (ret < 0) {
-               PERROR("recvmsg fds");
+               if (errno == EINTR) {
+                       goto retry;
+               } else {
+                       /* We consider EPIPE and EAGAIN as expected. */
+                       if (!lttng_opt_quiet &&
+                                       (errno != EPIPE && errno != EAGAIN)) {
+                               PERROR("recvmsg");
+                       }
+                       goto end;
+               }
+       }
+
+       if (ret != 1) {
+               fprintf(stderr, "Error: Received %zd bytes, expected %d\n",
+                               ret, 1);
+               goto end;
+       }
+
+       if (msg.msg_flags & MSG_CTRUNC) {
+               fprintf(stderr, "Error: Control message truncated.\n");
+               ret = -1;
                goto end;
        }
 
+       /*
+        * If the socket was configured with SO_PASSCRED, the kernel will add a
+        * control message (cmsg) to the ancillary data of the unix socket. We
+        * need to expect a cmsg of the SCM_CREDENTIALS as the first control
+        * message.
+        */
+       for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL; cmsg = CMSG_NXTHDR(&msg, cmsg)) {
+               if (cmsg->cmsg_level != SOL_SOCKET) {
+                       fprintf(stderr, "Error: The socket needs to be of type SOL_SOCKET\n");
+                       ret = -1;
+                       goto end;
+               }
+               if (cmsg->cmsg_type == SCM_RIGHTS) {
+                       /*
+                        * We found the controle message for file descriptors,
+                        * now copy the fds to the fds ptr and return success.
+                        */
+                       if (cmsg->cmsg_len != CMSG_LEN(sizeof_fds)) {
+                               fprintf(stderr, "Error: Received %zu bytes of"
+                                       "ancillary data for FDs, expected %zu\n",
+                                       (size_t) cmsg->cmsg_len,
+                                       (size_t) CMSG_LEN(sizeof_fds));
+                               ret = -1;
+                               goto end;
+                       }
+                       memcpy(fds, CMSG_DATA(cmsg), sizeof_fds);
+                       ret = sizeof_fds;
+                       goto end;
+               }
+#ifdef __linux__
+               if (cmsg->cmsg_type == SCM_CREDENTIALS) {
+                       /*
+                        * Expect credentials to be sent when expecting fds even
+                        * if no credential were include in the send(). The
+                        * kernel adds them...
+                        */
+                       ret = -1;
+               }
+#endif /* __linux__ */
+       }
+end:
+       return ret;
+}
+
+/*
+ * Recv a message accompanied by fd(s) from a non-blocking unix socket.
+ * Only use with non-blocking sockets.
+ *
+ * Returns the size of received data, or negative error value.
+ *
+ * Expect at most "nb_fd" file descriptors.
+ *
+ * Note that based on our comprehension, partial reception of fds is not
+ * possible since the FDs are actually in the control message. It is all or
+ * nothing, still the sender side can send the wrong number of fds.
+ */
+LTTNG_HIDDEN
+ssize_t lttcomm_recv_fds_unix_sock_non_block(int sock, int *fds, size_t nb_fd)
+{
+       struct iovec iov[1];
+       ssize_t ret = 0;
+       struct cmsghdr *cmsg;
+       size_t sizeof_fds = nb_fd * sizeof(int);
+
+#ifdef __linux__
+/* Account for the struct ucred cmsg in the buffer size */
+#define LTTNG_SOCK_RECV_FDS_BUF_SIZE CMSG_SPACE(sizeof_fds) + CMSG_SPACE(sizeof(struct ucred))
+#else
+#define LTTNG_SOCK_RECV_FDS_BUF_SIZE CMSG_SPACE(sizeof_fds)
+#endif /* __linux__ */
+
+       char recv_buf[LTTNG_SOCK_RECV_FDS_BUF_SIZE];
+       struct msghdr msg;
+       char dummy;
+
+       memset(&msg, 0, sizeof(msg));
+
+       /* Prepare to receive the structures */
+       iov[0].iov_base = &dummy;
+       iov[0].iov_len = 1;
+       msg.msg_iov = iov;
+       msg.msg_iovlen = 1;
+
+       cmsg = (struct cmsghdr *) recv_buf;
+       cmsg->cmsg_len = CMSG_LEN(sizeof_fds);
+       cmsg->cmsg_level = SOL_SOCKET;
+       cmsg->cmsg_type = SCM_RIGHTS;
+
+       msg.msg_control = cmsg;
+       msg.msg_controllen = CMSG_LEN(sizeof(recv_buf));
+       msg.msg_flags = 0;
+
+retry:
+       ret = lttng_recvmsg_nosigpipe(sock, &msg);
+       if (ret < 0) {
+               if (errno == EINTR) {
+                       goto retry;
+               } else {
+                       /*
+                        * We consider EPIPE and EAGAIN/EWOULDBLOCK as expected.
+                        */
+                       if (errno == EAGAIN || errno == EWOULDBLOCK) {
+                               /*
+                                * This can happen in non blocking mode.
+                                * Nothing was recv.
+                                */
+                               ret = 0;
+                               goto end;
+                       }
+
+                       if (errno == EPIPE) {
+                               /* Expected error, pass error to caller */
+                               DBG3("EPIPE on recvmsg");
+                               ret = -1;
+                               goto end;
+                       }
+
+                       /* Unexpected error */
+                       PERROR("recvmsg");
+                       ret = -1;
+                       goto end;
+               }
+       }
+
        if (ret != 1) {
                fprintf(stderr, "Error: Received %zd bytes, expected %d\n",
                                ret, 1);
index 5505a4e70accd7b1449f8299ed9aa900a1e6f99d..0cce81b7fb1536c94ffd7dc95f7c08fcc4702a03 100644 (file)
@@ -30,9 +30,14 @@ int lttcomm_close_unix_sock(int sock);
 /* Send a message accompanied by fd(s) over a unix socket. */
 LTTNG_HIDDEN
 ssize_t lttcomm_send_fds_unix_sock(int sock, const int *fds, size_t nb_fd);
+LTTNG_HIDDEN
+ssize_t lttcomm_send_fds_unix_sock_non_block(
+               int sock, const int *fds, size_t nb_fd);
 /* Recv a message accompanied by fd(s) from a unix socket */
 LTTNG_HIDDEN
 ssize_t lttcomm_recv_fds_unix_sock(int sock, int *fds, size_t nb_fd);
+LTTNG_HIDDEN
+ssize_t lttcomm_recv_fds_unix_sock_non_block(int sock, int *fds, size_t nb_fd);
 
 LTTNG_HIDDEN
 ssize_t lttcomm_recv_unix_sock(int sock, void *buf, size_t len);
This page took 0.039143 seconds and 4 git commands to generate.