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#pragma once
11
12#include "xr_dependencies.h"
13#include <string>
14#include <stdint.h>
15#include <stdlib.h>
16
17// OpenXR paths and registry key locations
18#define OPENXR_RELATIVE_PATH "openxr/"
19#define OPENXR_IMPLICIT_API_LAYER_RELATIVE_PATH "/api_layers/implicit.d"
20#define OPENXR_EXPLICIT_API_LAYER_RELATIVE_PATH "/api_layers/explicit.d"
21#ifdef XR_OS_WINDOWS
22#define OPENXR_REGISTRY_LOCATION "SOFTWARE\\Khronos\\OpenXR\\"
23#define OPENXR_IMPLICIT_API_LAYER_REGISTRY_LOCATION "\\ApiLayers\\Implicit"
24#define OPENXR_EXPLICIT_API_LAYER_REGISTRY_LOCATION "\\ApiLayers\\Explicit"
25#endif
26
27// OpenXR Loader environment variables of interest
28#define OPENXR_RUNTIME_JSON_ENV_VAR "XR_RUNTIME_JSON"
29#define OPENXR_API_LAYER_PATH_ENV_VAR "XR_API_LAYER_PATH"
30
31// This is a CMake generated file with #defines for any functions/includes
32// that it found present and build-time configuration.
33// If you don't have this file, on non-Windows you'll need to define
34// one of HAVE_SECURE_GETENV or HAVE___SECURE_GETENV depending on which
35// of secure_getenv or __secure_getenv are present
36#ifdef OPENXR_HAVE_COMMON_CONFIG
37#include "common_config.h"
38#endif // OPENXR_HAVE_COMMON_CONFIG
39
40// Consumers of this file must ensure this function is implemented. For example, the loader will implement this function so that it
41// can route messages through the loader's logging system.
42void LogPlatformUtilsError(const std::string& message);
43
44// Environment variables
45#if defined(XR_OS_LINUX) || defined(XR_OS_APPLE)
46
47#include <unistd.h>
48#include <fcntl.h>
49#include <iostream>
50
51namespace detail {
52
53static inline char* ImplGetEnv(const char* name) { return getenv(name); }
54
55static inline int ImplSetEnv(const char* name, const char* value, int overwrite) { return setenv(name, value, overwrite); }
56
57static inline char* ImplGetSecureEnv(const char* name) {
58#ifdef HAVE_SECURE_GETENV
59 return secure_getenv(name);
60#elif defined(HAVE___SECURE_GETENV)
61 return __secure_getenv(name);
62#else
63// clang-format off
64#pragma message( \
65 "Warning: Falling back to non-secure getenv for environmental" \
66 "lookups! Consider updating to a different libc.")
67 // clang-format on
68
69 return ImplGetEnv(name);
70#endif
71}
72} // namespace detail
73
74#endif // defined(XR_OS_LINUX) || defined(XR_OS_APPLE)
75#if defined(XR_OS_LINUX)
76
77static inline std::string PlatformUtilsGetEnv(const char* name) {
78 auto str = detail::ImplGetEnv(name);
79 if (str == nullptr) {
80 return {};
81 }
82 return str;
83}
84
85static inline std::string PlatformUtilsGetSecureEnv(const char* name) {
86 auto str = detail::ImplGetSecureEnv(name);
87 if (str == nullptr) {
88 str = detail::ImplGetEnv(name);
89 if (str != nullptr && !std::string(str).empty()) {
90 LogPlatformUtilsError(std::string("!!! WARNING !!! Environment variable ") + name +
91 " is being ignored due to running with secure execution. The value '" + str +
92 "' will NOT be used.");
93 }
94 return {};
95 }
96 return str;
97}
98
99static inline bool PlatformUtilsGetEnvSet(const char* name) { return detail::ImplGetEnv(name) != nullptr; }
100
101static inline bool PlatformUtilsSetEnv(const char* name, const char* value) {
102 const int shouldOverwrite = 1;
103 int result = detail::ImplSetEnv(name, value, shouldOverwrite);
104 return (result == 0);
105}
106
107#elif defined(XR_OS_APPLE)
108
109static inline std::string PlatformUtilsGetEnv(const char* name) {
110 auto str = detail::ImplGetEnv(name);
111 if (str == nullptr) {
112 return {};
113 }
114 return str;
115}
116
117static inline std::string PlatformUtilsGetSecureEnv(const char* name) {
118 auto str = detail::ImplGetSecureEnv(name);
119 if (str == nullptr) {
120 return {};
121 }
122 return str;
123}
124
125static inline bool PlatformUtilsGetEnvSet(const char* name) { return detail::ImplGetEnv(name) != nullptr; }
126
127static inline bool PlatformUtilsSetEnv(const char* name, const char* value) {
128 const int shouldOverwrite = 1;
129 int result = detail::ImplSetEnv(name, value, shouldOverwrite);
130 return (result == 0);
131}
132
133// Prefix for the Apple global runtime JSON file name
134static const std::string rt_dir_prefix = "/usr/local/share/openxr/";
135static const std::string rt_filename = "/active_runtime.json";
136
137static inline bool PlatformGetGlobalRuntimeFileName(uint16_t major_version, std::string& file_name) {
138 file_name = rt_dir_prefix;
139 file_name += std::to_string(major_version);
140 file_name += rt_filename;
141 return true;
142}
143
144#elif defined(XR_OS_WINDOWS)
145
146inline std::wstring utf8_to_wide(const std::string& utf8Text) {
147 if (utf8Text.empty()) {
148 return {};
149 }
150
151 std::wstring wideText;
152 const int wideLength = ::MultiByteToWideChar(CP_UTF8, 0, utf8Text.data(), (int)utf8Text.size(), nullptr, 0);
153 if (wideLength == 0) {
154 LogPlatformUtilsError("utf8_to_wide get size error: " + std::to_string(::GetLastError()));
155 return {};
156 }
157
158 // MultiByteToWideChar returns number of chars of the input buffer, regardless of null terminitor
159 wideText.resize(wideLength, 0);
160 wchar_t* wideString = const_cast<wchar_t*>(wideText.data()); // mutable data() only exists in c++17
161 const int length = ::MultiByteToWideChar(CP_UTF8, 0, utf8Text.data(), (int)utf8Text.size(), wideString, wideLength);
162 if (length != wideLength) {
163 LogPlatformUtilsError("utf8_to_wide convert string error: " + std::to_string(::GetLastError()));
164 return {};
165 }
166
167 return wideText;
168}
169
170inline std::string wide_to_utf8(const std::wstring& wideText) {
171 if (wideText.empty()) {
172 return {};
173 }
174
175 std::string narrowText;
176 int narrowLength = ::WideCharToMultiByte(CP_UTF8, 0, wideText.data(), (int)wideText.size(), nullptr, 0, nullptr, nullptr);
177 if (narrowLength == 0) {
178 LogPlatformUtilsError("wide_to_utf8 get size error: " + std::to_string(::GetLastError()));
179 return {};
180 }
181
182 // WideCharToMultiByte returns number of chars of the input buffer, regardless of null terminitor
183 narrowText.resize(narrowLength, 0);
184 char* narrowString = const_cast<char*>(narrowText.data()); // mutable data() only exists in c++17
185 const int length =
186 ::WideCharToMultiByte(CP_UTF8, 0, wideText.data(), (int)wideText.size(), narrowString, narrowLength, nullptr, nullptr);
187 if (length != narrowLength) {
188 LogPlatformUtilsError("wide_to_utf8 convert string error: " + std::to_string(::GetLastError()));
189 return {};
190 }
191
192 return narrowText;
193}
194
195// Returns true if the current process has an integrity level > SECURITY_MANDATORY_MEDIUM_RID.
196static inline bool IsHighIntegrityLevel() {
197 // Execute this check once and save the value as a static bool.
198 static bool isHighIntegrityLevel = ([] {
199 HANDLE processToken;
200 if (OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY | TOKEN_QUERY_SOURCE, &processToken)) {
201 // Maximum possible size of SID_AND_ATTRIBUTES is maximum size of a SID + size of attributes DWORD.
202 uint8_t mandatoryLabelBuffer[SECURITY_MAX_SID_SIZE + sizeof(DWORD)]{};
203 DWORD bufferSize;
204 if (GetTokenInformation(processToken, TokenIntegrityLevel, mandatoryLabelBuffer, sizeof(mandatoryLabelBuffer),
205 &bufferSize) != 0) {
206 const auto mandatoryLabel = reinterpret_cast<const TOKEN_MANDATORY_LABEL*>(mandatoryLabelBuffer);
207 if (mandatoryLabel->Label.Sid != 0) {
208 const DWORD subAuthorityCount = *GetSidSubAuthorityCount(mandatoryLabel->Label.Sid);
209 const DWORD integrityLevel = *GetSidSubAuthority(mandatoryLabel->Label.Sid, subAuthorityCount - 1);
210 CloseHandle(processToken);
211 return integrityLevel > SECURITY_MANDATORY_MEDIUM_RID;
212 }
213 }
214
215 CloseHandle(processToken);
216 }
217
218 return false;
219 })();
220
221 return isHighIntegrityLevel;
222}
223
224// Returns true if the given environment variable exists.
225// The name is a case-sensitive UTF8 string.
226static inline bool PlatformUtilsGetEnvSet(const char* name) {
227 const std::wstring wname = utf8_to_wide(name);
228 const DWORD valSize = ::GetEnvironmentVariableW(wname.c_str(), nullptr, 0);
229 // GetEnvironmentVariable returns 0 when environment variable does not exist or there is an error.
230 return 0 != valSize;
231}
232
233// Returns the environment variable value for the given name.
234// Returns an empty string if the environment variable doesn't exist or if it exists but is empty.
235// Use PlatformUtilsGetEnvSet to tell if it exists.
236// The name is a case-sensitive UTF8 string.
237static inline std::string PlatformUtilsGetEnv(const char* name) {
238 const std::wstring wname = utf8_to_wide(name);
239 const DWORD valSize = ::GetEnvironmentVariableW(wname.c_str(), nullptr, 0);
240 // GetEnvironmentVariable returns 0 when environment variable does not exist or there is an error.
241 // The size includes the null-terminator, so a size of 1 is means the variable was explicitly set to empty.
242 if (valSize == 0 || valSize == 1) {
243 return {};
244 }
245
246 // GetEnvironmentVariable returns size including null terminator for "query size" call.
247 std::wstring wValue(valSize, 0);
248 wchar_t* wValueData = &wValue[0];
249
250 // GetEnvironmentVariable returns string length, excluding null terminator for "get value"
251 // call if there was enough capacity. Else it returns the required capacity (including null terminator).
252 const DWORD length = ::GetEnvironmentVariableW(wname.c_str(), wValueData, (DWORD)wValue.size());
253 if ((length == 0) || (length >= wValue.size())) { // If error or the variable increased length between calls...
254 LogPlatformUtilsError("GetEnvironmentVariable get value error: " + std::to_string(::GetLastError()));
255 return {};
256 }
257
258 wValue.resize(length); // Strip the null terminator.
259
260 return wide_to_utf8(wValue);
261}
262
263// Acts the same as PlatformUtilsGetEnv except returns an empty string if IsHighIntegrityLevel.
264static inline std::string PlatformUtilsGetSecureEnv(const char* name) {
265 // No secure version for Windows so the below integrity check is needed.
266 const std::string envValue = PlatformUtilsGetEnv(name);
267
268 // Do not allow high integrity processes to act on data that can be controlled by medium integrity processes.
269 if (IsHighIntegrityLevel()) {
270 if (!envValue.empty()) {
271 LogPlatformUtilsError(std::string("!!! WARNING !!! Environment variable ") + name +
272 " is being ignored due to running from an elevated context. The value '" + envValue +
273 "' will NOT be used.");
274 }
275 return {};
276 }
277
278 return envValue;
279}
280
281// Sets an environment variable via UTF8 strings.
282// The name is case-sensitive.
283// Overwrites the variable if it already exists.
284// Returns true if it could be set.
285static inline bool PlatformUtilsSetEnv(const char* name, const char* value) {
286 const std::wstring wname = utf8_to_wide(name);
287 const std::wstring wvalue = utf8_to_wide(value);
288 BOOL result = ::SetEnvironmentVariableW(wname.c_str(), wvalue.c_str());
289 return (result != 0);
290}
291
292#elif defined(XR_OS_ANDROID)
293
294static inline bool PlatformUtilsGetEnvSet(const char* /* name */) {
295 // Stub func
296 return false;
297}
298
299static inline std::string PlatformUtilsGetEnv(const char* /* name */) {
300 // Stub func
301 return {};
302}
303
304static inline std::string PlatformUtilsGetSecureEnv(const char* /* name */) {
305 // Stub func
306 return {};
307}
308
309static inline bool PlatformUtilsSetEnv(const char* /* name */, const char* /* value */) {
310 // Stub func
311 return false;
312}
313
314#include <sys/stat.h>
315
316// Intended to be only used as a fallback on Android, with a more open, "native" technique used in most cases
317static inline bool PlatformGetGlobalRuntimeFileName(uint16_t major_version, std::string& file_name) {
318 // Prefix for the runtime JSON file name
319 static const char* rt_dir_prefixes[] = {"/product", "/odm", "/oem", "/vendor", "/system"};
320 static const std::string rt_filename = "/active_runtime.json";
321 static const std::string subdir = "/etc/openxr/";
322 for (const auto prefix : rt_dir_prefixes) {
323 auto path = prefix + subdir + std::to_string(major_version) + rt_filename;
324 struct stat buf;
325 if (0 == stat(path.c_str(), &buf)) {
326 file_name = path;
327 return true;
328 }
329 }
330 return false;
331}
332#else // Not Linux, Apple, nor Windows
333
334static inline bool PlatformUtilsGetEnvSet(const char* /* name */) {
335 // Stub func
336 return false;
337}
338
339static inline std::string PlatformUtilsGetEnv(const char* /* name */) {
340 // Stub func
341 return {};
342}
343
344static inline std::string PlatformUtilsGetSecureEnv(const char* /* name */) {
345 // Stub func
346 return {};
347}
348
349static inline bool PlatformUtilsSetEnv(const char* /* name */, const char* /* value */) {
350 // Stub func
351 return false;
352}
353
354static inline bool PlatformGetGlobalRuntimeFileName(uint16_t /* major_version */, std::string const& /* file_name */) {
355 // Stub func
356 return false;
357}
358
359#endif
360