// Copyright 2020-2021, Collabora, Ltd. // SPDX-License-Identifier: BSL-1.0 /*! * @file * @brief Server mainloop details on Linux. * @author Pete Black * @author Jakob Bornecrantz * @author Ryan Pavlik * @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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef XRT_HAVE_SYSTEMD #include #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? }