monado/src/xrt/ipc/server/ipc_server_mainloop_linux.c

262 lines
5.2 KiB
C

// Copyright 2020-2021, Collabora, Ltd.
// SPDX-License-Identifier: BSL-1.0
/*!
* @file
* @brief Server mainloop details on Linux.
* @author Pete Black <pblack@collabora.com>
* @author Jakob Bornecrantz <jakob@collabora.com>
* @author Ryan Pavlik <ryan.pavlik@collabora.com>
* @ingroup ipc_server
*/
#include "xrt/xrt_device.h"
#include "xrt/xrt_instance.h"
#include "xrt/xrt_compositor.h"
#include "xrt/xrt_config_have.h"
#include "xrt/xrt_config_os.h"
#include "os/os_time.h"
#include "util/u_var.h"
#include "util/u_misc.h"
#include "util/u_debug.h"
#include "shared/ipc_shmem.h"
#include "server/ipc_server.h"
#include <stdlib.h>
#include <unistd.h>
#include <inttypes.h>
#include <stdbool.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/epoll.h>
#include <fcntl.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <assert.h>
#ifdef XRT_HAVE_SYSTEMD
#include <systemd/sd-daemon.h>
#endif
/*
*
* Static functions.
*
*/
static int
get_systemd_socket(struct ipc_server_mainloop *ml, int *out_fd)
{
#ifdef XRT_HAVE_SYSTEMD
// We may have been launched with socket activation
int num_fds = sd_listen_fds(0);
if (num_fds > 1) {
U_LOG_E("Too many file descriptors passed by systemd.");
return -1;
}
if (num_fds == 1) {
*out_fd = SD_LISTEN_FDS_START + 0;
ml->launched_by_socket = true;
U_LOG_D("Got existing socket from systemd.");
}
#endif
return 0;
}
static int
create_listen_socket(struct ipc_server_mainloop *ml, int *out_fd)
{
// no fd provided
struct sockaddr_un addr;
int fd, ret;
fd = socket(PF_UNIX, SOCK_STREAM, 0);
if (fd < 0) {
U_LOG_E("Message Socket Create Error!");
return fd;
}
memset(&addr, 0, sizeof(addr));
addr.sun_family = AF_UNIX;
strcpy(addr.sun_path, IPC_MSG_SOCK_FILE);
ret = bind(fd, (struct sockaddr *)&addr, sizeof(addr));
if (ret < 0) {
U_LOG_E(
"Could not bind socket to path %s: is the "
"service running already?",
IPC_MSG_SOCK_FILE);
#ifdef XRT_HAVE_SYSTEMD
U_LOG_E(
"Or, is the systemd unit monado.socket or "
"monado-dev.socket active?");
#endif
close(fd);
return ret;
}
// Save for later
ml->socket_filename = strdup(IPC_MSG_SOCK_FILE);
ret = listen(fd, IPC_MAX_CLIENTS);
if (ret < 0) {
close(fd);
return ret;
}
U_LOG_D("Created listening socket.");
*out_fd = fd;
return 0;
}
static int
init_listen_socket(struct ipc_server_mainloop *ml)
{
int fd = -1, ret;
ml->listen_socket = -1;
ret = get_systemd_socket(ml, &fd);
if (ret < 0) {
return ret;
}
if (fd == -1) {
ret = create_listen_socket(ml, &fd);
if (ret < 0) {
return ret;
}
}
// All ok!
ml->listen_socket = fd;
U_LOG_D("Listening socket is fd %d", ml->listen_socket);
return fd;
}
static int
init_epoll(struct ipc_server_mainloop *ml)
{
int ret = epoll_create1(EPOLL_CLOEXEC);
if (ret < 0) {
return ret;
}
ml->epoll_fd = ret;
struct epoll_event ev = {0};
if (!ml->launched_by_socket) {
// Can't do this when launched by systemd socket activation by
// default.
// This polls stdin.
ev.events = EPOLLIN;
ev.data.fd = 0; // stdin
ret = epoll_ctl(ml->epoll_fd, EPOLL_CTL_ADD, 0, &ev);
if (ret < 0) {
U_LOG_E("epoll_ctl(stdin) failed '%i'", ret);
return ret;
}
}
ev.events = EPOLLIN;
ev.data.fd = ml->listen_socket;
ret = epoll_ctl(ml->epoll_fd, EPOLL_CTL_ADD, ml->listen_socket, &ev);
if (ret < 0) {
U_LOG_E("epoll_ctl(listen_socket) failed '%i'", ret);
return ret;
}
return 0;
}
static void
handle_listen(struct ipc_server *vs, struct ipc_server_mainloop *ml)
{
int ret = accept(ml->listen_socket, NULL, NULL);
if (ret < 0) {
U_LOG_E("accept '%i'", ret);
ipc_server_handle_failure(vs);
return;
}
ipc_server_start_client_listener_thread(vs, ret);
}
#define NUM_POLL_EVENTS 8
#define NO_SLEEP 0
/*
*
* Exported functions
*
*/
void
ipc_server_mainloop_poll(struct ipc_server *vs, struct ipc_server_mainloop *ml)
{
int epoll_fd = ml->epoll_fd;
struct epoll_event events[NUM_POLL_EVENTS] = {0};
// No sleeping, returns immediately.
int ret = epoll_wait(epoll_fd, events, NUM_POLL_EVENTS, NO_SLEEP);
if (ret < 0) {
U_LOG_E("epoll_wait failed with '%i'.", ret);
ipc_server_handle_failure(vs);
return;
}
for (int i = 0; i < ret; i++) {
// If we get data on stdin, stop.
if (events[i].data.fd == 0) {
ipc_server_handle_shutdown_signal(vs);
return;
}
// Somebody new at the door.
if (events[i].data.fd == ml->listen_socket) {
handle_listen(vs, ml);
}
}
}
int
ipc_server_mainloop_init(struct ipc_server_mainloop *ml)
{
int ret = init_listen_socket(ml);
if (ret < 0) {
ipc_server_mainloop_deinit(ml);
return ret;
}
ret = init_epoll(ml);
if (ret < 0) {
ipc_server_mainloop_deinit(ml);
return ret;
}
return 0;
}
void
ipc_server_mainloop_deinit(struct ipc_server_mainloop *ml)
{
if (ml == NULL) {
return;
}
if (ml->listen_socket > 0) {
// Close socket on exit
close(ml->listen_socket);
ml->listen_socket = -1;
if (!ml->launched_by_socket && ml->socket_filename) {
// Unlink it too, but only if we bound it.
unlink(ml->socket_filename);
free(ml->socket_filename);
ml->socket_filename = NULL;
}
}
//! @todo close epoll_fd?
}