The open source OpenXR runtime
at mr/scanout-values 427 lines 12 kB view raw
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}