The open source OpenXR runtime
1// Copyright 2020-2021, Collabora, Ltd.
2// SPDX-License-Identifier: BSL-1.0
3/*!
4 * @file
5 * @brief Server mainloop details on Linux.
6 * @author Pete Black <pblack@collabora.com>
7 * @author Jakob Bornecrantz <jakob@collabora.com>
8 * @author Rylie Pavlik <rylie.pavlik@collabora.com>
9 * @ingroup ipc_server
10 */
11
12#include "xrt/xrt_device.h"
13#include "xrt/xrt_instance.h"
14#include "xrt/xrt_compositor.h"
15#include "xrt/xrt_config_have.h"
16#include "xrt/xrt_config_os.h"
17
18#include "os/os_time.h"
19#include "util/u_var.h"
20#include "util/u_misc.h"
21#include "util/u_debug.h"
22#include "util/u_trace_marker.h"
23#include "util/u_file.h"
24
25#include "shared/ipc_shmem.h"
26#include "server/ipc_server.h"
27
28#include <stdlib.h>
29#include <unistd.h>
30#include <stdbool.h>
31#include <sys/types.h>
32#include <sys/stat.h>
33#include <sys/mman.h>
34#include <sys/socket.h>
35#include <sys/un.h>
36#include <sys/epoll.h>
37#include <fcntl.h>
38#include <errno.h>
39#include <stdio.h>
40#include <string.h>
41#include <assert.h>
42#include <limits.h>
43#include "util/u_debug.h"
44
45#ifdef XRT_HAVE_SYSTEMD
46#include <systemd/sd-daemon.h>
47#endif
48
49/*
50 * "XRT_NO_STDIN" option disables stdin and prevents monado-service from terminating.
51 * This could be useful for situations where there is no proper or in a non-interactive shell.
52 * Two example scenarios are:
53 * * IDE terminals,
54 * * Some scripting environments where monado-service is spawned in the background
55 */
56DEBUG_GET_ONCE_BOOL_OPTION(skip_stdin, "XRT_NO_STDIN", false)
57
58/*
59 *
60 * Static functions.
61 *
62 */
63static int
64get_systemd_socket(struct ipc_server_mainloop *ml, int *out_fd)
65{
66#ifdef XRT_HAVE_SYSTEMD
67 // We may have been launched with socket activation
68 int num_fds = sd_listen_fds(0);
69 if (num_fds > 1) {
70 U_LOG_E("Too many file descriptors passed by systemd.");
71 return -1;
72 }
73 if (num_fds == 1) {
74 *out_fd = SD_LISTEN_FDS_START + 0;
75 ml->launched_by_socket = true;
76 U_LOG_D("Got existing socket from systemd.");
77 }
78#endif
79 return 0;
80}
81
82static int
83create_listen_socket(struct ipc_server_mainloop *ml, int *out_fd)
84{
85 // no fd provided
86 struct sockaddr_un addr;
87 int fd;
88 int ret;
89
90 fd = socket(PF_UNIX, SOCK_STREAM, 0);
91 if (fd < 0) {
92 U_LOG_E("Message Socket Create Error!");
93 return fd;
94 }
95
96
97 char sock_file[PATH_MAX];
98
99 int size = u_file_get_path_in_runtime_dir(XRT_IPC_MSG_SOCK_FILENAME, sock_file, PATH_MAX);
100 if (size == -1) {
101 U_LOG_E("Could not get socket file name");
102 return -1;
103 }
104
105 memset(&addr, 0, sizeof(addr));
106
107 addr.sun_family = AF_UNIX;
108 strcpy(addr.sun_path, sock_file);
109
110 ret = bind(fd, (struct sockaddr *)&addr, sizeof(addr));
111
112#ifdef XRT_HAVE_LIBBSD
113 // no other instance is running, or we would have never arrived here
114 if (ret < 0 && errno == EADDRINUSE) {
115 U_LOG_W("Removing stale socket file %s", sock_file);
116
117 ret = unlink(sock_file);
118 if (ret < 0) {
119 U_LOG_E("Failed to remove stale socket file %s: %s", sock_file, strerror(errno));
120 return ret;
121 }
122 ret = bind(fd, (struct sockaddr *)&addr, sizeof(addr));
123 }
124#endif
125
126 if (ret < 0) {
127 U_LOG_E("Could not bind socket to path %s: %s. Is the service running already?", sock_file,
128 strerror(errno));
129#ifdef XRT_HAVE_SYSTEMD
130 U_LOG_E("Or, is the systemd unit monado.socket or monado-dev.socket active?");
131#endif
132 if (errno == EADDRINUSE) {
133 U_LOG_E("If monado-service is not running, delete %s before starting a new instance",
134 sock_file);
135 }
136 close(fd);
137 return ret;
138 }
139 // Save for later
140 ml->socket_filename = strdup(sock_file);
141
142 ret = listen(fd, IPC_MAX_CLIENTS);
143 if (ret < 0) {
144 close(fd);
145 return ret;
146 }
147 U_LOG_D("Created listening socket %s.", sock_file);
148 *out_fd = fd;
149 return 0;
150}
151
152static int
153init_listen_socket(struct ipc_server_mainloop *ml)
154{
155 int fd = -1;
156 int ret;
157 ml->listen_socket = -1;
158
159 ret = get_systemd_socket(ml, &fd);
160 if (ret < 0) {
161 return ret;
162 }
163
164 if (fd == -1) {
165 ret = create_listen_socket(ml, &fd);
166 if (ret < 0) {
167 return ret;
168 }
169 }
170 // All ok!
171 ml->listen_socket = fd;
172 U_LOG_D("Listening socket is fd %d", ml->listen_socket);
173
174 return fd;
175}
176
177static int
178init_epoll(struct ipc_server_mainloop *ml)
179{
180 int ret = epoll_create1(EPOLL_CLOEXEC);
181 if (ret < 0) {
182 return ret;
183 }
184
185 ml->epoll_fd = ret;
186
187 struct epoll_event ev = {0};
188
189 if (!ml->launched_by_socket && !debug_get_bool_option_skip_stdin()) {
190 // Can't do this when launched by systemd socket activation by
191 // default.
192 // This polls stdin.
193 ev.events = EPOLLIN;
194 ev.data.fd = 0; // stdin
195 ret = epoll_ctl(ml->epoll_fd, EPOLL_CTL_ADD, 0, &ev);
196 if (ret < 0) {
197 U_LOG_E("epoll_ctl(stdin) failed '%i'", ret);
198 return ret;
199 }
200 }
201
202 ev.events = EPOLLIN;
203 ev.data.fd = ml->listen_socket;
204 ret = epoll_ctl(ml->epoll_fd, EPOLL_CTL_ADD, ml->listen_socket, &ev);
205 if (ret < 0) {
206 U_LOG_E("epoll_ctl(listen_socket) failed '%i'", ret);
207 return ret;
208 }
209
210 return 0;
211}
212
213static void
214handle_listen(struct ipc_server *vs, struct ipc_server_mainloop *ml)
215{
216 int ret = accept(ml->listen_socket, NULL, NULL);
217 if (ret < 0) {
218 U_LOG_E("accept '%i'", ret);
219 ipc_server_handle_failure(vs);
220 return;
221 }
222
223 // Call into the generic client connected handling code.
224 ipc_server_handle_client_connected(vs, ret);
225}
226
227#define NUM_POLL_EVENTS 8
228#define NO_SLEEP 0
229
230/*
231 *
232 * Exported functions
233 *
234 */
235
236void
237ipc_server_mainloop_poll(struct ipc_server *vs, struct ipc_server_mainloop *ml)
238{
239 IPC_TRACE_MARKER();
240
241 int epoll_fd = ml->epoll_fd;
242
243 struct epoll_event events[NUM_POLL_EVENTS] = {0};
244
245 // No sleeping, returns immediately.
246 int ret = epoll_wait(epoll_fd, events, NUM_POLL_EVENTS, NO_SLEEP);
247 if (ret < 0) {
248 U_LOG_E("epoll_wait failed with '%i'.", ret);
249 ipc_server_handle_failure(vs);
250 return;
251 }
252
253 for (int i = 0; i < ret; i++) {
254 // If we get data on stdin, stop.
255 if (events[i].data.fd == 0) {
256 ipc_server_handle_shutdown_signal(vs);
257 return;
258 }
259
260 // Somebody new at the door.
261 if (events[i].data.fd == ml->listen_socket) {
262 handle_listen(vs, ml);
263 }
264 }
265}
266
267int
268ipc_server_mainloop_init(struct ipc_server_mainloop *ml)
269{
270 IPC_TRACE_MARKER();
271
272 int ret = init_listen_socket(ml);
273 if (ret < 0) {
274 ipc_server_mainloop_deinit(ml);
275 return ret;
276 }
277
278 ret = init_epoll(ml);
279 if (ret < 0) {
280 ipc_server_mainloop_deinit(ml);
281 return ret;
282 }
283 return 0;
284}
285
286void
287ipc_server_mainloop_deinit(struct ipc_server_mainloop *ml)
288{
289 IPC_TRACE_MARKER();
290
291 if (ml == NULL) {
292 return;
293 }
294 if (ml->listen_socket > 0) {
295 // Close socket on exit
296 close(ml->listen_socket);
297 ml->listen_socket = -1;
298 if (!ml->launched_by_socket && ml->socket_filename) {
299 // Unlink it too, but only if we bound it.
300 unlink(ml->socket_filename);
301 free(ml->socket_filename);
302 ml->socket_filename = NULL;
303 }
304 }
305 //! @todo close epoll_fd?
306}