| // Copyright 2017 The Chromium OS Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <stdint.h> |
| #include <string.h> // memcpy |
| #include <sys/errno.h> |
| #include <sys/socket.h> // CMSG_* |
| |
| /* |
| * Returns the number of bytes the `cmsg_buffer` must be for the functions that take a cmsg_buffer |
| * in this module. |
| * Arguments: |
| * * `fd_count` - Maximum number of file descriptors to be sent or received via the cmsg. |
| */ |
| size_t scm_cmsg_buffer_len(size_t fd_count) |
| { |
| return CMSG_SPACE(sizeof(int) * fd_count); |
| } |
| |
| /* |
| * Convenience wrapper around `sendmsg` that builds up the `msghdr` structure for you given the |
| * array of fds. |
| * Arguments: |
| * * `fd` - Unix domain socket to `sendmsg` on. |
| * * `outv` - Array of `outv_count` length `iovec`s that contain the data to send. |
| * * `outv_count` - Number of elements in `outv` array. |
| * * `cmsg_buffer` - A buffer that must be at least `scm_cmsg_buffer_len(fd_count)` bytes long. |
| * * `fds` - Array of `fd_count` file descriptors to send along with data. |
| * * `fd_count` - Number of elements in `fds` array. |
| * Returns: |
| * A non-negative number indicating how many bytes were sent on success or a negative errno on |
| * failure. |
| */ |
| ssize_t scm_sendmsg(int fd, const struct iovec *outv, size_t outv_count, uint8_t *cmsg_buffer, |
| const int *fds, size_t fd_count) |
| { |
| if (fd < 0 || ((!cmsg_buffer || !fds) && fd_count > 0)) |
| return -EINVAL; |
| |
| struct msghdr msg = {0}; |
| msg.msg_iov = (struct iovec *)outv; // discard const, sendmsg won't mutate it |
| msg.msg_iovlen = outv_count; |
| |
| if (fd_count) { |
| msg.msg_control = cmsg_buffer; |
| msg.msg_controllen = scm_cmsg_buffer_len(fd_count); |
| |
| struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg); |
| cmsg->cmsg_level = SOL_SOCKET; |
| cmsg->cmsg_type = SCM_RIGHTS; |
| cmsg->cmsg_len = CMSG_LEN(fd_count * sizeof(int)); |
| memcpy(CMSG_DATA(cmsg), fds, fd_count * sizeof(int)); |
| |
| msg.msg_controllen = cmsg->cmsg_len; |
| } |
| |
| ssize_t bytes_sent = sendmsg(fd, &msg, MSG_NOSIGNAL); |
| if (bytes_sent == -1) |
| return -errno; |
| |
| return bytes_sent; |
| } |
| |
| /* |
| * Convenience wrapper around `recvmsg` that builds up the `msghdr` structure and returns up to |
| * `*fd_count` file descriptors in the given `fds` array. |
| * Arguments: |
| * * `fd` - Unix domain socket to `recvmsg` on. |
| * * `outv` - Array of `outv_count` length `iovec`s that will contain the received data. |
| * * `outv_count` - Number of elements in `outv` array. |
| * * `cmsg_buffer` - A buffer that must be at least `scm_cmsg_buffer_len(*fd_count)` bytes long. |
| * * `fds` - Array of `fd_count` file descriptors to receive along with data. |
| * * `fd_count` - Number of elements in `fds` array. |
| * Returns: |
| * A non-negative number indicating how many bytes were received on success or a negative errno on |
| * failure. |
| */ |
| ssize_t scm_recvmsg(int fd, struct iovec *outv, size_t outv_count, uint8_t *cmsg_buffer, int *fds, |
| size_t *fd_count) |
| { |
| if (fd < 0 || !cmsg_buffer || !fds || !fd_count) |
| return -EINVAL; |
| |
| struct msghdr msg = {0}; |
| msg.msg_iov = outv; |
| msg.msg_iovlen = outv_count; |
| msg.msg_control = cmsg_buffer; |
| msg.msg_controllen = scm_cmsg_buffer_len(*fd_count); |
| |
| ssize_t total_read = recvmsg(fd, &msg, 0); |
| if (total_read == -1) |
| return -errno; |
| |
| if (total_read == 0 && CMSG_FIRSTHDR(&msg) == NULL) { |
| *fd_count = 0; |
| return 0; |
| } |
| |
| size_t fd_idx = 0; |
| struct cmsghdr *cmsg; |
| for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL; cmsg = CMSG_NXTHDR(&msg, cmsg)) { |
| if (cmsg->cmsg_level != SOL_SOCKET || cmsg->cmsg_type != SCM_RIGHTS) |
| continue; |
| |
| size_t cmsg_fd_count = (cmsg->cmsg_len - CMSG_LEN(0)) / sizeof(int); |
| |
| int *cmsg_fds = (int *)CMSG_DATA(cmsg); |
| size_t cmsg_fd_idx; |
| for (cmsg_fd_idx = 0; cmsg_fd_idx < cmsg_fd_count; cmsg_fd_idx++) { |
| if (fd_idx < *fd_count) { |
| fds[fd_idx] = cmsg_fds[cmsg_fd_idx]; |
| fd_idx++; |
| } else { |
| close(cmsg_fds[cmsg_fd_idx]); |
| } |
| } |
| } |
| |
| *fd_count = fd_idx; |
| |
| return total_read; |
| } |