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