The ipc_unixsocket.h and process.h internal headers don't need to include event_notifier.h, the former because a forward declaration suffices, and the latter because it doesn't use event notifiers. Remove the unnecessary include, and include signal.h instead which is required and was included indirectly through event_notifier.h. Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com> Reviewed-by: Niklas Söderlund <niklas.soderlund@ragnatech.se> Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
348 lines
8.4 KiB
C++
348 lines
8.4 KiB
C++
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
|
/*
|
|
* Copyright (C) 2019, Google Inc.
|
|
*
|
|
* ipc_unixsocket.cpp - IPC mechanism based on Unix sockets
|
|
*/
|
|
|
|
#include "libcamera/internal/ipc_unixsocket.h"
|
|
|
|
#include <poll.h>
|
|
#include <string.h>
|
|
#include <sys/socket.h>
|
|
#include <unistd.h>
|
|
|
|
#include <libcamera/event_notifier.h>
|
|
|
|
#include "libcamera/internal/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([[maybe_unused]] 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 */
|