diff options
Diffstat (limited to 'thirdparty/openxr/src/loader/manifest_file.cpp')
| -rw-r--r-- | thirdparty/openxr/src/loader/manifest_file.cpp | 845 | 
1 files changed, 845 insertions, 0 deletions
| diff --git a/thirdparty/openxr/src/loader/manifest_file.cpp b/thirdparty/openxr/src/loader/manifest_file.cpp new file mode 100644 index 0000000000..e4eab3949e --- /dev/null +++ b/thirdparty/openxr/src/loader/manifest_file.cpp @@ -0,0 +1,845 @@ +// Copyright (c) 2017-2022, The Khronos Group Inc. +// Copyright (c) 2017-2019 Valve Corporation +// Copyright (c) 2017-2019 LunarG, Inc. +// +// SPDX-License-Identifier: Apache-2.0 OR MIT +// +// Initial Authors: Mark Young <marky@lunarg.com>, Dave Houlton <daveh@lunarg.com> +// + +#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS) +#define _CRT_SECURE_NO_WARNINGS +#endif  // defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS) + +#include "manifest_file.hpp" + +#ifdef OPENXR_HAVE_COMMON_CONFIG +#include "common_config.h" +#endif  // OPENXR_HAVE_COMMON_CONFIG + +#include "filesystem_utils.hpp" +#include "loader_platform.hpp" +#include "platform_utils.hpp" +#include "loader_logger.hpp" + +#include <json/json.h> +#include <openxr/openxr.h> + +#include <algorithm> +#include <cstring> +#include <fstream> +#include <memory> +#include <sstream> +#include <stdexcept> +#include <stdio.h> +#include <stdlib.h> +#include <string> +#include <unordered_map> +#include <utility> +#include <vector> + +#ifndef FALLBACK_CONFIG_DIRS +#define FALLBACK_CONFIG_DIRS "/etc/xdg" +#endif  // !FALLBACK_CONFIG_DIRS + +#ifndef FALLBACK_DATA_DIRS +#define FALLBACK_DATA_DIRS "/usr/local/share:/usr/share" +#endif  // !FALLBACK_DATA_DIRS + +#ifndef SYSCONFDIR +#define SYSCONFDIR "/etc" +#endif  // !SYSCONFDIR + +#ifdef XRLOADER_DISABLE_EXCEPTION_HANDLING +#if JSON_USE_EXCEPTIONS +#error \ +    "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." +#endif  // JSON_USE_EXCEPTIONS +#endif  // !XRLOADER_DISABLE_EXCEPTION_HANDLING + +#include "runtime_interface.hpp" + +// Utility functions for finding files in the appropriate paths + +static inline bool StringEndsWith(const std::string &value, const std::string &ending) { +    if (ending.size() > value.size()) { +        return false; +    } +    return std::equal(ending.rbegin(), ending.rend(), value.rbegin()); +} + +// If the file found is a manifest file name, add it to the out_files manifest list. +static void AddIfJson(const std::string &full_file, std::vector<std::string> &manifest_files) { +    if (full_file.empty() || !StringEndsWith(full_file, ".json")) { +        return; +    } +    manifest_files.push_back(full_file); +} + +// Check the current path for any manifest files.  If the provided search_path is a directory, look for +// all included JSON files in that directory.  Otherwise, just check the provided search_path which should +// be a single filename. +static void CheckAllFilesInThePath(const std::string &search_path, bool is_directory_list, +                                   std::vector<std::string> &manifest_files) { +    if (FileSysUtilsPathExists(search_path)) { +        std::string absolute_path; +        if (!is_directory_list) { +            // If the file exists, try to add it +            if (FileSysUtilsIsRegularFile(search_path)) { +                FileSysUtilsGetAbsolutePath(search_path, absolute_path); +                AddIfJson(absolute_path, manifest_files); +            } +        } else { +            std::vector<std::string> files; +            if (FileSysUtilsFindFilesInPath(search_path, files)) { +                for (std::string &cur_file : files) { +                    std::string relative_path; +                    FileSysUtilsCombinePaths(search_path, cur_file, relative_path); +                    if (!FileSysUtilsGetAbsolutePath(relative_path, absolute_path)) { +                        continue; +                    } +                    AddIfJson(absolute_path, manifest_files); +                } +            } +        } +    } +} + +// Add all manifest files in the provided paths to the manifest_files list.  If search_path +// is made up of directory listings (versus direct manifest file names) search each path for +// any manifest files. +static void AddFilesInPath(const std::string &search_path, bool is_directory_list, std::vector<std::string> &manifest_files) { +    std::size_t last_found = 0; +    std::size_t found = search_path.find_first_of(PATH_SEPARATOR); +    std::string cur_search; + +    // Handle any path listings in the string (separated by the appropriate path separator) +    while (found != std::string::npos) { +        // substr takes a start index and length. +        std::size_t length = found - last_found; +        cur_search = search_path.substr(last_found, length); + +        CheckAllFilesInThePath(cur_search, is_directory_list, manifest_files); + +        // This works around issue if multiple path separator follow each other directly. +        last_found = found; +        while (found == last_found) { +            last_found = found + 1; +            found = search_path.find_first_of(PATH_SEPARATOR, last_found); +        } +    } + +    // If there's something remaining in the string, copy it over +    if (last_found < search_path.size()) { +        cur_search = search_path.substr(last_found); +        CheckAllFilesInThePath(cur_search, is_directory_list, manifest_files); +    } +} + +// Copy all paths listed in the cur_path string into output_path and append the appropriate relative_path onto the end of each. +static void CopyIncludedPaths(bool is_directory_list, const std::string &cur_path, const std::string &relative_path, +                              std::string &output_path) { +    if (!cur_path.empty()) { +        std::size_t last_found = 0; +        std::size_t found = cur_path.find_first_of(PATH_SEPARATOR); + +        // Handle any path listings in the string (separated by the appropriate path separator) +        while (found != std::string::npos) { +            std::size_t length = found - last_found; +            output_path += cur_path.substr(last_found, length); +            if (is_directory_list && (cur_path[found - 1] != '\\' && cur_path[found - 1] != '/')) { +                output_path += DIRECTORY_SYMBOL; +            } +            output_path += relative_path; +            output_path += PATH_SEPARATOR; + +            last_found = found; +            found = cur_path.find_first_of(PATH_SEPARATOR, found + 1); +        } + +        // If there's something remaining in the string, copy it over +        size_t last_char = cur_path.size() - 1; +        if (last_found != last_char) { +            output_path += cur_path.substr(last_found); +            if (is_directory_list && (cur_path[last_char] != '\\' && cur_path[last_char] != '/')) { +                output_path += DIRECTORY_SYMBOL; +            } +            output_path += relative_path; +            output_path += PATH_SEPARATOR; +        } +    } +} + +// Look for data files in the provided paths, but first check the environment override to determine if we should use that instead. +static void ReadDataFilesInSearchPaths(const std::string &override_env_var, const std::string &relative_path, bool &override_active, +                                       std::vector<std::string> &manifest_files) { +    std::string override_path; +    std::string search_path; + +    if (!override_env_var.empty()) { +        bool permit_override = true; +#ifndef XR_OS_WINDOWS +        if (geteuid() != getuid() || getegid() != getgid()) { +            // Don't allow setuid apps to use the env var +            permit_override = false; +        } +#endif +        if (permit_override) { +            override_path = PlatformUtilsGetSecureEnv(override_env_var.c_str()); +        } +    } + +    if (!override_path.empty()) { +        CopyIncludedPaths(true, override_path, "", search_path); +        override_active = true; +    } else { +        override_active = false; +#if !defined(XR_OS_WINDOWS) && !defined(XR_OS_ANDROID) +        const char home_additional[] = ".local/share/"; + +        // Determine how much space is needed to generate the full search path +        // for the current manifest files. +        std::string xdg_conf_dirs = PlatformUtilsGetSecureEnv("XDG_CONFIG_DIRS"); +        std::string xdg_data_dirs = PlatformUtilsGetSecureEnv("XDG_DATA_DIRS"); +        std::string xdg_data_home = PlatformUtilsGetSecureEnv("XDG_DATA_HOME"); +        std::string home = PlatformUtilsGetSecureEnv("HOME"); + +        if (xdg_conf_dirs.empty()) { +            CopyIncludedPaths(true, FALLBACK_CONFIG_DIRS, relative_path, search_path); +        } else { +            CopyIncludedPaths(true, xdg_conf_dirs, relative_path, search_path); +        } + +        CopyIncludedPaths(true, SYSCONFDIR, relative_path, search_path); +#if defined(EXTRASYSCONFDIR) +        CopyIncludedPaths(true, EXTRASYSCONFDIR, relative_path, search_path); +#endif + +        if (xdg_data_dirs.empty()) { +            CopyIncludedPaths(true, FALLBACK_DATA_DIRS, relative_path, search_path); +        } else { +            CopyIncludedPaths(true, xdg_data_dirs, relative_path, search_path); +        } + +        if (!xdg_data_home.empty()) { +            CopyIncludedPaths(true, xdg_data_home, relative_path, search_path); +        } else if (!home.empty()) { +            std::string relative_home_path = home_additional; +            relative_home_path += relative_path; +            CopyIncludedPaths(true, home, relative_home_path, search_path); +        } +#else +        (void)relative_path; +#endif +    } + +    // Now, parse the paths and add any manifest files found in them. +    AddFilesInPath(search_path, true, manifest_files); +} + +#ifdef XR_OS_LINUX + +// Get an XDG environment variable with a $HOME-relative default +static std::string GetXDGEnvHome(const char *name, const char *fallback_path) { +    std::string result = PlatformUtilsGetSecureEnv(name); +    if (!result.empty()) { +        return result; +    } +    result = PlatformUtilsGetSecureEnv("HOME"); +    if (result.empty()) { +        return result; +    } +    result += "/"; +    result += fallback_path; +    return result; +} + +// Get an XDG environment variable with absolute defaults +static std::string GetXDGEnvAbsolute(const char *name, const char *fallback_paths) { +    std::string result = PlatformUtilsGetSecureEnv(name); +    if (!result.empty()) { +        return result; +    } +    return fallback_paths; +} + +// Return the first instance of relative_path occurring in an XDG config dir according to standard +// precedence order. +static bool FindXDGConfigFile(const std::string &relative_path, std::string &out) { +    out = GetXDGEnvHome("XDG_CONFIG_HOME", ".config"); +    if (!out.empty()) { +        out += "/"; +        out += relative_path; + +        LoaderLogger::LogInfoMessage("", "Looking for " + relative_path + " in XDG_CONFIG_HOME: " + out); +        if (FileSysUtilsPathExists(out)) { +            return true; +        } +    } + +    std::istringstream iss(GetXDGEnvAbsolute("XDG_CONFIG_DIRS", FALLBACK_CONFIG_DIRS)); +    std::string path; +    while (std::getline(iss, path, PATH_SEPARATOR)) { +        if (path.empty()) { +            continue; +        } +        out = path; +        out += "/"; +        out += relative_path; +        LoaderLogger::LogInfoMessage("", "Looking for " + relative_path + " in an entry of XDG_CONFIG_DIRS: " + out); +        if (FileSysUtilsPathExists(out)) { +            return true; +        } +    } + +    out = SYSCONFDIR; +    out += "/"; +    out += relative_path; +    LoaderLogger::LogInfoMessage("", "Looking for " + relative_path + " in compiled-in SYSCONFDIR: " + out); +    if (FileSysUtilsPathExists(out)) { +        return true; +    } + +#if defined(EXTRASYSCONFDIR) +    out = EXTRASYSCONFDIR; +    out += "/"; +    out += relative_path; +    LoaderLogger::LogInfoMessage("", "Looking for " + relative_path + " in compiled-in EXTRASYSCONFDIR: " + out); +    if (FileSysUtilsPathExists(out)) { +        return true; +    } +#endif + +    out.clear(); +    return false; +} + +#endif + +#ifdef XR_OS_WINDOWS + +// Look for runtime data files in the provided paths, but first check the environment override to determine +// if we should use that instead. +static void ReadRuntimeDataFilesInRegistry(const std::string &runtime_registry_location, +                                           const std::string &default_runtime_value_name, +                                           std::vector<std::string> &manifest_files) { +    HKEY hkey; +    DWORD access_flags; +    wchar_t value_w[1024]; +    DWORD value_size_w = sizeof(value_w);  // byte size of the buffer. + +    // Generate the full registry location for the registry information +    std::string full_registry_location = OPENXR_REGISTRY_LOCATION; +    full_registry_location += std::to_string(XR_VERSION_MAJOR(XR_CURRENT_API_VERSION)); +    full_registry_location += runtime_registry_location; + +    const std::wstring full_registry_location_w = utf8_to_wide(full_registry_location); +    const std::wstring default_runtime_value_name_w = utf8_to_wide(default_runtime_value_name); + +    // Use 64 bit regkey for 64bit application, and use 32 bit regkey in WOW for 32bit application. +    access_flags = KEY_QUERY_VALUE; +    LONG open_value = RegOpenKeyExW(HKEY_LOCAL_MACHINE, full_registry_location_w.c_str(), 0, access_flags, &hkey); + +    if (ERROR_SUCCESS != open_value) { +        LoaderLogger::LogWarningMessage("", +                                        "ReadRuntimeDataFilesInRegistry - failed to open registry key " + full_registry_location); +    } else if (ERROR_SUCCESS != RegGetValueW(hkey, nullptr, default_runtime_value_name_w.c_str(), +                                             RRF_RT_REG_SZ | REG_EXPAND_SZ | RRF_ZEROONFAILURE, NULL, +                                             reinterpret_cast<LPBYTE>(&value_w), &value_size_w)) { +        LoaderLogger::LogWarningMessage( +            "", "ReadRuntimeDataFilesInRegistry - failed to read registry value " + default_runtime_value_name); +    } else { +        AddFilesInPath(wide_to_utf8(value_w), false, manifest_files); +    } +} + +// Look for layer data files in the provided paths, but first check the environment override to determine +// if we should use that instead. +static void ReadLayerDataFilesInRegistry(const std::string ®istry_location, std::vector<std::string> &manifest_files) { +    const std::wstring full_registry_location_w = +        utf8_to_wide(OPENXR_REGISTRY_LOCATION + std::to_string(XR_VERSION_MAJOR(XR_CURRENT_API_VERSION)) + registry_location); + +    auto ReadLayerDataFilesInHive = [&](HKEY hive) { +        HKEY hkey; +        LONG open_value = RegOpenKeyExW(hive, full_registry_location_w.c_str(), 0, KEY_QUERY_VALUE, &hkey); +        if (ERROR_SUCCESS != open_value) { +            return false; +        } + +        wchar_t name_w[1024]{}; +        LONG rtn_value; +        DWORD name_size = 1023; +        DWORD value; +        DWORD value_size = sizeof(value); +        DWORD key_index = 0; +        while (ERROR_SUCCESS == +               (rtn_value = RegEnumValueW(hkey, key_index++, name_w, &name_size, NULL, NULL, (LPBYTE)&value, &value_size))) { +            if (value_size == sizeof(value) && value == 0) { +                const std::string filename = wide_to_utf8(name_w); +                AddFilesInPath(filename, false, manifest_files); +            } +            // Reset some items for the next loop +            name_size = 1023; +        } + +        RegCloseKey(hkey); + +        return true; +    }; + +    // Do not allow high integrity processes to act on data that can be controlled by medium integrity processes. +    const bool readFromCurrentUser = !IsHighIntegrityLevel(); + +    bool found = ReadLayerDataFilesInHive(HKEY_LOCAL_MACHINE); +    if (readFromCurrentUser) { +        found |= ReadLayerDataFilesInHive(HKEY_CURRENT_USER); +    } + +    if (!found) { +        std::string warning_message = "ReadLayerDataFilesInRegistry - failed to read registry location "; +        warning_message += registry_location; +        warning_message += (readFromCurrentUser ? " in either HKEY_LOCAL_MACHINE or HKEY_CURRENT_USER" : " in HKEY_LOCAL_MACHINE"); +        LoaderLogger::LogWarningMessage("", warning_message); +    } +} + +#endif  // XR_OS_WINDOWS + +ManifestFile::ManifestFile(ManifestFileType type, const std::string &filename, const std::string &library_path) +    : _filename(filename), _type(type), _library_path(library_path) {} + +bool ManifestFile::IsValidJson(const Json::Value &root_node, JsonVersion &version) { +    if (root_node["file_format_version"].isNull() || !root_node["file_format_version"].isString()) { +        LoaderLogger::LogErrorMessage("", "ManifestFile::IsValidJson - JSON file missing \"file_format_version\""); +        return false; +    } +    std::string file_format = root_node["file_format_version"].asString(); +    const int num_fields = sscanf(file_format.c_str(), "%u.%u.%u", &version.major, &version.minor, &version.patch); + +    // Only version 1.0.0 is defined currently.  Eventually we may have more version, but +    // some of the versions may only be valid for layers or runtimes specifically. +    if (num_fields != 3 || version.major != 1 || version.minor != 0 || version.patch != 0) { +        std::ostringstream error_ss; +        error_ss << "ManifestFile::IsValidJson - JSON \"file_format_version\" " << version.major << "." << version.minor << "." +                 << version.patch << " is not supported"; +        LoaderLogger::LogErrorMessage("", error_ss.str()); +        return false; +    } + +    return true; +} + +static void GetExtensionProperties(const std::vector<ExtensionListing> &extensions, std::vector<XrExtensionProperties> &props) { +    for (const auto &ext : extensions) { +        auto it = +            std::find_if(props.begin(), props.end(), [&](XrExtensionProperties &prop) { return prop.extensionName == ext.name; }); +        if (it != props.end()) { +            it->extensionVersion = std::max(it->extensionVersion, ext.extension_version); +        } else { +            XrExtensionProperties prop = {}; +            prop.type = XR_TYPE_EXTENSION_PROPERTIES; +            prop.next = nullptr; +            strncpy(prop.extensionName, ext.name.c_str(), XR_MAX_EXTENSION_NAME_SIZE - 1); +            prop.extensionName[XR_MAX_EXTENSION_NAME_SIZE - 1] = '\0'; +            prop.extensionVersion = ext.extension_version; +            props.push_back(prop); +        } +    } +} + +// Return any instance extensions found in the manifest files in the proper form for +// OpenXR (XrExtensionProperties). +void ManifestFile::GetInstanceExtensionProperties(std::vector<XrExtensionProperties> &props) { +    GetExtensionProperties(_instance_extensions, props); +} + +const std::string &ManifestFile::GetFunctionName(const std::string &func_name) const { +    if (!_functions_renamed.empty()) { +        auto found = _functions_renamed.find(func_name); +        if (found != _functions_renamed.end()) { +            return found->second; +        } +    } +    return func_name; +} + +RuntimeManifestFile::RuntimeManifestFile(const std::string &filename, const std::string &library_path) +    : ManifestFile(MANIFEST_TYPE_RUNTIME, filename, library_path) {} + +static void ParseExtension(Json::Value const &ext, std::vector<ExtensionListing> &extensions) { +    Json::Value ext_name = ext["name"]; +    Json::Value ext_version = ext["extension_version"]; + +    // Allow "extension_version" as a String or a UInt to maintain backwards compatibility, even though it should be a String. +    // Internal Issue 1411: https://gitlab.khronos.org/openxr/openxr/-/issues/1411 +    // Internal MR !1867: https://gitlab.khronos.org/openxr/openxr/-/merge_requests/1867 +    if (ext_name.isString() && (ext_version.isString() || ext_version.isUInt())) { +        ExtensionListing ext_listing = {}; +        ext_listing.name = ext_name.asString(); +        if (ext_version.isUInt()) { +            ext_listing.extension_version = ext_version.asUInt(); +        } else { +            ext_listing.extension_version = atoi(ext_version.asString().c_str()); +        } +        extensions.push_back(ext_listing); +    } +} + +void ManifestFile::ParseCommon(Json::Value const &root_node) { +    const Json::Value &inst_exts = root_node["instance_extensions"]; +    if (!inst_exts.isNull() && inst_exts.isArray()) { +        for (const auto &ext : inst_exts) { +            ParseExtension(ext, _instance_extensions); +        } +    } +    const Json::Value &funcs_renamed = root_node["functions"]; +    if (!funcs_renamed.isNull() && !funcs_renamed.empty()) { +        for (Json::ValueConstIterator func_it = funcs_renamed.begin(); func_it != funcs_renamed.end(); ++func_it) { +            if (!(*func_it).isString()) { +                LoaderLogger::LogWarningMessage( +                    "", "ManifestFile::ParseCommon " + _filename + " \"functions\" section contains non-string values."); +                continue; +            } +            std::string original_name = func_it.key().asString(); +            std::string new_name = (*func_it).asString(); +            _functions_renamed.emplace(original_name, new_name); +        } +    } +} + +void RuntimeManifestFile::CreateIfValid(std::string const &filename, +                                        std::vector<std::unique_ptr<RuntimeManifestFile>> &manifest_files) { +    std::ifstream json_stream(filename, std::ifstream::in); + +    LoaderLogger::LogInfoMessage("", "RuntimeManifestFile::CreateIfValid - attempting to load " + filename); +    std::ostringstream error_ss("RuntimeManifestFile::CreateIfValid "); +    if (!json_stream.is_open()) { +        error_ss << "failed to open " << filename << ".  Does it exist?"; +        LoaderLogger::LogErrorMessage("", error_ss.str()); +        return; +    } +    Json::CharReaderBuilder builder; +    std::string errors; +    Json::Value root_node = Json::nullValue; +    if (!Json::parseFromStream(builder, json_stream, &root_node, &errors) || !root_node.isObject()) { +        error_ss << "failed to parse " << filename << "."; +        if (!errors.empty()) { +            error_ss << " (Error message: " << errors << ")"; +        } +        error_ss << " Is it a valid runtime manifest file?"; +        LoaderLogger::LogErrorMessage("", error_ss.str()); +        return; +    } + +    CreateIfValid(root_node, filename, manifest_files); +} + +void RuntimeManifestFile::CreateIfValid(const Json::Value &root_node, const std::string &filename, +                                        std::vector<std::unique_ptr<RuntimeManifestFile>> &manifest_files) { +    std::ostringstream error_ss("RuntimeManifestFile::CreateIfValid "); +    JsonVersion file_version = {}; +    if (!ManifestFile::IsValidJson(root_node, file_version)) { +        error_ss << "isValidJson indicates " << filename << " is not a valid manifest file."; +        LoaderLogger::LogErrorMessage("", error_ss.str()); +        return; +    } +    const Json::Value &runtime_root_node = root_node["runtime"]; +    // The Runtime manifest file needs the "runtime" root as well as a sub-node for "library_path".  If any of those aren't there, +    // fail. +    if (runtime_root_node.isNull() || runtime_root_node["library_path"].isNull() || !runtime_root_node["library_path"].isString()) { +        error_ss << filename << " is missing required fields.  Verify all proper fields exist."; +        LoaderLogger::LogErrorMessage("", error_ss.str()); +        return; +    } + +    std::string lib_path = runtime_root_node["library_path"].asString(); + +    // If the library_path variable has no directory symbol, it's just a file name and should be accessible on the +    // global library path. +    if (lib_path.find('\\') != std::string::npos || lib_path.find('/') != std::string::npos) { +        // If the library_path is an absolute path, just use that if it exists +        if (FileSysUtilsIsAbsolutePath(lib_path)) { +            if (!FileSysUtilsPathExists(lib_path)) { +                error_ss << filename << " library " << lib_path << " does not appear to exist"; +                LoaderLogger::LogErrorMessage("", error_ss.str()); +                return; +            } +        } else { +            // Otherwise, treat the library path as a relative path based on the JSON file. +            std::string canonical_path; +            std::string combined_path; +            std::string file_parent; +            // Search relative to the real manifest file, not relative to the symlink +            if (!FileSysUtilsGetCanonicalPath(filename, canonical_path)) { +                // Give relative to the non-canonical path a chance +                canonical_path = filename; +            } +            if (!FileSysUtilsGetParentPath(canonical_path, file_parent) || +                !FileSysUtilsCombinePaths(file_parent, lib_path, combined_path) || !FileSysUtilsPathExists(combined_path)) { +                error_ss << filename << " library " << combined_path << " does not appear to exist"; +                LoaderLogger::LogErrorMessage("", error_ss.str()); +                return; +            } +            lib_path = combined_path; +        } +    } + +    // Add this runtime manifest file +    manifest_files.emplace_back(new RuntimeManifestFile(filename, lib_path)); + +    // Add any extensions to it after the fact. +    // Handle any renamed functions +    manifest_files.back()->ParseCommon(runtime_root_node); +} + +// Find all manifest files in the appropriate search paths/registries for the given type. +XrResult RuntimeManifestFile::FindManifestFiles(std::vector<std::unique_ptr<RuntimeManifestFile>> &manifest_files) { +    XrResult result = XR_SUCCESS; +    std::string filename = PlatformUtilsGetSecureEnv(OPENXR_RUNTIME_JSON_ENV_VAR); +    if (!filename.empty()) { +        LoaderLogger::LogInfoMessage( +            "", "RuntimeManifestFile::FindManifestFiles - using environment variable override runtime file " + filename); +    } else { +#ifdef XR_OS_WINDOWS +        std::vector<std::string> filenames; +        ReadRuntimeDataFilesInRegistry("", "ActiveRuntime", filenames); +        if (filenames.size() == 0) { +            LoaderLogger::LogErrorMessage( +                "", "RuntimeManifestFile::FindManifestFiles - failed to find active runtime file in registry"); +            return XR_ERROR_RUNTIME_UNAVAILABLE; +        } +        if (filenames.size() > 1) { +            LoaderLogger::LogWarningMessage( +                "", "RuntimeManifestFile::FindManifestFiles - found too many default runtime files in registry"); +        } +        filename = filenames[0]; +        LoaderLogger::LogInfoMessage("", +                                     "RuntimeManifestFile::FindManifestFiles - using registry-specified runtime file " + filename); +#elif defined(XR_OS_LINUX) +        const std::string relative_path = +            "openxr/" + std::to_string(XR_VERSION_MAJOR(XR_CURRENT_API_VERSION)) + "/active_runtime.json"; +        if (!FindXDGConfigFile(relative_path, filename)) { +            LoaderLogger::LogErrorMessage( +                "", "RuntimeManifestFile::FindManifestFiles - failed to determine active runtime file path for this environment"); +            return XR_ERROR_RUNTIME_UNAVAILABLE; +        } +#else + +#if defined(XR_KHR_LOADER_INIT_SUPPORT) +        Json::Value virtualManifest; +        result = GetPlatformRuntimeVirtualManifest(virtualManifest); +        if (XR_SUCCESS == result) { +            RuntimeManifestFile::CreateIfValid(virtualManifest, "", manifest_files); +            return result; +        } +#endif  // defined(XR_KHR_LOADER_INIT_SUPPORT) +        if (!PlatformGetGlobalRuntimeFileName(XR_VERSION_MAJOR(XR_CURRENT_API_VERSION), filename)) { +            LoaderLogger::LogErrorMessage( +                "", "RuntimeManifestFile::FindManifestFiles - failed to determine active runtime file path for this environment"); +            return XR_ERROR_RUNTIME_UNAVAILABLE; +        } +        result = XR_SUCCESS; +        LoaderLogger::LogInfoMessage("", "RuntimeManifestFile::FindManifestFiles - using global runtime file " + filename); +#endif +    } +    RuntimeManifestFile::CreateIfValid(filename, manifest_files); + +    return result; +} + +ApiLayerManifestFile::ApiLayerManifestFile(ManifestFileType type, const std::string &filename, const std::string &layer_name, +                                           const std::string &description, const JsonVersion &api_version, +                                           const uint32_t &implementation_version, const std::string &library_path) +    : ManifestFile(type, filename, library_path), +      _api_version(api_version), +      _layer_name(layer_name), +      _description(description), +      _implementation_version(implementation_version) {} + +void ApiLayerManifestFile::CreateIfValid(ManifestFileType type, const std::string &filename, +                                         std::vector<std::unique_ptr<ApiLayerManifestFile>> &manifest_files) { +    std::ifstream json_stream(filename, std::ifstream::in); + +    std::ostringstream error_ss("ApiLayerManifestFile::CreateIfValid "); +    if (!json_stream.is_open()) { +        error_ss << "failed to open " << filename << ".  Does it exist?"; +        LoaderLogger::LogErrorMessage("", error_ss.str()); +        return; +    } + +    Json::CharReaderBuilder builder; +    std::string errors; +    Json::Value root_node = Json::nullValue; +    if (!Json::parseFromStream(builder, json_stream, &root_node, &errors) || !root_node.isObject()) { +        error_ss << "failed to parse " << filename << "."; +        if (!errors.empty()) { +            error_ss << " (Error message: " << errors << ")"; +        } +        error_ss << " Is it a valid layer manifest file?"; +        LoaderLogger::LogErrorMessage("", error_ss.str()); +        return; +    } +    JsonVersion file_version = {}; +    if (!ManifestFile::IsValidJson(root_node, file_version)) { +        error_ss << "isValidJson indicates " << filename << " is not a valid manifest file."; +        LoaderLogger::LogErrorMessage("", error_ss.str()); +        return; +    } + +    Json::Value layer_root_node = root_node["api_layer"]; + +    // The API Layer manifest file needs the "api_layer" root as well as other sub-nodes. +    // If any of those aren't there, fail. +    if (layer_root_node.isNull() || layer_root_node["name"].isNull() || !layer_root_node["name"].isString() || +        layer_root_node["api_version"].isNull() || !layer_root_node["api_version"].isString() || +        layer_root_node["library_path"].isNull() || !layer_root_node["library_path"].isString() || +        layer_root_node["implementation_version"].isNull() || !layer_root_node["implementation_version"].isString()) { +        error_ss << filename << " is missing required fields.  Verify all proper fields exist."; +        LoaderLogger::LogErrorMessage("", error_ss.str()); +        return; +    } +    if (MANIFEST_TYPE_IMPLICIT_API_LAYER == type) { +        bool enabled = true; +        // Implicit layers require the disable environment variable. +        if (layer_root_node["disable_environment"].isNull() || !layer_root_node["disable_environment"].isString()) { +            error_ss << "Implicit layer " << filename << " is missing \"disable_environment\""; +            LoaderLogger::LogErrorMessage("", error_ss.str()); +            return; +        } +        // Check if there's an enable environment variable provided +        if (!layer_root_node["enable_environment"].isNull() && layer_root_node["enable_environment"].isString()) { +            std::string env_var = layer_root_node["enable_environment"].asString(); +            // If it's not set in the environment, disable the layer +            if (!PlatformUtilsGetEnvSet(env_var.c_str())) { +                enabled = false; +            } +        } +        // Check for the disable environment variable, which must be provided in the JSON +        std::string env_var = layer_root_node["disable_environment"].asString(); +        // If the env var is set, disable the layer. Disable env var overrides enable above +        if (PlatformUtilsGetEnvSet(env_var.c_str())) { +            enabled = false; +        } + +        // Not enabled, so pretend like it isn't even there. +        if (!enabled) { +            error_ss << "Implicit layer " << filename << " is disabled"; +            LoaderLogger::LogInfoMessage("", error_ss.str()); +            return; +        } +    } +    std::string layer_name = layer_root_node["name"].asString(); +    std::string api_version_string = layer_root_node["api_version"].asString(); +    JsonVersion api_version = {}; +    const int num_fields = sscanf(api_version_string.c_str(), "%u.%u", &api_version.major, &api_version.minor); +    api_version.patch = 0; + +    if ((num_fields != 2) || (api_version.major == 0 && api_version.minor == 0) || +        api_version.major > XR_VERSION_MAJOR(XR_CURRENT_API_VERSION)) { +        error_ss << "layer " << filename << " has invalid API Version.  Skipping layer."; +        LoaderLogger::LogWarningMessage("", error_ss.str()); +        return; +    } + +    uint32_t implementation_version = atoi(layer_root_node["implementation_version"].asString().c_str()); +    std::string library_path = layer_root_node["library_path"].asString(); + +    // If the library_path variable has no directory symbol, it's just a file name and should be accessible on the +    // global library path. +    if (library_path.find('\\') != std::string::npos || library_path.find('/') != std::string::npos) { +        // If the library_path is an absolute path, just use that if it exists +        if (FileSysUtilsIsAbsolutePath(library_path)) { +            if (!FileSysUtilsPathExists(library_path)) { +                error_ss << filename << " library " << library_path << " does not appear to exist"; +                LoaderLogger::LogErrorMessage("", error_ss.str()); +                return; +            } +        } else { +            // Otherwise, treat the library path as a relative path based on the JSON file. +            std::string combined_path; +            std::string file_parent; +            if (!FileSysUtilsGetParentPath(filename, file_parent) || +                !FileSysUtilsCombinePaths(file_parent, library_path, combined_path) || !FileSysUtilsPathExists(combined_path)) { +                error_ss << filename << " library " << combined_path << " does not appear to exist"; +                LoaderLogger::LogErrorMessage("", error_ss.str()); +                return; +            } +            library_path = combined_path; +        } +    } + +    std::string description; +    if (!layer_root_node["description"].isNull() && layer_root_node["description"].isString()) { +        description = layer_root_node["description"].asString(); +    } + +    // Add this layer manifest file +    manifest_files.emplace_back( +        new ApiLayerManifestFile(type, filename, layer_name, description, api_version, implementation_version, library_path)); + +    // Add any extensions to it after the fact. +    manifest_files.back()->ParseCommon(layer_root_node); +} + +void ApiLayerManifestFile::PopulateApiLayerProperties(XrApiLayerProperties &props) const { +    props.layerVersion = _implementation_version; +    props.specVersion = XR_MAKE_VERSION(_api_version.major, _api_version.minor, _api_version.patch); +    strncpy(props.layerName, _layer_name.c_str(), XR_MAX_API_LAYER_NAME_SIZE - 1); +    if (_layer_name.size() >= XR_MAX_API_LAYER_NAME_SIZE - 1) { +        props.layerName[XR_MAX_API_LAYER_NAME_SIZE - 1] = '\0'; +    } +    strncpy(props.description, _description.c_str(), XR_MAX_API_LAYER_DESCRIPTION_SIZE - 1); +    if (_description.size() >= XR_MAX_API_LAYER_DESCRIPTION_SIZE - 1) { +        props.description[XR_MAX_API_LAYER_DESCRIPTION_SIZE - 1] = '\0'; +    } +} + +// Find all layer manifest files in the appropriate search paths/registries for the given type. +XrResult ApiLayerManifestFile::FindManifestFiles(ManifestFileType type, +                                                 std::vector<std::unique_ptr<ApiLayerManifestFile>> &manifest_files) { +    std::string relative_path; +    std::string override_env_var; +    std::string registry_location; + +    // Add the appropriate top-level folders for the relative path.  These should be +    // the string "openxr/" followed by the API major version as a string. +    relative_path = OPENXR_RELATIVE_PATH; +    relative_path += std::to_string(XR_VERSION_MAJOR(XR_CURRENT_API_VERSION)); + +    switch (type) { +        case MANIFEST_TYPE_IMPLICIT_API_LAYER: +            relative_path += OPENXR_IMPLICIT_API_LAYER_RELATIVE_PATH; +            override_env_var = ""; +#ifdef XR_OS_WINDOWS +            registry_location = OPENXR_IMPLICIT_API_LAYER_REGISTRY_LOCATION; +#endif +            break; +        case MANIFEST_TYPE_EXPLICIT_API_LAYER: +            relative_path += OPENXR_EXPLICIT_API_LAYER_RELATIVE_PATH; +            override_env_var = OPENXR_API_LAYER_PATH_ENV_VAR; +#ifdef XR_OS_WINDOWS +            registry_location = OPENXR_EXPLICIT_API_LAYER_REGISTRY_LOCATION; +#endif +            break; +        default: +            LoaderLogger::LogErrorMessage("", "ApiLayerManifestFile::FindManifestFiles - unknown manifest file requested"); +            return XR_ERROR_FILE_ACCESS_ERROR; +    } + +    bool override_active = false; +    std::vector<std::string> filenames; +    ReadDataFilesInSearchPaths(override_env_var, relative_path, override_active, filenames); + +#ifdef XR_OS_WINDOWS +    // Read the registry if the override wasn't active. +    if (!override_active) { +        ReadLayerDataFilesInRegistry(registry_location, filenames); +    } +#endif + +    for (std::string &cur_file : filenames) { +        ApiLayerManifestFile::CreateIfValid(type, cur_file, manifest_files); +    } + +    return XR_SUCCESS; +} |