summaryrefslogtreecommitdiff
path: root/Documentation/meson.build
AgeCommit message (Expand)Author
2021-01-18libcamera: Document sensor driver requirementsJacopo Mondi
2020-12-27Documentation: Restore alphabetical order of sources in meson.buildLaurent Pinchart
2020-12-27Documentation: Add descriptions for environment variablesSebastian Fricke
2020-11-03Documentation: tracing: Add tracing guidePaul Elder
2020-09-24Documentation: Move all dependencies into featuresRicardo Ribalda
2020-09-24Documentation: Search for dot binaryRicardo Ribalda
2020-08-20Documentation: Guides: Pipeline Handler Writer's GuideChris Chinchilla
2020-08-20Documentation: Guides: Application Writer's GuideChris Chinchilla
2020-08-20Documentation: Guides: Developer's Guide to libcameraChris Chinchilla
2020-05-18meson: Rename variables storing headers listsLaurent Pinchart
2020-05-13licenses: License all meson files under CC0-1.0Laurent Pinchart
2020-01-31Documentation: Add linkcheck targetKieran Bingham
2019-11-20ipa: Switch to the plain C APIJacopo Mondi
2019-09-15libcamera: Move ipa includes to the same level as libcameraLaurent Pinchart
2019-08-16readme: Move index page content to READMEKieran Bingham
2019-07-09libcamera: Rework automatic version generation to avoid rebuildsLaurent Pinchart
2019-07-04Documentation: Wrap extended doc_install_dir lineKieran Bingham
2019-07-04libcamera: Auto generate version informationKieran Bingham
2019-05-23meson: Fix coding style in meson.build filesLaurent Pinchart
2019-04-28Documentation: Drop install directive for DoxyfileNiklas Söderlund
2019-01-14Documentation: quieten sphinx-build outputKieran Bingham
2018-12-14Documentation: Add architecture documentationLaurent Pinchart
2018-12-13Documentation: Add coding style documentJacopo Mondi
2018-12-12Documentation: Generate source code documentation using DoxygenLaurent Pinchart
2018-12-12Documentation: Don't hardcode install directoryLaurent Pinchart
2018-12-11Documentation: Set install_dir in custom_target()Laurent Pinchart
2018-11-28Documentation: Introduce sphinx documentationKieran Bingham
>334 335 336 337 338 339 340 341 342 343
/* 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.
 */

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 */