The open source OpenXR runtime
1// Copyright 2020-2024, Collabora, Ltd.
2// SPDX-License-Identifier: BSL-1.0
3/*!
4 * @file
5 * @brief Just the client connection setup/teardown bits.
6 * @author Jakob Bornecrantz <jakob@collabora.com>
7 * @author Rylie Pavlik <rylie.pavlik@collabora.com>
8 * @ingroup ipc_client
9 */
10
11#include "os/os_threading.h"
12#include "xrt/xrt_results.h"
13#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS)
14#define _CRT_SECURE_NO_WARNINGS
15#endif
16
17#include "xrt/xrt_instance.h"
18#include "xrt/xrt_handles.h"
19#include "xrt/xrt_config_os.h"
20#include "xrt/xrt_config_android.h"
21
22#include "util/u_var.h"
23#include "util/u_misc.h"
24#include "util/u_file.h"
25#include "util/u_debug.h"
26#include "util/u_git_tag.h"
27#include "util/u_system_helpers.h"
28
29#include "shared/ipc_utils.h"
30#include "shared/ipc_protocol.h"
31#include "client/ipc_client_connection.h"
32
33#include "ipc_client_generated.h"
34
35
36#include <stdio.h>
37#if !defined(XRT_OS_WINDOWS)
38#include <sys/socket.h>
39#include <sys/un.h>
40#include <sys/types.h>
41#include <sys/stat.h>
42#include <sys/mman.h>
43#include <errno.h>
44#include <fcntl.h>
45#include <unistd.h>
46#endif
47#include <limits.h>
48
49#ifdef XRT_GRAPHICS_BUFFER_HANDLE_IS_AHARDWAREBUFFER
50#include "android/android_ahardwarebuffer_allocator.h"
51#endif
52
53#ifdef XRT_OS_ANDROID
54#include "android/ipc_client_android.h"
55#endif // XRT_OS_ANDROID
56
57DEBUG_GET_ONCE_BOOL_OPTION(ipc_ignore_version, "IPC_IGNORE_VERSION", false)
58
59#ifdef XRT_OS_ANDROID
60
61static bool
62ipc_client_socket_connect(struct ipc_connection *ipc_c, struct _JavaVM *vm, void *context)
63{
64 ipc_c->ica = ipc_client_android_create(vm, context);
65
66 if (ipc_c->ica == NULL) {
67 IPC_ERROR(ipc_c, "Client create error!");
68 return false;
69 }
70
71 int socket = ipc_client_android_blocking_connect(ipc_c->ica);
72 if (socket < 0) {
73 IPC_ERROR(ipc_c, "Service Connect error!");
74 return false;
75 }
76 // The ownership belongs to the Java object. Dup because the fd will be
77 // closed when client destroy.
78 socket = dup(socket);
79 if (socket < 0) {
80 IPC_ERROR(ipc_c, "Failed to dup fd with error %d!", errno);
81 return false;
82 }
83
84 // Set socket.
85 ipc_c->imc.ipc_handle = socket;
86
87 return true;
88}
89
90#elif defined(XRT_OS_WINDOWS)
91
92#if defined(NO_XRT_SERVICE_LAUNCH) || !defined(XRT_SERVICE_EXECUTABLE)
93static HANDLE
94ipc_connect_pipe(struct ipc_connection *ipc_c, const char *pipe_name)
95{
96 HANDLE pipe_inst = CreateFileA(pipe_name, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
97 if (pipe_inst == INVALID_HANDLE_VALUE) {
98 DWORD err = GetLastError();
99 IPC_ERROR(ipc_c, "Connect to %s failed: %d %s", pipe_name, err, ipc_winerror(err));
100 }
101 return pipe_inst;
102}
103#else
104// N.B. quality of life fallback to try launch the XRT_SERVICE_EXECUTABLE if pipe is not found
105static HANDLE
106ipc_connect_pipe(struct ipc_connection *ipc_c, const char *pipe_name)
107{
108 HANDLE pipe_inst = CreateFileA(pipe_name, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
109 if (pipe_inst != INVALID_HANDLE_VALUE) {
110 return pipe_inst;
111 }
112 DWORD err = GetLastError();
113 IPC_ERROR(ipc_c, "Connect to %s failed: %d %s", pipe_name, err, ipc_winerror(err));
114 if (err != ERROR_FILE_NOT_FOUND) {
115 return INVALID_HANDLE_VALUE;
116 }
117 IPC_INFO(ipc_c, "Trying to launch " XRT_SERVICE_EXECUTABLE "...");
118 HMODULE hmod;
119 if (!GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
120 (LPCSTR)ipc_connect_pipe, &hmod)) {
121 IPC_ERROR(ipc_c, "GetModuleHandleExA failed: %d %s", err, ipc_winerror(err));
122 return INVALID_HANDLE_VALUE;
123 }
124 char service_path[MAX_PATH];
125 if (!GetModuleFileNameA(hmod, service_path, sizeof(service_path))) {
126 IPC_ERROR(ipc_c, "GetModuleFileNameA failed: %d %s", err, ipc_winerror(err));
127 return INVALID_HANDLE_VALUE;
128 }
129 char *p = strrchr(service_path, '\\');
130 if (!p) {
131 IPC_ERROR(ipc_c, "failed to parse the path %s", service_path);
132 return INVALID_HANDLE_VALUE;
133 }
134 strcpy(p + 1, XRT_SERVICE_EXECUTABLE);
135 STARTUPINFOA si = {.cb = sizeof(si)};
136 PROCESS_INFORMATION pi;
137 if (!CreateProcessA(NULL, service_path, NULL, NULL, false, 0, NULL, NULL, &si, &pi)) {
138 *p = 0;
139 p = strrchr(service_path, '\\');
140 if (!p) {
141 err = GetLastError();
142 IPC_INFO(ipc_c, XRT_SERVICE_EXECUTABLE " not found in %s: %d %s", service_path, err,
143 ipc_winerror(err));
144 return INVALID_HANDLE_VALUE;
145 }
146 strcpy(p + 1, "service\\" XRT_SERVICE_EXECUTABLE);
147 if (!CreateProcessA(NULL, service_path, NULL, NULL, false, 0, NULL, NULL, &si, &pi)) {
148 err = GetLastError();
149 IPC_INFO(ipc_c, XRT_SERVICE_EXECUTABLE " not found at %s: %d %s", service_path, err,
150 ipc_winerror(err));
151 return INVALID_HANDLE_VALUE;
152 }
153 }
154 IPC_INFO(ipc_c, "Launched %s (pid %d)... Waiting for %s...", service_path, pi.dwProcessId, pipe_name);
155 CloseHandle(pi.hThread);
156 for (int i = 0;; i++) {
157 pipe_inst = CreateFileA(pipe_name, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
158 if (pipe_inst != INVALID_HANDLE_VALUE) {
159 IPC_INFO(ipc_c, "Connected to %s after %d msec on try %d!", pipe_name, i * 100, i + 1);
160 break;
161 }
162 err = GetLastError();
163 if (err != ERROR_FILE_NOT_FOUND || WaitForSingleObject(pi.hProcess, 100) != WAIT_TIMEOUT) {
164 IPC_ERROR(ipc_c, "Connect to %s failed: %d %s", pipe_name, err, ipc_winerror(err));
165 break;
166 }
167 }
168 CloseHandle(pi.hProcess);
169 return pipe_inst;
170}
171#endif
172
173static bool
174ipc_client_socket_connect(struct ipc_connection *ipc_c)
175{
176 const char pipe_prefix[] = "\\\\.\\pipe\\";
177#define prefix_len sizeof(pipe_prefix) - 1
178 char pipe_name[MAX_PATH + prefix_len];
179 strcpy(pipe_name, pipe_prefix);
180
181 if (u_file_get_path_in_runtime_dir(XRT_IPC_MSG_SOCK_FILENAME, pipe_name + prefix_len, MAX_PATH) == -1) {
182 U_LOG_E("u_file_get_path_in_runtime_dir failed!");
183 return false;
184 }
185
186 HANDLE pipe_inst = ipc_connect_pipe(ipc_c, pipe_name);
187 if (pipe_inst == INVALID_HANDLE_VALUE) {
188 return false;
189 }
190 DWORD mode = PIPE_READMODE_MESSAGE | PIPE_WAIT;
191 if (!SetNamedPipeHandleState(pipe_inst, &mode, NULL, NULL)) {
192 DWORD err = GetLastError();
193 IPC_ERROR(ipc_c, "SetNamedPipeHandleState(PIPE_READMODE_MESSAGE | PIPE_WAIT) failed: %d %s", err,
194 ipc_winerror(err));
195 return false;
196 }
197
198 // Set socket.
199 ipc_c->imc.ipc_handle = pipe_inst;
200
201 return true;
202}
203
204#else
205
206static bool
207ipc_client_socket_connect(struct ipc_connection *ipc_c)
208{
209#ifdef SOCK_CLOEXEC
210 // Make sure the socket is not inherited by child processes. For one, when there is an fd to the socket
211 // in the child process closing the client connection (or killing the connected process)
212 // may not be seen in the server as client disconnection.
213 const int flags = SOCK_CLOEXEC;
214#else
215 const int flags = 0;
216#endif
217 struct sockaddr_un addr;
218 int ret;
219
220
221 // create our IPC socket
222
223 ret = socket(PF_UNIX, SOCK_STREAM | flags, 0);
224 if (ret < 0) {
225 IPC_ERROR(ipc_c, "Socket Create Error!");
226 return false;
227 }
228
229 int socket = ret;
230
231 char sock_file[PATH_MAX];
232
233 ssize_t size = u_file_get_path_in_runtime_dir(XRT_IPC_MSG_SOCK_FILENAME, sock_file, PATH_MAX);
234 if (size == -1) {
235 IPC_ERROR(ipc_c, "Could not get socket file name");
236 return false;
237 }
238
239 memset(&addr, 0, sizeof(addr));
240 addr.sun_family = AF_UNIX;
241 strcpy(addr.sun_path, sock_file);
242
243 ret = connect(socket, (struct sockaddr *)&addr, sizeof(addr));
244 if (ret < 0) {
245 IPC_ERROR(ipc_c, "Failed to connect to socket %s: %s!", sock_file, strerror(errno));
246 close(socket);
247 return false;
248 }
249
250 // Set socket.
251 ipc_c->imc.ipc_handle = socket;
252
253 return true;
254}
255
256#endif
257
258
259static xrt_result_t
260ipc_client_setup_shm(struct ipc_connection *ipc_c)
261{
262 /*
263 * Get our shared memory area from the server.
264 */
265
266 xrt_result_t xret = ipc_call_instance_get_shm_fd(ipc_c, &ipc_c->ism_handle, 1);
267 if (xret != XRT_SUCCESS) {
268 IPC_ERROR(ipc_c, "Failed to retrieve shm fd!");
269 return xret;
270 }
271
272 /*
273 * Now map it.
274 */
275
276 const size_t size = sizeof(struct ipc_shared_memory);
277
278#ifdef XRT_OS_WINDOWS
279 DWORD access = FILE_MAP_READ | FILE_MAP_WRITE;
280
281 ipc_c->ism = MapViewOfFile(ipc_c->ism_handle, access, 0, 0, size);
282#else
283 const int flags = MAP_SHARED;
284 const int access = PROT_READ | PROT_WRITE;
285
286 ipc_c->ism = mmap(NULL, size, access, flags, ipc_c->ism_handle, 0);
287#endif
288
289 if (ipc_c->ism == NULL) {
290 IPC_ERROR(ipc_c, "Failed to mmap shm!");
291 return XRT_ERROR_IPC_FAILURE;
292 }
293
294 return XRT_SUCCESS;
295}
296
297static xrt_result_t
298ipc_client_check_git_tag(struct ipc_connection *ipc_c)
299{
300 // Does the git tags match?
301 if (strncmp(u_git_tag, ipc_c->ism->u_git_tag, IPC_VERSION_NAME_LEN) == 0) {
302 return XRT_SUCCESS;
303 }
304
305 IPC_ERROR(ipc_c, "Monado client library version %s does not match service version %s", u_git_tag,
306 ipc_c->ism->u_git_tag);
307
308 if (!debug_get_bool_option_ipc_ignore_version()) {
309 IPC_ERROR(ipc_c, "Set IPC_IGNORE_VERSION=1 to ignore this version conflict");
310 return XRT_ERROR_IPC_FAILURE;
311 }
312
313 // Error is ignored.
314 return XRT_SUCCESS;
315}
316
317static xrt_result_t
318ipc_client_describe_client(struct ipc_connection *ipc_c, const struct xrt_application_info *a_info)
319{
320#ifdef XRT_OS_WINDOWS
321 DWORD pid = GetCurrentProcessId();
322#else
323 pid_t pid = getpid();
324#endif
325
326 struct ipc_client_description desc = {0};
327 desc.info = *a_info;
328 desc.pid = pid; // Extra info.
329
330 xrt_result_t xret = ipc_call_instance_describe_client(ipc_c, &desc);
331 if (xret != XRT_SUCCESS) {
332 IPC_ERROR(ipc_c, "Failed to set instance description!");
333 return xret;
334 }
335
336 return XRT_SUCCESS;
337}
338
339
340/*
341 *
342 * 'Exported' functions.
343 *
344 */
345
346xrt_result_t
347ipc_client_connection_init(struct ipc_connection *ipc_c,
348 enum u_logging_level log_level,
349 const struct xrt_instance_info *i_info)
350{
351 xrt_result_t xret;
352
353 U_ZERO(ipc_c);
354 ipc_c->imc.ipc_handle = XRT_IPC_HANDLE_INVALID;
355 ipc_c->imc.log_level = log_level;
356 ipc_c->ism_handle = XRT_SHMEM_HANDLE_INVALID;
357
358 // Must be done first.
359 int ret = os_mutex_init(&ipc_c->mutex);
360 if (ret != 0) {
361 U_LOG_E("Failed to init mutex!");
362 return XRT_ERROR_IPC_FAILURE;
363 }
364
365 // Connect the service.
366#ifdef XRT_OS_ANDROID
367 struct _JavaVM *vm = i_info->platform_info.vm;
368 void *context = i_info->platform_info.context;
369
370 if (!ipc_client_socket_connect(ipc_c, vm, context)) {
371#else
372 if (!ipc_client_socket_connect(ipc_c)) {
373#endif
374 IPC_ERROR(ipc_c,
375 "Failed to connect to monado service process\n\n"
376 "###\n"
377 "#\n"
378 "# Please make sure that the service process is running\n"
379 "#\n"
380 "# It is called \"monado-service\"\n"
381 "# In build trees, it is located "
382 "\"build-dir/src/xrt/targets/service/monado-service\"\n"
383 "#\n"
384 "###");
385 os_mutex_destroy(&ipc_c->mutex);
386 return XRT_ERROR_IPC_FAILURE;
387 }
388
389 // Do this first so we can use it to check git tags.
390 xret = ipc_client_setup_shm(ipc_c);
391 if (xret != XRT_SUCCESS) {
392 goto err_fini; // Already logged.
393 }
394
395 // Requires shm.
396 xret = ipc_client_check_git_tag(ipc_c);
397 if (xret != XRT_SUCCESS) {
398 goto err_fini; // Already logged.
399 }
400
401 // Do this last.
402 xret = ipc_client_describe_client(ipc_c, &i_info->app_info);
403 if (xret != XRT_SUCCESS) {
404 goto err_fini; // Already logged.
405 }
406
407 return XRT_SUCCESS;
408
409err_fini:
410 ipc_client_connection_fini(ipc_c);
411
412 return xret;
413}
414
415void
416ipc_client_connection_fini(struct ipc_connection *ipc_c)
417{
418 if (ipc_c->ism_handle != XRT_SHMEM_HANDLE_INVALID) {
419 /// @todo how to tear down the shared memory?
420 }
421 ipc_message_channel_close(&ipc_c->imc);
422 os_mutex_destroy(&ipc_c->mutex);
423
424#ifdef XRT_OS_ANDROID
425 ipc_client_android_destroy(&(ipc_c->ica));
426#endif
427}