1 /*
2  * Copyright (c) 2021 The Khronos Group Inc.
3  * Copyright (c) 2021 Valve Corporation
4  * Copyright (c) 2021 LunarG, Inc.
5  *
6  * Permission is hereby granted, free of charge, to any person obtaining a copy
7  * of this software and/or associated documentation files (the "Materials"), to
8  * deal in the Materials without restriction, including without limitation the
9  * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
10  * sell copies of the Materials, and to permit persons to whom the Materials are
11  * furnished to do so, subject to the following conditions:
12  *
13  * The above copyright notice(s) and this permission notice shall be included in
14  * all copies or substantial portions of the Materials.
15  *
16  * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19  *
20  * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
21  * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
22  * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE MATERIALS OR THE
23  * USE OR OTHER DEALINGS IN THE MATERIALS.
24  *
25  * Author: Charles Giessen <charles@lunarg.com>
26  */
27 
28 #include "shim.h"
29 
30 #include <random>
31 
redirect_all_paths(fs::path const & path)32 void PlatformShim::redirect_all_paths(fs::path const& path) {
33     redirect_category(path, ManifestCategory::implicit_layer);
34     redirect_category(path, ManifestCategory::explicit_layer);
35     redirect_category(path, ManifestCategory::icd);
36 }
37 
parse_env_var_list(std::string const & var)38 std::vector<std::string> parse_env_var_list(std::string const& var) {
39     std::vector<std::string> items;
40     size_t start = 0;
41     size_t len = 0;
42     for (size_t i = 0; i < var.size(); i++) {
43 #if defined(WIN32)
44         if (var[i] == ';') {
45 #elif defined(__linux__) || defined(__APPLE__) || defined(__FreeBSD__)
46         if (var[i] == ':') {
47 #endif
48             if (len != 0) {
49                 // only push back non empty strings
50                 items.push_back(var.substr(start, len));
51             }
52             start = i + 1;
53             len = 0;
54         } else {
55             len++;
56         }
57     }
58     items.push_back(var.substr(start, len));
59 
60     return items;
61 }
62 
63 #if defined(WIN32)
64 
65 std::string category_path_name(ManifestCategory category) {
66     if (category == ManifestCategory::implicit_layer) return "ImplicitLayers";
67     if (category == ManifestCategory::explicit_layer)
68         return "ExplicitLayers";
69     else
70         return "Drivers";
71 }
72 
73 std::string override_base_path(uint32_t random_base_path) {
74     return std::string("SOFTWARE\\LoaderRegressionTests_") + std::to_string(random_base_path);
75 }
76 
77 std::string get_override_path(HKEY root_key, uint32_t random_base_path) {
78     std::string override_path = override_base_path(random_base_path);
79 
80     if (root_key == HKEY_CURRENT_USER) {
81         override_path += "\\HKCU";
82     } else if (root_key == HKEY_LOCAL_MACHINE) {
83         override_path += "\\HKLM";
84     }
85     return override_path;
86 }
87 
88 HKEY create_key(HKEY key_root, const char* key_path) {
89     DWORD dDisposition{};
90     HKEY key{};
91     LSTATUS out =
92         RegCreateKeyExA(key_root, key_path, NULL, NULL, REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL, &key, &dDisposition);
93     if (out != ERROR_SUCCESS) std::cerr << win_api_error_str(out) << " failed to create key " << key << " at " << key_path << "\n";
94     return key;
95 }
96 
97 void close_key(HKEY key) {
98     LSTATUS out = RegCloseKey(key);
99     if (out != ERROR_SUCCESS) std::cerr << win_api_error_str(out) << " failed to close key " << key << "\n";
100 }
101 
102 void delete_key(HKEY key, const char* key_path, bool report_failure = true) {
103     LSTATUS out = RegDeleteKeyA(key, key_path);
104     if (out != ERROR_SUCCESS)
105         if (report_failure)
106             std::cerr << win_api_error_str(out) << " failed to close key " << key << " with path " << key_path << "\n";
107 }
108 
109 void setup_override_key(HKEY root_key, uint32_t random_base_path) {
110     DWORD dDisposition{};
111     LSTATUS out;
112 
113     auto override_path = get_override_path(root_key, random_base_path);
114     HKEY override_key;
115     out = RegCreateKeyExA(HKEY_CURRENT_USER, override_path.c_str(), NULL, NULL, REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL,
116                           &override_key, &dDisposition);
117     if (out != ERROR_SUCCESS)
118         std::cerr << win_api_error_str(out) << " failed to create key " << override_key << " with path " << override_path << "\n";
119 
120     out = RegOverridePredefKey(root_key, override_key);
121     if (out != ERROR_SUCCESS) std::cerr << win_api_error_str(out) << " failed to override key " << override_key << "\n";
122 
123     close_key(override_key);
124 }
125 void revert_override(HKEY root_key, uint32_t random_base_path) {
126     LSTATUS out = RegOverridePredefKey(root_key, NULL);
127     if (out != ERROR_SUCCESS) std::cerr << win_api_error_str(out) << " failed to revert override key " << root_key << "\n";
128 
129     auto override_path = get_override_path(root_key, random_base_path);
130     out = RegDeleteTreeA(HKEY_CURRENT_USER, override_path.c_str());
131     if (out != ERROR_SUCCESS) print_error_message(out, "RegDeleteTreeA", std::string("Key") + override_path);
132 }
133 
134 KeyWrapper::KeyWrapper(HKEY key) noexcept : key(key) {}
135 KeyWrapper::KeyWrapper(HKEY key_root, const char* key_path) noexcept { key = create_key(key_root, key_path); }
136 KeyWrapper::~KeyWrapper() noexcept {
137     if (key != NULL) close_key(key);
138 }
139 KeyWrapper::KeyWrapper(KeyWrapper&& other) noexcept : key(other.key) { other.key = NULL; };
140 KeyWrapper& KeyWrapper::operator=(KeyWrapper&& other) noexcept {
141     if (this != &other) {
142         if (key != NULL) close_key(key);
143         key = other.key;
144         other.key = NULL;
145     }
146     return *this;
147 };
148 
149 void add_key_value(HKEY const& key, fs::path const& manifest_path, bool enabled = true) {
150     DWORD value = enabled ? 0 : 1;
151     LSTATUS out = RegSetValueEx(key, manifest_path.c_str(), 0, REG_DWORD, reinterpret_cast<const BYTE*>(&value), sizeof(value));
152     if (out != ERROR_SUCCESS) std::cerr << win_api_error_str(out) << " failed to set key value for " << manifest_path.str() << "\n";
153 }
154 
155 void add_key_value_string(HKEY const& key, const char* name, const char* str) {
156     LSTATUS out = RegSetValueExA(key, name, 0, REG_SZ, reinterpret_cast<const BYTE*>(str), static_cast<DWORD>(strlen(str)));
157     if (out != ERROR_SUCCESS)
158         std::cerr << win_api_error_str(out) << " failed to set string value for " << name << ":" << str << "\n";
159 }
160 
161 void remove_key_value(HKEY const& key, fs::path const& manifest_path) {
162     LSTATUS out = RegDeleteValueA(key, manifest_path.c_str());
163     if (out != ERROR_SUCCESS)
164         std::cerr << win_api_error_str(out) << " failed to delete key value for " << manifest_path.str() << "\n";
165 }
166 
167 uint32_t setup_override(DebugMode debug_mode) {
168     uint32_t random_base_path = 0;
169     std::random_device rd;
170     std::ranlux48 gen(rd());
171     std::uniform_int_distribution<uint32_t> dist(0, 2000000);
172     while (random_base_path == 0) {
173         uint32_t random_num = dist(gen);
174         auto override_path = get_override_path(HKEY_CURRENT_USER, random_num);
175         HKEY temp_key = NULL;
176         auto result = RegOpenKeyEx(HKEY_CURRENT_USER, override_path.c_str(), 0, KEY_READ, &temp_key);
177         if (result != ERROR_SUCCESS) {
178             // Didn't find it, use the random number
179             random_base_path = random_num;
180         } else {
181             // try a different random number that isn't being used
182             std::cout << "INFO: Encountered existing registry key, is the registry full of old LoaderRegressionTest keys?\n";
183         }
184     }
185     auto reg_base = override_base_path(random_base_path);
186     HKEY timestamp_key = create_key(HKEY_CURRENT_USER, reg_base.c_str());
187 
188     std::time_t cur_time = std::time(nullptr);
189     char mbstr[100];
190     tm time_buf{};
191     localtime_s(&time_buf, &cur_time);
192     if (std::strftime(mbstr, sizeof(mbstr), "%A %c", &time_buf)) {
193         add_key_value_string(timestamp_key, "Timestamp", mbstr);
194     }
195 
196     setup_override_key(HKEY_LOCAL_MACHINE, random_base_path);
197     setup_override_key(HKEY_CURRENT_USER, random_base_path);
198     return random_base_path;
199 }
200 void clear_override(DebugMode debug_mode, uint32_t random_base_path) {
201     if (debug_mode != DebugMode::no_delete) {
202         revert_override(HKEY_CURRENT_USER, random_base_path);
203         revert_override(HKEY_LOCAL_MACHINE, random_base_path);
204 
205         LSTATUS out = RegDeleteKeyA(HKEY_CURRENT_USER, override_base_path(random_base_path).c_str());
206         if (out != ERROR_SUCCESS)
207             print_error_message(out, "RegDeleteKeyA", std::string("Key") + override_base_path(random_base_path).c_str());
208     }
209 }
210 void PlatformShim::reset(DebugMode debug_mode) {
211     delete_key(HKEY_CURRENT_USER, "SOFTWARE\\Khronos\\Vulkan\\ImplicitLayers", false);
212     delete_key(HKEY_CURRENT_USER, "SOFTWARE\\Khronos\\Vulkan\\ExplicitLayers", false);
213 
214     delete_key(HKEY_LOCAL_MACHINE, "SOFTWARE\\Khronos\\Vulkan\\ImplicitLayers", false);
215     delete_key(HKEY_LOCAL_MACHINE, "SOFTWARE\\Khronos\\Vulkan\\ExplicitLayers", false);
216     delete_key(HKEY_LOCAL_MACHINE, "SOFTWARE\\Khronos\\Vulkan\\Drivers", false);
217 
218     delete_key(HKEY_LOCAL_MACHINE, "SOFTWARE\\WOW6432Node\\Khronos\\Vulkan\\Drivers", false);
219     delete_key(HKEY_LOCAL_MACHINE, "SOFTWARE\\WOW6432Node\\Khronos\\Vulkan\\ExplicitLayers", false);
220     delete_key(HKEY_LOCAL_MACHINE, "SOFTWARE\\WOW6432Node\\Khronos\\Vulkan\\ImplicitLayers", false);
221 }
222 
223 void PlatformShim::set_path(ManifestCategory category, fs::path const& path) {}
224 
225 void PlatformShim::add_manifest(ManifestCategory category, fs::path const& path) {
226     std::string reg_path = std::string("SOFTWARE\\Khronos\\Vulkan\\") + category_path_name(category);
227     KeyWrapper key{HKEY_LOCAL_MACHINE, reg_path.c_str()};
228     add_key_value(key, path);
229     if (category == ManifestCategory::icd) {
230         icd_paths.push_back(path);
231     }
232 }
233 void PlatformShim::add_dxgi_adapter(fs::path const& manifest_path, GpuType gpu_preference, uint32_t known_driver_index,
234                                     DXGI_ADAPTER_DESC1 desc1) {
235     dxgi_adapters.push_back(DXGIAdapter(manifest_path, gpu_preference, known_driver_index, desc1, next_adapter_handle++));
236 }
237 
238 void PlatformShim::add_d3dkmt_adapter(SHIM_D3DKMT_ADAPTERINFO adapter, fs::path const& path) {
239     d3dkmt_adapters.push_back({adapter, path});
240 }
241 
242 void PlatformShim::add_CM_Device_ID(std::wstring const& id, fs::path const& icd_path, fs::path const& layer_path) {
243     // append a null byte as separator if there is already id's in the list
244     if (CM_device_ID_list.size() != 0) {
245         CM_device_ID_list += L'\0';  // I'm sure this wont cause issues with std::string down the line... /s
246     }
247     CM_device_ID_list += id;
248     std::string id_str(id.length(), '\0');
249     size_t size_written{};
250     wcstombs_s(&size_written, &id_str[0], id_str.length(), id.c_str(), id.length());
251 
252     std::string device_path = std::string(pnp_registry_path) + "\\" + id_str;
253     CM_device_ID_registry_keys.emplace_back(HKEY_LOCAL_MACHINE, device_path.c_str());
254     HKEY id_key = CM_device_ID_registry_keys.back().key;
255     add_key_value_string(id_key, "VulkanDriverName", icd_path.c_str());
256     add_key_value_string(id_key, "VulkanLayerName", layer_path.c_str());
257     // TODO: decide how to handle 32 bit
258     // add_key_value_string(id_key, "VulkanDriverNameWoW", icd_path.c_str());
259     // add_key_value_string(id_key, "VulkanLayerName", layer_path.c_str());
260 }
261 
262 void PlatformShim::redirect_category(fs::path const& new_path, ManifestCategory search_category) {
263     switch (search_category) {
264         case (ManifestCategory::implicit_layer):
265             create_key(HKEY_CURRENT_USER, "SOFTWARE\\Khronos\\Vulkan\\ImplicitLayers");
266             create_key(HKEY_LOCAL_MACHINE, "SOFTWARE\\Khronos\\Vulkan\\ImplicitLayers");
267             create_key(HKEY_LOCAL_MACHINE, "SOFTWARE\\WOW6432Node\\Khronos\\Vulkan\\ImplicitLayers");
268             break;
269         case (ManifestCategory::explicit_layer):
270             create_key(HKEY_CURRENT_USER, "SOFTWARE\\Khronos\\Vulkan\\ExplicitLayers");
271             create_key(HKEY_LOCAL_MACHINE, "SOFTWARE\\Khronos\\Vulkan\\ExplicitLayers");
272             create_key(HKEY_LOCAL_MACHINE, "SOFTWARE\\WOW6432Node\\Khronos\\Vulkan\\ExplicitLayers");
273             break;
274         case (ManifestCategory::icd):
275             create_key(HKEY_LOCAL_MACHINE, "SOFTWARE\\Khronos\\Vulkan\\Drivers");
276             create_key(HKEY_LOCAL_MACHINE, "SOFTWARE\\WOW6432Node\\Khronos\\Vulkan\\Drivers");
277             break;
278     }
279 }
280 
281 #elif defined(__linux__) || defined(__APPLE__) || defined(__FreeBSD__)
282 
283 #include <dirent.h>
284 #include <unistd.h>
285 
286 std::string category_path_name(ManifestCategory category) {
287     if (category == ManifestCategory::implicit_layer) return "implicit_layer.d";
288     if (category == ManifestCategory::explicit_layer)
289         return "explicit_layer.d";
290     else
291         return "icd.d";
292 }
293 
294 void PlatformShim::setup_override(DebugMode debug_mode) {}
295 void PlatformShim::clear_override(DebugMode debug_mode) {}
296 void PlatformShim::reset(DebugMode debug_mode) { redirection_map.clear(); }
297 
298 void PlatformShim::redirect_path(fs::path const& path, fs::path const& new_path) { redirection_map[path.str()] = new_path; }
299 void PlatformShim::remove_redirect(fs::path const& path) { redirection_map.erase(path.str()); }
300 bool PlatformShim::is_fake_path(fs::path const& path) { return redirection_map.count(path.str()) > 0; }
301 fs::path const& PlatformShim::get_fake_path(fs::path const& path) { return redirection_map.at(path.str()); }
302 
303 void PlatformShim::add_manifest(ManifestCategory category, fs::path const& path) {}
304 
305 void parse_and_add_env_var_override(std::vector<std::string>& paths, std::string env_var_contents) {
306     auto parsed_paths = parse_env_var_list(env_var_contents);
307     paths.insert(paths.end(), parsed_paths.begin(), parsed_paths.end());
308 }
309 
310 void PlatformShim::redirect_category(fs::path const& new_path, ManifestCategory category) {
311     std::vector<std::string> paths;
312     auto home = fs::path(get_env_var("HOME"));
313     if (home.size() != 0) {
314         paths.push_back((home / ".config").str());
315         paths.push_back((home / ".local/share").str());
316     }
317     parse_and_add_env_var_override(paths, get_env_var("XDG_CONFIG_DIRS"));
318     parse_and_add_env_var_override(paths, get_env_var("XDG_CONFIG_HOME"));
319     parse_and_add_env_var_override(paths, get_env_var("XDG_DATA_DIRS"));
320     parse_and_add_env_var_override(paths, get_env_var("XDG_DATA_HOME"));
321     if (category == ManifestCategory::explicit_layer) {
322         parse_and_add_env_var_override(paths, get_env_var("VK_LAYER_PATH", false));  // don't report failure
323     }
324     parse_and_add_env_var_override(paths, FALLBACK_DATA_DIRS);
325     parse_and_add_env_var_override(paths, FALLBACK_CONFIG_DIRS);
326 
327     auto sys_conf_dir = std::string(SYSCONFDIR);
328     if (!sys_conf_dir.empty()) {
329         paths.push_back(sys_conf_dir);
330     }
331 #if defined(EXTRASYSCONFDIR)
332     // EXTRASYSCONFDIR default is /etc, if SYSCONFDIR wasn't defined, it will have /etc put
333     // as its default. Don't want to double add it
334     auto extra_sys_conf_dir = std::string(EXTRASYSCONFDIR);
335     if (!extra_sys_conf_dir.empty() && sys_conf_dir != extra_sys_conf_dir) {
336         paths.push_back(extra_sys_conf_dir);
337     }
338 #endif
339 
340     for (auto& path : paths) {
341         if (!path.empty()) {
342             redirect_path(fs::path(path) / "vulkan" / category_path_name(category), new_path);
343         }
344     }
345 }
346 
347 void PlatformShim::set_path(ManifestCategory category, fs::path const& path) {
348     // use /etc as the 'redirection path' by default since its always searched
349     redirect_path(fs::path(SYSCONFDIR) / "vulkan" / category_path_name(category), path);
350 }
351 
352 #endif