1// Copyright (c) 2017-2023, The Khronos Group Inc.
2// Copyright (c) 2017-2019 Valve Corporation
3// Copyright (c) 2017-2019 LunarG, Inc.
4//
5// SPDX-License-Identifier: Apache-2.0 OR MIT
6//
7// Initial Authors: Mark Young <marky@lunarg.com>, Dave Houlton <daveh@lunarg.com>
8//
9
10#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS)
11#define _CRT_SECURE_NO_WARNINGS
12#endif // defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS)
13
14#include "manifest_file.hpp"
15
16#ifdef OPENXR_HAVE_COMMON_CONFIG
17#include "common_config.h"
18#endif // OPENXR_HAVE_COMMON_CONFIG
19
20#include "filesystem_utils.hpp"
21#include "loader_platform.hpp"
22#include "platform_utils.hpp"
23#include "loader_logger.hpp"
24#include "unique_asset.h"
25
26#include <json/json.h>
27#include <openxr/openxr.h>
28
29#include <algorithm>
30#include <cstdlib>
31#include <cstdio>
32#include <cstring>
33#include <fstream>
34#include <memory>
35#include <sstream>
36#include <stdexcept>
37#include <string>
38#include <unordered_map>
39#include <utility>
40#include <vector>
41
42#ifndef FALLBACK_CONFIG_DIRS
43#define FALLBACK_CONFIG_DIRS "/etc/xdg"
44#endif // !FALLBACK_CONFIG_DIRS
45
46#ifndef FALLBACK_DATA_DIRS
47#define FALLBACK_DATA_DIRS "/usr/local/share:/usr/share"
48#endif // !FALLBACK_DATA_DIRS
49
50#ifndef SYSCONFDIR
51#define SYSCONFDIR "/etc"
52#endif // !SYSCONFDIR
53
54#ifdef XR_USE_PLATFORM_ANDROID
55#include <android/asset_manager.h>
56#endif
57
58#ifdef XRLOADER_DISABLE_EXCEPTION_HANDLING
59#if JSON_USE_EXCEPTIONS
60#error \
61 "Loader is configured to not catch exceptions, but jsoncpp was built with exception-throwing enabled, which could violate the C ABI. One of those two things needs to change."
62#endif // JSON_USE_EXCEPTIONS
63#endif // !XRLOADER_DISABLE_EXCEPTION_HANDLING
64
65#include "runtime_interface.hpp"
66
67// Utility functions for finding files in the appropriate paths
68
69static inline bool StringEndsWith(const std::string &value, const std::string &ending) {
70 if (ending.size() > value.size()) {
71 return false;
72 }
73 return std::equal(ending.rbegin(), ending.rend(), value.rbegin());
74}
75
76// If the file found is a manifest file name, add it to the out_files manifest list.
77static void AddIfJson(const std::string &full_file, std::vector<std::string> &manifest_files) {
78 if (full_file.empty() || !StringEndsWith(full_file, ".json")) {
79 return;
80 }
81 manifest_files.push_back(full_file);
82}
83
84// Check the current path for any manifest files. If the provided search_path is a directory, look for
85// all included JSON files in that directory. Otherwise, just check the provided search_path which should
86// be a single filename.
87static void CheckAllFilesInThePath(const std::string &search_path, bool is_directory_list,
88 std::vector<std::string> &manifest_files) {
89 if (FileSysUtilsPathExists(search_path)) {
90 std::string absolute_path;
91 if (!is_directory_list) {
92 // If the file exists, try to add it
93 if (FileSysUtilsIsRegularFile(search_path)) {
94 FileSysUtilsGetAbsolutePath(search_path, absolute_path);
95 AddIfJson(absolute_path, manifest_files);
96 }
97 } else {
98 std::vector<std::string> files;
99 if (FileSysUtilsFindFilesInPath(search_path, files)) {
100 for (std::string &cur_file : files) {
101 std::string relative_path;
102 FileSysUtilsCombinePaths(search_path, cur_file, relative_path);
103 if (!FileSysUtilsGetAbsolutePath(relative_path, absolute_path)) {
104 continue;
105 }
106 AddIfJson(absolute_path, manifest_files);
107 }
108 }
109 }
110 }
111}
112
113// Add all manifest files in the provided paths to the manifest_files list. If search_path
114// is made up of directory listings (versus direct manifest file names) search each path for
115// any manifest files.
116static void AddFilesInPath(const std::string &search_path, bool is_directory_list, std::vector<std::string> &manifest_files) {
117 std::size_t last_found = 0;
118 std::size_t found = search_path.find_first_of(PATH_SEPARATOR);
119 std::string cur_search;
120
121 // Handle any path listings in the string (separated by the appropriate path separator)
122 while (found != std::string::npos) {
123 // substr takes a start index and length.
124 std::size_t length = found - last_found;
125 cur_search = search_path.substr(last_found, length);
126
127 CheckAllFilesInThePath(cur_search, is_directory_list, manifest_files);
128
129 // This works around issue if multiple path separator follow each other directly.
130 last_found = found;
131 while (found == last_found) {
132 last_found = found + 1;
133 found = search_path.find_first_of(PATH_SEPARATOR, last_found);
134 }
135 }
136
137 // If there's something remaining in the string, copy it over
138 if (last_found < search_path.size()) {
139 cur_search = search_path.substr(last_found);
140 CheckAllFilesInThePath(cur_search, is_directory_list, manifest_files);
141 }
142}
143
144// Copy all paths listed in the cur_path string into output_path and append the appropriate relative_path onto the end of each.
145static void CopyIncludedPaths(bool is_directory_list, const std::string &cur_path, const std::string &relative_path,
146 std::string &output_path) {
147 if (!cur_path.empty()) {
148 std::size_t last_found = 0;
149 std::size_t found = cur_path.find_first_of(PATH_SEPARATOR);
150
151 // Handle any path listings in the string (separated by the appropriate path separator)
152 while (found != std::string::npos) {
153 std::size_t length = found - last_found;
154 output_path += cur_path.substr(last_found, length);
155 if (is_directory_list && (cur_path[found - 1] != '\\' && cur_path[found - 1] != '/')) {
156 output_path += DIRECTORY_SYMBOL;
157 }
158 output_path += relative_path;
159 output_path += PATH_SEPARATOR;
160
161 last_found = found;
162 found = cur_path.find_first_of(PATH_SEPARATOR, found + 1);
163 }
164
165 // If there's something remaining in the string, copy it over
166 size_t last_char = cur_path.size() - 1;
167 if (last_found != last_char) {
168 output_path += cur_path.substr(last_found);
169 if (is_directory_list && (cur_path[last_char] != '\\' && cur_path[last_char] != '/')) {
170 output_path += DIRECTORY_SYMBOL;
171 }
172 output_path += relative_path;
173 output_path += PATH_SEPARATOR;
174 }
175 }
176}
177
178// Look for data files in the provided paths, but first check the environment override to determine if we should use that instead.
179static void ReadDataFilesInSearchPaths(const std::string &override_env_var, const std::string &relative_path, bool &override_active,
180 std::vector<std::string> &manifest_files) {
181 std::string override_path;
182 std::string search_path;
183
184 if (!override_env_var.empty()) {
185 bool permit_override = true;
186#ifndef XR_OS_WINDOWS
187 if (geteuid() != getuid() || getegid() != getgid()) {
188 // Don't allow setuid apps to use the env var
189 permit_override = false;
190 }
191#endif
192 if (permit_override) {
193 override_path = PlatformUtilsGetSecureEnv(override_env_var.c_str());
194 }
195 }
196
197 if (!override_path.empty()) {
198 CopyIncludedPaths(true, override_path, "", search_path);
199 override_active = true;
200 } else {
201 override_active = false;
202#if !defined(XR_OS_WINDOWS) && !defined(XR_OS_ANDROID)
203 const char home_additional[] = ".local/share/";
204
205 // Determine how much space is needed to generate the full search path
206 // for the current manifest files.
207 std::string xdg_conf_dirs = PlatformUtilsGetSecureEnv("XDG_CONFIG_DIRS");
208 std::string xdg_data_dirs = PlatformUtilsGetSecureEnv("XDG_DATA_DIRS");
209 std::string xdg_data_home = PlatformUtilsGetSecureEnv("XDG_DATA_HOME");
210 std::string home = PlatformUtilsGetSecureEnv("HOME");
211
212 if (xdg_conf_dirs.empty()) {
213 CopyIncludedPaths(true, FALLBACK_CONFIG_DIRS, relative_path, search_path);
214 } else {
215 CopyIncludedPaths(true, xdg_conf_dirs, relative_path, search_path);
216 }
217
218 CopyIncludedPaths(true, SYSCONFDIR, relative_path, search_path);
219#if defined(EXTRASYSCONFDIR)
220 CopyIncludedPaths(true, EXTRASYSCONFDIR, relative_path, search_path);
221#endif
222
223 if (xdg_data_dirs.empty()) {
224 CopyIncludedPaths(true, FALLBACK_DATA_DIRS, relative_path, search_path);
225 } else {
226 CopyIncludedPaths(true, xdg_data_dirs, relative_path, search_path);
227 }
228
229 if (!xdg_data_home.empty()) {
230 CopyIncludedPaths(true, xdg_data_home, relative_path, search_path);
231 } else if (!home.empty()) {
232 std::string relative_home_path = home_additional;
233 relative_home_path += relative_path;
234 CopyIncludedPaths(true, home, relative_home_path, search_path);
235 }
236#elif defined(XR_OS_ANDROID)
237 CopyIncludedPaths(true, "/product/etc", relative_path, search_path);
238 CopyIncludedPaths(true, "/odm/etc", relative_path, search_path);
239 CopyIncludedPaths(true, "/oem/etc", relative_path, search_path);
240 CopyIncludedPaths(true, "/vendor/etc", relative_path, search_path);
241 CopyIncludedPaths(true, "/system/etc", relative_path, search_path);
242#else
243 (void)relative_path;
244#endif
245 }
246
247 // Now, parse the paths and add any manifest files found in them.
248 AddFilesInPath(search_path, true, manifest_files);
249}
250
251#ifdef XR_OS_LINUX
252
253// Get an XDG environment variable with a $HOME-relative default
254static std::string GetXDGEnvHome(const char *name, const char *fallback_path) {
255 std::string result = PlatformUtilsGetSecureEnv(name);
256 if (!result.empty()) {
257 return result;
258 }
259 result = PlatformUtilsGetSecureEnv("HOME");
260 if (result.empty()) {
261 return result;
262 }
263 result += "/";
264 result += fallback_path;
265 return result;
266}
267
268// Get an XDG environment variable with absolute defaults
269static std::string GetXDGEnvAbsolute(const char *name, const char *fallback_paths) {
270 std::string result = PlatformUtilsGetSecureEnv(name);
271 if (!result.empty()) {
272 return result;
273 }
274 return fallback_paths;
275}
276
277// Return the first instance of relative_path occurring in an XDG config dir according to standard
278// precedence order.
279static bool FindXDGConfigFile(const std::string &relative_path, std::string &out) {
280 out = GetXDGEnvHome("XDG_CONFIG_HOME", ".config");
281 if (!out.empty()) {
282 out += "/";
283 out += relative_path;
284
285 LoaderLogger::LogInfoMessage("", "Looking for " + relative_path + " in XDG_CONFIG_HOME: " + out);
286 if (FileSysUtilsPathExists(out)) {
287 return true;
288 }
289 }
290
291 std::istringstream iss(GetXDGEnvAbsolute("XDG_CONFIG_DIRS", FALLBACK_CONFIG_DIRS));
292 std::string path;
293 while (std::getline(iss, path, PATH_SEPARATOR)) {
294 if (path.empty()) {
295 continue;
296 }
297 out = path;
298 out += "/";
299 out += relative_path;
300 LoaderLogger::LogInfoMessage("", "Looking for " + relative_path + " in an entry of XDG_CONFIG_DIRS: " + out);
301 if (FileSysUtilsPathExists(out)) {
302 return true;
303 }
304 }
305
306 out = SYSCONFDIR;
307 out += "/";
308 out += relative_path;
309 LoaderLogger::LogInfoMessage("", "Looking for " + relative_path + " in compiled-in SYSCONFDIR: " + out);
310 if (FileSysUtilsPathExists(out)) {
311 return true;
312 }
313
314#if defined(EXTRASYSCONFDIR)
315 out = EXTRASYSCONFDIR;
316 out += "/";
317 out += relative_path;
318 LoaderLogger::LogInfoMessage("", "Looking for " + relative_path + " in compiled-in EXTRASYSCONFDIR: " + out);
319 if (FileSysUtilsPathExists(out)) {
320 return true;
321 }
322#endif
323
324 out.clear();
325 return false;
326}
327
328#endif
329
330#ifdef XR_OS_WINDOWS
331
332// Look for runtime data files in the provided paths, but first check the environment override to determine
333// if we should use that instead.
334static void ReadRuntimeDataFilesInRegistry(const std::string &runtime_registry_location,
335 const std::string &default_runtime_value_name,
336 std::vector<std::string> &manifest_files) {
337 HKEY hkey;
338 DWORD access_flags;
339 wchar_t value_w[1024];
340 DWORD value_size_w = sizeof(value_w); // byte size of the buffer.
341
342 // Generate the full registry location for the registry information
343 std::string full_registry_location = OPENXR_REGISTRY_LOCATION;
344 full_registry_location += std::to_string(XR_VERSION_MAJOR(XR_CURRENT_API_VERSION));
345 full_registry_location += runtime_registry_location;
346
347 const std::wstring full_registry_location_w = utf8_to_wide(full_registry_location);
348 const std::wstring default_runtime_value_name_w = utf8_to_wide(default_runtime_value_name);
349
350 // Use 64 bit regkey for 64bit application, and use 32 bit regkey in WOW for 32bit application.
351 access_flags = KEY_QUERY_VALUE;
352 LONG open_value = RegOpenKeyExW(HKEY_LOCAL_MACHINE, full_registry_location_w.c_str(), 0, access_flags, &hkey);
353
354 if (ERROR_SUCCESS != open_value) {
355 LoaderLogger::LogWarningMessage("",
356 "ReadRuntimeDataFilesInRegistry - failed to open registry key " + full_registry_location);
357
358 return;
359 }
360
361 if (ERROR_SUCCESS != RegGetValueW(hkey, nullptr, default_runtime_value_name_w.c_str(),
362 RRF_RT_REG_SZ | REG_EXPAND_SZ | RRF_ZEROONFAILURE, NULL, reinterpret_cast<LPBYTE>(&value_w),
363 &value_size_w)) {
364 LoaderLogger::LogWarningMessage(
365 "", "ReadRuntimeDataFilesInRegistry - failed to read registry value " + default_runtime_value_name);
366 } else {
367 AddFilesInPath(wide_to_utf8(value_w), false, manifest_files);
368 }
369
370 RegCloseKey(hkey);
371}
372
373// Look for layer data files in the provided paths, but first check the environment override to determine
374// if we should use that instead.
375static void ReadLayerDataFilesInRegistry(const std::string &registry_location, std::vector<std::string> &manifest_files) {
376 const std::wstring full_registry_location_w =
377 utf8_to_wide(OPENXR_REGISTRY_LOCATION + std::to_string(XR_VERSION_MAJOR(XR_CURRENT_API_VERSION)) + registry_location);
378
379 auto ReadLayerDataFilesInHive = [&](HKEY hive) {
380 HKEY hkey;
381 LONG open_value = RegOpenKeyExW(hive, full_registry_location_w.c_str(), 0, KEY_QUERY_VALUE, &hkey);
382 if (ERROR_SUCCESS != open_value) {
383 return false;
384 }
385
386 wchar_t name_w[1024]{};
387 LONG rtn_value;
388 DWORD name_size = 1023;
389 DWORD value;
390 DWORD value_size = sizeof(value);
391 DWORD key_index = 0;
392 while (ERROR_SUCCESS ==
393 (rtn_value = RegEnumValueW(hkey, key_index++, name_w, &name_size, NULL, NULL, (LPBYTE)&value, &value_size))) {
394 if (value_size == sizeof(value) && value == 0) {
395 const std::string filename = wide_to_utf8(name_w);
396 AddFilesInPath(filename, false, manifest_files);
397 }
398 // Reset some items for the next loop
399 name_size = 1023;
400 }
401
402 RegCloseKey(hkey);
403
404 return true;
405 };
406
407 // Do not allow high integrity processes to act on data that can be controlled by medium integrity processes.
408 const bool readFromCurrentUser = !IsHighIntegrityLevel();
409
410 bool found = ReadLayerDataFilesInHive(HKEY_LOCAL_MACHINE);
411 if (readFromCurrentUser) {
412 found |= ReadLayerDataFilesInHive(HKEY_CURRENT_USER);
413 }
414
415 if (!found) {
416 std::string warning_message = "ReadLayerDataFilesInRegistry - failed to read registry location ";
417 warning_message += registry_location;
418 warning_message += (readFromCurrentUser ? " in either HKEY_LOCAL_MACHINE or HKEY_CURRENT_USER" : " in HKEY_LOCAL_MACHINE");
419 LoaderLogger::LogWarningMessage("", warning_message);
420 }
421}
422
423#endif // XR_OS_WINDOWS
424
425ManifestFile::ManifestFile(ManifestFileType type, const std::string &filename, const std::string &library_path)
426 : _filename(filename), _type(type), _library_path(library_path) {}
427
428bool ManifestFile::IsValidJson(const Json::Value &root_node, JsonVersion &version) {
429 if (root_node["file_format_version"].isNull() || !root_node["file_format_version"].isString()) {
430 LoaderLogger::LogErrorMessage("", "ManifestFile::IsValidJson - JSON file missing \"file_format_version\"");
431 return false;
432 }
433 std::string file_format = root_node["file_format_version"].asString();
434 const int num_fields = sscanf(file_format.c_str(), "%u.%u.%u", &version.major, &version.minor, &version.patch);
435
436 // Only version 1.0.0 is defined currently. Eventually we may have more version, but
437 // some of the versions may only be valid for layers or runtimes specifically.
438 if (num_fields != 3 || version.major != 1 || version.minor != 0 || version.patch != 0) {
439 std::ostringstream error_ss;
440 error_ss << "ManifestFile::IsValidJson - JSON \"file_format_version\" " << version.major << "." << version.minor << "."
441 << version.patch << " is not supported";
442 LoaderLogger::LogErrorMessage("", error_ss.str());
443 return false;
444 }
445
446 return true;
447}
448
449static void GetExtensionProperties(const std::vector<ExtensionListing> &extensions, std::vector<XrExtensionProperties> &props) {
450 for (const auto &ext : extensions) {
451 auto it =
452 std::find_if(props.begin(), props.end(), [&](XrExtensionProperties &prop) { return prop.extensionName == ext.name; });
453 if (it != props.end()) {
454 it->extensionVersion = std::max(it->extensionVersion, ext.extension_version);
455 } else {
456 XrExtensionProperties prop{};
457 prop.type = XR_TYPE_EXTENSION_PROPERTIES;
458 strncpy(prop.extensionName, ext.name.c_str(), XR_MAX_EXTENSION_NAME_SIZE - 1);
459 prop.extensionName[XR_MAX_EXTENSION_NAME_SIZE - 1] = '\0';
460 prop.extensionVersion = ext.extension_version;
461 props.push_back(prop);
462 }
463 }
464}
465
466// Return any instance extensions found in the manifest files in the proper form for
467// OpenXR (XrExtensionProperties).
468void ManifestFile::GetInstanceExtensionProperties(std::vector<XrExtensionProperties> &props) {
469 GetExtensionProperties(_instance_extensions, props);
470}
471
472const std::string &ManifestFile::GetFunctionName(const std::string &func_name) const {
473 if (!_functions_renamed.empty()) {
474 auto found = _functions_renamed.find(func_name);
475 if (found != _functions_renamed.end()) {
476 return found->second;
477 }
478 }
479 return func_name;
480}
481
482RuntimeManifestFile::RuntimeManifestFile(const std::string &filename, const std::string &library_path)
483 : ManifestFile(MANIFEST_TYPE_RUNTIME, filename, library_path) {}
484
485static void ParseExtension(Json::Value const &ext, std::vector<ExtensionListing> &extensions) {
486 Json::Value ext_name = ext["name"];
487 Json::Value ext_version = ext["extension_version"];
488
489 // Allow "extension_version" as a String or a UInt to maintain backwards compatibility, even though it should be a String.
490 // Internal Issue 1411: https://gitlab.khronos.org/openxr/openxr/-/issues/1411
491 // Internal MR !1867: https://gitlab.khronos.org/openxr/openxr/-/merge_requests/1867
492 if (ext_name.isString() && (ext_version.isString() || ext_version.isUInt())) {
493 ExtensionListing ext_listing = {};
494 ext_listing.name = ext_name.asString();
495 if (ext_version.isUInt()) {
496 ext_listing.extension_version = ext_version.asUInt();
497 } else {
498 ext_listing.extension_version = atoi(ext_version.asString().c_str());
499 }
500 extensions.push_back(ext_listing);
501 }
502}
503
504void ManifestFile::ParseCommon(Json::Value const &root_node) {
505 const Json::Value &inst_exts = root_node["instance_extensions"];
506 if (!inst_exts.isNull() && inst_exts.isArray()) {
507 for (const auto &ext : inst_exts) {
508 ParseExtension(ext, _instance_extensions);
509 }
510 }
511 const Json::Value &funcs_renamed = root_node["functions"];
512 if (!funcs_renamed.isNull() && !funcs_renamed.empty()) {
513 for (Json::ValueConstIterator func_it = funcs_renamed.begin(); func_it != funcs_renamed.end(); ++func_it) {
514 if (!(*func_it).isString()) {
515 LoaderLogger::LogWarningMessage(
516 "", "ManifestFile::ParseCommon " + _filename + " \"functions\" section contains non-string values.");
517 continue;
518 }
519 std::string original_name = func_it.key().asString();
520 std::string new_name = (*func_it).asString();
521 _functions_renamed.emplace(original_name, new_name);
522 }
523 }
524}
525
526void RuntimeManifestFile::CreateIfValid(std::string const &filename,
527 std::vector<std::unique_ptr<RuntimeManifestFile>> &manifest_files) {
528 std::ifstream json_stream(filename, std::ifstream::in);
529
530 LoaderLogger::LogInfoMessage("", "RuntimeManifestFile::CreateIfValid - attempting to load " + filename);
531 std::ostringstream error_ss("RuntimeManifestFile::CreateIfValid ");
532 if (!json_stream.is_open()) {
533 error_ss << "failed to open " << filename << ". Does it exist?";
534 LoaderLogger::LogErrorMessage("", error_ss.str());
535 return;
536 }
537 Json::CharReaderBuilder builder;
538 std::string errors;
539 Json::Value root_node = Json::nullValue;
540 if (!Json::parseFromStream(builder, json_stream, &root_node, &errors) || !root_node.isObject()) {
541 error_ss << "failed to parse " << filename << ".";
542 if (!errors.empty()) {
543 error_ss << " (Error message: " << errors << ")";
544 }
545 error_ss << " Is it a valid runtime manifest file?";
546 LoaderLogger::LogErrorMessage("", error_ss.str());
547 return;
548 }
549
550 CreateIfValid(root_node, filename, manifest_files);
551}
552
553void RuntimeManifestFile::CreateIfValid(const Json::Value &root_node, const std::string &filename,
554 std::vector<std::unique_ptr<RuntimeManifestFile>> &manifest_files) {
555 std::ostringstream error_ss("RuntimeManifestFile::CreateIfValid ");
556 JsonVersion file_version = {};
557 if (!ManifestFile::IsValidJson(root_node, file_version)) {
558 error_ss << "isValidJson indicates " << filename << " is not a valid manifest file.";
559 LoaderLogger::LogErrorMessage("", error_ss.str());
560 return;
561 }
562 const Json::Value &runtime_root_node = root_node["runtime"];
563 // The Runtime manifest file needs the "runtime" root as well as a sub-node for "library_path". If any of those aren't there,
564 // fail.
565 if (runtime_root_node.isNull() || runtime_root_node["library_path"].isNull() || !runtime_root_node["library_path"].isString()) {
566 error_ss << filename << " is missing required fields. Verify all proper fields exist.";
567 LoaderLogger::LogErrorMessage("", error_ss.str());
568 return;
569 }
570
571 std::string lib_path = runtime_root_node["library_path"].asString();
572
573 // If the library_path variable has no directory symbol, it's just a file name and should be accessible on the
574 // global library path.
575 if (lib_path.find('\\') != std::string::npos || lib_path.find('/') != std::string::npos) {
576 // If the library_path is an absolute path, just use that if it exists
577 if (FileSysUtilsIsAbsolutePath(lib_path)) {
578 if (!FileSysUtilsPathExists(lib_path)) {
579 error_ss << filename << " library " << lib_path << " does not appear to exist";
580 LoaderLogger::LogErrorMessage("", error_ss.str());
581 return;
582 }
583 } else {
584 // Otherwise, treat the library path as a relative path based on the JSON file.
585 std::string canonical_path;
586 std::string combined_path;
587 std::string file_parent;
588 // Search relative to the real manifest file, not relative to the symlink
589 if (!FileSysUtilsGetCanonicalPath(filename, canonical_path)) {
590 // Give relative to the non-canonical path a chance
591 canonical_path = filename;
592 }
593 if (!FileSysUtilsGetParentPath(canonical_path, file_parent) ||
594 !FileSysUtilsCombinePaths(file_parent, lib_path, combined_path) || !FileSysUtilsPathExists(combined_path)) {
595 error_ss << filename << " library " << combined_path << " does not appear to exist";
596 LoaderLogger::LogErrorMessage("", error_ss.str());
597 return;
598 }
599 lib_path = combined_path;
600 }
601 }
602
603 // Add this runtime manifest file
604 manifest_files.emplace_back(new RuntimeManifestFile(filename, lib_path));
605
606 // Add any extensions to it after the fact.
607 // Handle any renamed functions
608 manifest_files.back()->ParseCommon(runtime_root_node);
609}
610
611// Find all manifest files in the appropriate search paths/registries for the given type.
612XrResult RuntimeManifestFile::FindManifestFiles(std::vector<std::unique_ptr<RuntimeManifestFile>> &manifest_files) {
613 XrResult result = XR_SUCCESS;
614 std::string filename = PlatformUtilsGetSecureEnv(OPENXR_RUNTIME_JSON_ENV_VAR);
615 if (!filename.empty()) {
616 LoaderLogger::LogInfoMessage(
617 "", "RuntimeManifestFile::FindManifestFiles - using environment variable override runtime file " + filename);
618 } else {
619#ifdef XR_OS_WINDOWS
620 std::vector<std::string> filenames;
621 ReadRuntimeDataFilesInRegistry("", "ActiveRuntime", filenames);
622 if (filenames.size() == 0) {
623 LoaderLogger::LogErrorMessage(
624 "", "RuntimeManifestFile::FindManifestFiles - failed to find active runtime file in registry");
625 return XR_ERROR_RUNTIME_UNAVAILABLE;
626 }
627 if (filenames.size() > 1) {
628 LoaderLogger::LogWarningMessage(
629 "", "RuntimeManifestFile::FindManifestFiles - found too many default runtime files in registry");
630 }
631 filename = filenames[0];
632 LoaderLogger::LogInfoMessage("",
633 "RuntimeManifestFile::FindManifestFiles - using registry-specified runtime file " + filename);
634#elif defined(XR_OS_LINUX)
635 const std::string relative_path =
636 "openxr/" + std::to_string(XR_VERSION_MAJOR(XR_CURRENT_API_VERSION)) + "/active_runtime.json";
637 if (!FindXDGConfigFile(relative_path, filename)) {
638 LoaderLogger::LogErrorMessage(
639 "", "RuntimeManifestFile::FindManifestFiles - failed to determine active runtime file path for this environment");
640 return XR_ERROR_RUNTIME_UNAVAILABLE;
641 }
642#else
643
644#if defined(XR_KHR_LOADER_INIT_SUPPORT)
645 Json::Value virtualManifest;
646 result = GetPlatformRuntimeVirtualManifest(virtualManifest);
647 if (XR_SUCCESS == result) {
648 RuntimeManifestFile::CreateIfValid(virtualManifest, "", manifest_files);
649 return result;
650 }
651#endif // defined(XR_KHR_LOADER_INIT_SUPPORT)
652 if (!PlatformGetGlobalRuntimeFileName(XR_VERSION_MAJOR(XR_CURRENT_API_VERSION), filename)) {
653 LoaderLogger::LogErrorMessage(
654 "", "RuntimeManifestFile::FindManifestFiles - failed to determine active runtime file path for this environment");
655 return XR_ERROR_RUNTIME_UNAVAILABLE;
656 }
657 result = XR_SUCCESS;
658 LoaderLogger::LogInfoMessage("", "RuntimeManifestFile::FindManifestFiles - using global runtime file " + filename);
659#endif
660 }
661 RuntimeManifestFile::CreateIfValid(filename, manifest_files);
662
663 return result;
664}
665
666ApiLayerManifestFile::ApiLayerManifestFile(ManifestFileType type, const std::string &filename, const std::string &layer_name,
667 const std::string &description, const JsonVersion &api_version,
668 const uint32_t &implementation_version, const std::string &library_path)
669 : ManifestFile(type, filename, library_path),
670 _api_version(api_version),
671 _layer_name(layer_name),
672 _description(description),
673 _implementation_version(implementation_version) {}
674
675#ifdef XR_USE_PLATFORM_ANDROID
676void ApiLayerManifestFile::AddManifestFilesAndroid(ManifestFileType type,
677 std::vector<std::unique_ptr<ApiLayerManifestFile>> &manifest_files) {
678 AAssetManager *assetManager = (AAssetManager *)Android_Get_Asset_Manager();
679 std::vector<std::string> filenames;
680 {
681 std::string search_path = "";
682 switch (type) {
683 case MANIFEST_TYPE_IMPLICIT_API_LAYER:
684 search_path = "openxr/1/api_layers/implicit.d/";
685 break;
686 case MANIFEST_TYPE_EXPLICIT_API_LAYER:
687 search_path = "openxr/1/api_layers/explicit.d/";
688 break;
689 default:
690 return;
691 }
692
693 UniqueAssetDir dir{AAssetManager_openDir(assetManager, search_path.c_str())};
694 if (!dir) {
695 return;
696 }
697 const std::string json = ".json";
698 const char *fn = nullptr;
699 while ((fn = AAssetDir_getNextFileName(dir.get())) != nullptr) {
700 const std::string filename = search_path + fn;
701 if (filename.size() < json.size()) {
702 continue;
703 }
704 if (filename.compare(filename.size() - json.size(), json.size(), json) == 0) {
705 filenames.push_back(filename);
706 }
707 }
708 }
709 for (const auto &filename : filenames) {
710 UniqueAsset asset{AAssetManager_open(assetManager, filename.c_str(), AASSET_MODE_BUFFER)};
711 if (!asset) {
712 LoaderLogger::LogWarningMessage(
713 "", "ApiLayerManifestFile::AddManifestFilesAndroid unable to open asset " + filename + ", skipping");
714
715 continue;
716 }
717 size_t length = AAsset_getLength(asset.get());
718 const char *buf = reinterpret_cast<const char *>(AAsset_getBuffer(asset.get()));
719 if (!buf) {
720 LoaderLogger::LogWarningMessage(
721 "", "ApiLayerManifestFile::AddManifestFilesAndroid unable to access asset" + filename + ", skipping");
722
723 continue;
724 }
725 std::istringstream json_stream(std::string{buf, length});
726
727 CreateIfValid(ManifestFileType::MANIFEST_TYPE_EXPLICIT_API_LAYER, filename, json_stream,
728 &ApiLayerManifestFile::LocateLibraryInAssets, manifest_files);
729 }
730}
731#endif // XR_USE_PLATFORM_ANDROID
732
733void ApiLayerManifestFile::CreateIfValid(ManifestFileType type, const std::string &filename, std::istream &json_stream,
734 LibraryLocator locate_library,
735 std::vector<std::unique_ptr<ApiLayerManifestFile>> &manifest_files) {
736 std::ostringstream error_ss("ApiLayerManifestFile::CreateIfValid ");
737 Json::CharReaderBuilder builder;
738 std::string errors;
739 Json::Value root_node = Json::nullValue;
740 if (!Json::parseFromStream(builder, json_stream, &root_node, &errors) || !root_node.isObject()) {
741 error_ss << "failed to parse " << filename << ".";
742 if (!errors.empty()) {
743 error_ss << " (Error message: " << errors << ")";
744 }
745 error_ss << " Is it a valid layer manifest file?";
746 LoaderLogger::LogErrorMessage("", error_ss.str());
747 return;
748 }
749 JsonVersion file_version = {};
750 if (!ManifestFile::IsValidJson(root_node, file_version)) {
751 error_ss << "isValidJson indicates " << filename << " is not a valid manifest file.";
752 LoaderLogger::LogErrorMessage("", error_ss.str());
753 return;
754 }
755
756 Json::Value layer_root_node = root_node["api_layer"];
757
758 // The API Layer manifest file needs the "api_layer" root as well as other sub-nodes.
759 // If any of those aren't there, fail.
760 if (layer_root_node.isNull() || layer_root_node["name"].isNull() || !layer_root_node["name"].isString() ||
761 layer_root_node["api_version"].isNull() || !layer_root_node["api_version"].isString() ||
762 layer_root_node["library_path"].isNull() || !layer_root_node["library_path"].isString() ||
763 layer_root_node["implementation_version"].isNull() || !layer_root_node["implementation_version"].isString()) {
764 error_ss << filename << " is missing required fields. Verify all proper fields exist.";
765 LoaderLogger::LogErrorMessage("", error_ss.str());
766 return;
767 }
768 if (MANIFEST_TYPE_IMPLICIT_API_LAYER == type) {
769 bool enabled = true;
770 // Implicit layers require the disable environment variable.
771 if (layer_root_node["disable_environment"].isNull() || !layer_root_node["disable_environment"].isString()) {
772 error_ss << "Implicit layer " << filename << " is missing \"disable_environment\"";
773 LoaderLogger::LogErrorMessage("", error_ss.str());
774 return;
775 }
776 // Check if there's an enable environment variable provided
777 if (!layer_root_node["enable_environment"].isNull() && layer_root_node["enable_environment"].isString()) {
778 std::string env_var = layer_root_node["enable_environment"].asString();
779 // If it's not set in the environment, disable the layer
780 if (!PlatformUtilsGetEnvSet(env_var.c_str())) {
781 enabled = false;
782 }
783 }
784 // Check for the disable environment variable, which must be provided in the JSON
785 std::string env_var = layer_root_node["disable_environment"].asString();
786 // If the env var is set, disable the layer. Disable env var overrides enable above
787 if (PlatformUtilsGetEnvSet(env_var.c_str())) {
788 enabled = false;
789 }
790
791 // Not enabled, so pretend like it isn't even there.
792 if (!enabled) {
793 error_ss << "Implicit layer " << filename << " is disabled";
794 LoaderLogger::LogInfoMessage("", error_ss.str());
795 return;
796 }
797 }
798 std::string layer_name = layer_root_node["name"].asString();
799 std::string api_version_string = layer_root_node["api_version"].asString();
800 JsonVersion api_version = {};
801 const int num_fields = sscanf(api_version_string.c_str(), "%u.%u", &api_version.major, &api_version.minor);
802 api_version.patch = 0;
803
804 if ((num_fields != 2) || (api_version.major == 0 && api_version.minor == 0) ||
805 api_version.major > XR_VERSION_MAJOR(XR_CURRENT_API_VERSION)) {
806 error_ss << "layer " << filename << " has invalid API Version. Skipping layer.";
807 LoaderLogger::LogWarningMessage("", error_ss.str());
808 return;
809 }
810
811 uint32_t implementation_version = atoi(layer_root_node["implementation_version"].asString().c_str());
812 std::string library_path = layer_root_node["library_path"].asString();
813
814 // If the library_path variable has no directory symbol, it's just a file name and should be accessible on the
815 // global library path.
816 if (library_path.find('\\') != std::string::npos || library_path.find('/') != std::string::npos) {
817 // If the library_path is an absolute path, just use that if it exists
818 if (FileSysUtilsIsAbsolutePath(library_path)) {
819 if (!FileSysUtilsPathExists(library_path)) {
820 error_ss << filename << " library " << library_path << " does not appear to exist";
821 LoaderLogger::LogErrorMessage("", error_ss.str());
822 return;
823 }
824 } else {
825 // Otherwise, treat the library path as a relative path based on the JSON file.
826 std::string combined_path;
827 if (!locate_library(filename, library_path, combined_path)) {
828 error_ss << filename << " library " << combined_path << " does not appear to exist";
829 LoaderLogger::LogErrorMessage("", error_ss.str());
830 return;
831 }
832 library_path = combined_path;
833 }
834 }
835
836 std::string description;
837 if (!layer_root_node["description"].isNull() && layer_root_node["description"].isString()) {
838 description = layer_root_node["description"].asString();
839 }
840
841 // Add this layer manifest file
842 manifest_files.emplace_back(
843 new ApiLayerManifestFile(type, filename, layer_name, description, api_version, implementation_version, library_path));
844
845 // Add any extensions to it after the fact.
846 manifest_files.back()->ParseCommon(layer_root_node);
847}
848
849void ApiLayerManifestFile::CreateIfValid(ManifestFileType type, const std::string &filename,
850 std::vector<std::unique_ptr<ApiLayerManifestFile>> &manifest_files) {
851 std::ifstream json_stream(filename, std::ifstream::in);
852 if (!json_stream.is_open()) {
853 std::ostringstream error_ss("ApiLayerManifestFile::CreateIfValid ");
854 error_ss << "failed to open " << filename << ". Does it exist?";
855 LoaderLogger::LogErrorMessage("", error_ss.str());
856 return;
857 }
858 CreateIfValid(type, filename, json_stream, &ApiLayerManifestFile::LocateLibraryRelativeToJson, manifest_files);
859}
860
861bool ApiLayerManifestFile::LocateLibraryRelativeToJson(
862 const std::string &json_filename, const std::string &library_path,
863 std::string &out_combined_path) { // Otherwise, treat the library path as a relative path based on the JSON file.
864 std::string combined_path;
865 std::string file_parent;
866 if (!FileSysUtilsGetParentPath(json_filename, file_parent) ||
867 !FileSysUtilsCombinePaths(file_parent, library_path, combined_path) || !FileSysUtilsPathExists(combined_path)) {
868 out_combined_path = combined_path;
869 return false;
870 }
871 out_combined_path = combined_path;
872 return true;
873}
874
875#ifdef XR_USE_PLATFORM_ANDROID
876bool ApiLayerManifestFile::LocateLibraryInAssets(const std::string & /* json_filename */, const std::string &library_path,
877 std::string &out_combined_path) {
878 std::string combined_path;
879 std::string file_parent = GetAndroidNativeLibraryDir();
880 if (!FileSysUtilsCombinePaths(file_parent, library_path, combined_path) || !FileSysUtilsPathExists(combined_path)) {
881 out_combined_path = combined_path;
882 return false;
883 }
884 out_combined_path = combined_path;
885 return true;
886}
887#endif
888
889void ApiLayerManifestFile::PopulateApiLayerProperties(XrApiLayerProperties &props) const {
890 props.layerVersion = _implementation_version;
891 props.specVersion = XR_MAKE_VERSION(_api_version.major, _api_version.minor, _api_version.patch);
892 strncpy(props.layerName, _layer_name.c_str(), XR_MAX_API_LAYER_NAME_SIZE - 1);
893 if (_layer_name.size() >= XR_MAX_API_LAYER_NAME_SIZE - 1) {
894 props.layerName[XR_MAX_API_LAYER_NAME_SIZE - 1] = '\0';
895 }
896 strncpy(props.description, _description.c_str(), XR_MAX_API_LAYER_DESCRIPTION_SIZE - 1);
897 if (_description.size() >= XR_MAX_API_LAYER_DESCRIPTION_SIZE - 1) {
898 props.description[XR_MAX_API_LAYER_DESCRIPTION_SIZE - 1] = '\0';
899 }
900}
901
902// Find all layer manifest files in the appropriate search paths/registries for the given type.
903XrResult ApiLayerManifestFile::FindManifestFiles(ManifestFileType type,
904 std::vector<std::unique_ptr<ApiLayerManifestFile>> &manifest_files) {
905 std::string relative_path;
906 std::string override_env_var;
907 std::string registry_location;
908
909 // Add the appropriate top-level folders for the relative path. These should be
910 // the string "openxr/" followed by the API major version as a string.
911 relative_path = OPENXR_RELATIVE_PATH;
912 relative_path += std::to_string(XR_VERSION_MAJOR(XR_CURRENT_API_VERSION));
913
914 switch (type) {
915 case MANIFEST_TYPE_IMPLICIT_API_LAYER:
916 relative_path += OPENXR_IMPLICIT_API_LAYER_RELATIVE_PATH;
917 override_env_var = "";
918#ifdef XR_OS_WINDOWS
919 registry_location = OPENXR_IMPLICIT_API_LAYER_REGISTRY_LOCATION;
920#endif
921 break;
922 case MANIFEST_TYPE_EXPLICIT_API_LAYER:
923 relative_path += OPENXR_EXPLICIT_API_LAYER_RELATIVE_PATH;
924 override_env_var = OPENXR_API_LAYER_PATH_ENV_VAR;
925#ifdef XR_OS_WINDOWS
926 registry_location = OPENXR_EXPLICIT_API_LAYER_REGISTRY_LOCATION;
927#endif
928 break;
929 default:
930 LoaderLogger::LogErrorMessage("", "ApiLayerManifestFile::FindManifestFiles - unknown manifest file requested");
931 return XR_ERROR_FILE_ACCESS_ERROR;
932 }
933
934 bool override_active = false;
935 std::vector<std::string> filenames;
936 ReadDataFilesInSearchPaths(override_env_var, relative_path, override_active, filenames);
937
938#ifdef XR_OS_WINDOWS
939 // Read the registry if the override wasn't active.
940 if (!override_active) {
941 ReadLayerDataFilesInRegistry(registry_location, filenames);
942 }
943#endif
944
945 for (std::string &cur_file : filenames) {
946 ApiLayerManifestFile::CreateIfValid(type, cur_file, manifest_files);
947 }
948
949#ifdef XR_USE_PLATFORM_ANDROID
950 ApiLayerManifestFile::AddManifestFilesAndroid(type, manifest_files);
951#endif // XR_USE_PLATFORM_ANDROID
952
953 return XR_SUCCESS;
954}
955