| /* SPDX-License-Identifier: LGPL-2.1-or-later */ |
| /* |
| * Copyright (C) 2019, Google Inc. |
| * |
| * ipc_unixsocket.cpp - IPC mechanism based on Unix sockets |
| */ |
| |
| #include "ipc_unixsocket.h" |
| |
| #include <poll.h> |
| #include <string.h> |
| #include <sys/socket.h> |
| #include <unistd.h> |
| |
| #include "log.h" |
| |
| /** |
| * \file ipc_unixsocket.h |
| * \brief IPC mechanism based on Unix sockets |
| */ |
| |
| namespace libcamera { |
| |
| LOG_DEFINE_CATEGORY(IPCUnixSocket) |
| |
| /** |
| * \struct IPCUnixSocket::Payload |
| * \brief Container for an IPC payload |
| * |
| * Holds an array of bytes and an array of file descriptors that can be |
| * transported across a IPC boundary. |
| */ |
| |
| /** |
| * \var IPCUnixSocket::Payload::data |
| * \brief Array of bytes to cross IPC boundary |
| */ |
| |
| /** |
| * \var IPCUnixSocket::Payload::fds |
| * \brief Array of file descriptors to cross IPC boundary |
| */ |
| |
| /** |
| * \class IPCUnixSocket |
| * \brief IPC mechanism based on Unix sockets |
| * |
| * The Unix socket IPC allows bidirectional communication between two processes |
| * through unnamed Unix sockets. It implements datagram-based communication, |
| * transporting entire payloads with guaranteed ordering. |
| * |
| * The IPC design is asynchronous, a message is queued to a receiver which gets |
| * notified that a message is ready to be consumed by the \ref readyRead |
| * signal. The sender of the message gets no notification when a message is |
| * delivered nor processed. If such interactions are needed a protocol specific |
| * to the users use-case should be implemented on top of the IPC objects. |
| * |
| * Establishment of an IPC channel is asymmetrical. The side that initiates |
| * communication first instantiates a local side socket and creates the channel |
| * with create(). The method returns a file descriptor for the remote side of |
| * the channel, which is passed to the remote process through an out-of-band |
| * communication method. The remote side then instantiates a socket, and binds |
| * it to the other side by passing the file descriptor to bind(). At that point |
| * the channel is operation and communication is bidirectional and symmmetrical. |
| * |
| * \context This class is \threadbound. |
| */ |
| |
| IPCUnixSocket::IPCUnixSocket() |
| : fd_(-1), headerReceived_(false), notifier_(nullptr) |
| { |
| } |
| |
| IPCUnixSocket::~IPCUnixSocket() |
| { |
| close(); |
| } |
| |
| /** |
| * \brief Create an new IPC channel |
| * |
| * This method creates a new IPC channel. The socket instance is bound to the |
| * local side of the channel, and the method returns a file descriptor bound to |
| * the remote side. The caller is responsible for passing the file descriptor to |
| * the remote process, where it can be used with IPCUnixSocket::bind() to bind |
| * the remote side socket. |
| * |
| * \return A file descriptor on success, negative error code on failure |
| */ |
| int IPCUnixSocket::create() |
| { |
| int sockets[2]; |
| int ret; |
| |
| ret = socketpair(AF_UNIX, SOCK_DGRAM | SOCK_NONBLOCK, 0, sockets); |
| if (ret) { |
| ret = -errno; |
| LOG(IPCUnixSocket, Error) |
| << "Failed to create socket pair: " << strerror(-ret); |
| return ret; |
| } |
| |
| ret = bind(sockets[0]); |
| if (ret) |
| return ret; |
| |
| return sockets[1]; |
| } |
| |
| /** |
| * \brief Bind to an existing IPC channel |
| * \param[in] fd File descriptor |
| * |
| * This method binds the socket instance to an existing IPC channel identified |
| * by the file descriptor \a fd. The file descriptor is obtained from the |
| * IPCUnixSocket::create() method. |
| * |
| * \return 0 on success or a negative error code otherwise |
| */ |
| int IPCUnixSocket::bind(int fd) |
| { |
| if (isBound()) |
| return -EINVAL; |
| |
| fd_ = fd; |
| notifier_ = new EventNotifier(fd_, EventNotifier::Read); |
| notifier_->activated.connect(this, &IPCUnixSocket::dataNotifier); |
| |
| return 0; |
| } |
| |
| /** |
| * \brief Close the IPC channel |
| * |
| * No communication is possible after close() has been called. |
| */ |
| void IPCUnixSocket::close() |
| { |
| if (!isBound()) |
| return; |
| |
| delete notifier_; |
| notifier_ = nullptr; |
| |
| ::close(fd_); |
| |
| fd_ = -1; |
| headerReceived_ = false; |
| } |
| |
| /** |
| * \brief Check if the IPC channel is bound |
| * \return True if the IPC channel is bound, false otherwise |
| */ |
| bool IPCUnixSocket::isBound() const |
| { |
| return fd_ != -1; |
| } |
| |
| /** |
| * \brief Send a message payload |
| * \param[in] payload Message payload to send |
| * |
| * This method queues the message payload for transmission to the other end of |
| * the IPC channel. It returns immediately, before the message is delivered to |
| * the remote side. |
| * |
| * \return 0 on success or a negative error code otherwise |
| */ |
| int IPCUnixSocket::send(const Payload &payload) |
| { |
| int ret; |
| |
| if (!isBound()) |
| return -ENOTCONN; |
| |
| Header hdr = {}; |
| hdr.data = payload.data.size(); |
| hdr.fds = payload.fds.size(); |
| |
| if (!hdr.data && !hdr.fds) |
| return -EINVAL; |
| |
| ret = ::send(fd_, &hdr, sizeof(hdr), 0); |
| if (ret < 0) { |
| ret = -errno; |
| LOG(IPCUnixSocket, Error) |
| << "Failed to send: " << strerror(-ret); |
| return ret; |
| } |
| |
| return sendData(payload.data.data(), hdr.data, payload.fds.data(), hdr.fds); |
| } |
| |
| /** |
| * \brief Receive a message payload |
| * \param[out] payload Payload where to write the received message |
| * |
| * This method receives the message payload from the IPC channel and writes it |
| * to the \a payload. If no message payload is available, it returns |
| * immediately with -EAGAIN. The \ref readyRead signal shall be used to receive |
| * notification of message availability. |
| * |
| * \todo Add state machine to make sure we don't block forever and that |
| * a header is always followed by a payload. |
| * |
| * \return 0 on success or a negative error code otherwise |
| * \retval -EAGAIN No message payload is available |
| * \retval -ENOTCONN The socket is not connected (neither create() nor bind() |
| * has been called) |
| */ |
| int IPCUnixSocket::receive(Payload *payload) |
| { |
| if (!isBound()) |
| return -ENOTCONN; |
| |
| if (!headerReceived_) |
| return -EAGAIN; |
| |
| payload->data.resize(header_.data); |
| payload->fds.resize(header_.fds); |
| |
| int ret = recvData(payload->data.data(), header_.data, |
| payload->fds.data(), header_.fds); |
| if (ret < 0) |
| return ret; |
| |
| headerReceived_ = false; |
| notifier_->setEnabled(true); |
| |
| return 0; |
| } |
| |
| /** |
| * \var IPCUnixSocket::readyRead |
| * \brief A Signal emitted when a message is ready to be read |
| */ |
| |
| int IPCUnixSocket::sendData(const void *buffer, size_t length, |
| const int32_t *fds, unsigned int num) |
| { |
| struct iovec iov[1]; |
| iov[0].iov_base = const_cast<void *>(buffer); |
| iov[0].iov_len = length; |
| |
| char buf[CMSG_SPACE(num * sizeof(uint32_t))]; |
| memset(buf, 0, sizeof(buf)); |
| |
| struct cmsghdr *cmsg = (struct cmsghdr *)buf; |
| cmsg->cmsg_len = CMSG_LEN(num * sizeof(uint32_t)); |
| cmsg->cmsg_level = SOL_SOCKET; |
| cmsg->cmsg_type = SCM_RIGHTS; |
| |
| struct msghdr msg; |
| msg.msg_name = nullptr; |
| msg.msg_namelen = 0; |
| msg.msg_iov = iov; |
| msg.msg_iovlen = 1; |
| msg.msg_control = cmsg; |
| msg.msg_controllen = cmsg->cmsg_len; |
| msg.msg_flags = 0; |
| memcpy(CMSG_DATA(cmsg), fds, num * sizeof(uint32_t)); |
| |
| if (sendmsg(fd_, &msg, 0) < 0) { |
| int ret = -errno; |
| LOG(IPCUnixSocket, Error) |
| << "Failed to sendmsg: " << strerror(-ret); |
| return ret; |
| } |
| |
| return 0; |
| } |
| |
| int IPCUnixSocket::recvData(void *buffer, size_t length, |
| int32_t *fds, unsigned int num) |
| { |
| struct iovec iov[1]; |
| iov[0].iov_base = buffer; |
| iov[0].iov_len = length; |
| |
| char buf[CMSG_SPACE(num * sizeof(uint32_t))]; |
| memset(buf, 0, sizeof(buf)); |
| |
| struct cmsghdr *cmsg = (struct cmsghdr *)buf; |
| cmsg->cmsg_len = CMSG_LEN(num * sizeof(uint32_t)); |
| cmsg->cmsg_level = SOL_SOCKET; |
| cmsg->cmsg_type = SCM_RIGHTS; |
| |
| struct msghdr msg; |
| msg.msg_name = nullptr; |
| msg.msg_namelen = 0; |
| msg.msg_iov = iov; |
| msg.msg_iovlen = 1; |
| msg.msg_control = cmsg; |
| msg.msg_controllen = cmsg->cmsg_len; |
| msg.msg_flags = 0; |
| |
| if (recvmsg(fd_, &msg, 0) < 0) { |
| int ret = -errno; |
| if (ret != -EAGAIN) |
| LOG(IPCUnixSocket, Error) |
| << "Failed to recvmsg: " << strerror(-ret); |
| return ret; |
| } |
| |
| memcpy(fds, CMSG_DATA(cmsg), num * sizeof(uint32_t)); |
| |
| return 0; |
| } |
| |
| void IPCUnixSocket::dataNotifier(EventNotifier *notifier) |
| { |
| int ret; |
| |
| if (!headerReceived_) { |
| /* Receive the header. */ |
| ret = ::recv(fd_, &header_, sizeof(header_), 0); |
| if (ret < 0) { |
| ret = -errno; |
| LOG(IPCUnixSocket, Error) |
| << "Failed to receive header: " << strerror(-ret); |
| return; |
| } |
| |
| headerReceived_ = true; |
| } |
| |
| /* |
| * If the payload has arrived, disable the notifier and emit the |
| * readyRead signal. The notifier will be reenabled by the receive() |
| * method. |
| */ |
| struct pollfd fds = { fd_, POLLIN, 0 }; |
| ret = poll(&fds, 1, 0); |
| if (ret < 0) |
| return; |
| |
| if (!(fds.revents & POLLIN)) |
| return; |
| |
| notifier_->setEnabled(false); |
| readyRead.emit(this); |
| } |
| |
| } /* namespace libcamera */ |