From: Jonathan Rajotte Date: Fri, 10 Jul 2020 12:46:04 +0000 (-0400) Subject: unix: add non block send and receive flavors for fd passing X-Git-Tag: v2.13.0-rc1~579 X-Git-Url: https://git.lttng.org/?p=lttng-tools.git;a=commitdiff_plain;h=b72ce6309a63e5c2ec29042c4677ba559f21746b;ds=sidebyside unix: add non block send and receive flavors for fd passing 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 Signed-off-by: Jérémie Galarneau Change-Id: Iabf8de876991c9f1c5b46ea609f9af961c4a7ab9 --- diff --git a/src/common/unix.c b/src/common/unix.c index df92b7a06..6cdc75996 100644 --- a/src/common/unix.c +++ b/src/common/unix.c @@ -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); diff --git a/src/common/unix.h b/src/common/unix.h index 5505a4e70..0cce81b7f 100644 --- a/src/common/unix.h +++ b/src/common/unix.h @@ -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);