summaryrefslogtreecommitdiff
path: root/src/libcamera/ipc_unixsocket.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/libcamera/ipc_unixsocket.cpp')
-rw-r--r--src/libcamera/ipc_unixsocket.cpp309
1 files changed, 309 insertions, 0 deletions
diff --git a/src/libcamera/ipc_unixsocket.cpp b/src/libcamera/ipc_unixsocket.cpp
new file mode 100644
index 00000000..c11f1160
--- /dev/null
+++ b/src/libcamera/ipc_unixsocket.cpp
@@ -0,0 +1,309 @@
+/* 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 <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 a signal. The queuer 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.
+ */
+
+IPCUnixSocket::IPCUnixSocket()
+ : fd_(-1), 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, 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;
+}
+
+/**
+ * \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. It blocks until one message is received, if an
+ * asynchronous behavior is desired this method should be called when the
+ * readyRead signal is emitted.
+ *
+ * \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
+ */
+int IPCUnixSocket::receive(Payload *payload)
+{
+ Header hdr;
+ int ret;
+
+ if (!isBound())
+ return -ENOTCONN;
+
+ if (!payload)
+ return -EINVAL;
+
+ ret = ::recv(fd_, &hdr, sizeof(hdr), 0);
+ if (ret < 0) {
+ ret = -errno;
+ LOG(IPCUnixSocket, Error)
+ << "Failed to recv header: " << strerror(-ret);
+ return ret;
+ }
+
+ payload->data.resize(hdr.data);
+ payload->fds.resize(hdr.fds);
+
+ return recvData(payload->data.data(), hdr.data, payload->fds.data(), hdr.fds);
+}
+
+/**
+ * \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;
+ 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)
+{
+ readyRead.emit(this);
+}
+
+} /* namespace libcamera */