1// Copyright (c) 2017-2023, The Khronos Group Inc.
2// Copyright (c) 2017 Valve Corporation
3// Copyright (c) 2017 LunarG, Inc.
4//
5// SPDX-License-Identifier: Apache-2.0 OR MIT
6//
7// Initial Authors: Mark Young <marky@lunarg.com>
8// Nat Brown <natb@valvesoftware.com>
9//
10
11#include "filesystem_utils.hpp"
12
13#include "platform_utils.hpp"
14
15#include <cstring>
16#include <string>
17
18#if defined DISABLE_STD_FILESYSTEM
19#define USE_EXPERIMENTAL_FS 0
20#define USE_FINAL_FS 0
21
22#else
23#include "stdfs_conditions.h"
24#endif
25
26#if USE_FINAL_FS == 1
27#include <filesystem>
28#define FS_PREFIX std::filesystem
29#elif USE_EXPERIMENTAL_FS == 1
30#include <experimental/filesystem>
31#define FS_PREFIX std::experimental::filesystem
32#elif defined(XR_USE_PLATFORM_WIN32)
33// Windows fallback includes
34#include <stdint.h>
35#include <direct.h>
36#else
37// Linux/Apple fallback includes
38#include <sys/stat.h>
39#include <unistd.h>
40#include <limits.h>
41#include <stdlib.h>
42#include <dirent.h>
43#endif
44
45#if defined(XR_USE_PLATFORM_WIN32)
46#define PATH_SEPARATOR ';'
47#define DIRECTORY_SYMBOL '\\'
48#define ALTERNATE_DIRECTORY_SYMBOL '/'
49#else
50#define PATH_SEPARATOR ':'
51#define DIRECTORY_SYMBOL '/'
52#endif
53
54#if (USE_FINAL_FS == 1) || (USE_EXPERIMENTAL_FS == 1)
55// We can use one of the C++ filesystem packages
56
57bool FileSysUtilsIsRegularFile(const std::string& path) { return FS_PREFIX::is_regular_file(path); }
58
59bool FileSysUtilsIsDirectory(const std::string& path) { return FS_PREFIX::is_directory(path); }
60
61bool FileSysUtilsPathExists(const std::string& path) { return FS_PREFIX::exists(path); }
62
63bool FileSysUtilsIsAbsolutePath(const std::string& path) {
64 FS_PREFIX::path file_path(path);
65 return file_path.is_absolute();
66}
67
68bool FileSysUtilsGetCurrentPath(std::string& path) {
69 FS_PREFIX::path cur_path = FS_PREFIX::current_path();
70 path = cur_path.string();
71 return true;
72}
73
74bool FileSysUtilsGetParentPath(const std::string& file_path, std::string& parent_path) {
75 FS_PREFIX::path path_var(file_path);
76 parent_path = path_var.parent_path().string();
77 return true;
78}
79
80bool FileSysUtilsGetAbsolutePath(const std::string& path, std::string& absolute) {
81 absolute = FS_PREFIX::absolute(path).string();
82 return true;
83}
84
85bool FileSysUtilsGetCanonicalPath(const std::string& path, std::string& canonical) {
86#if defined(XR_USE_PLATFORM_WIN32)
87 // std::filesystem::canonical fails on UWP and must be avoided. Further, PathCchCanonicalize is not available on Windows 7 and
88 // PathCanonicalizeW is not available on UWP. However, symbolic links are not important on Windows since the loader uses the
89 // registry for indirection instead, and so this function can be a no-op on Windows.
90 canonical = path;
91#else
92 canonical = FS_PREFIX::canonical(path).string();
93#endif
94 return true;
95}
96
97bool FileSysUtilsCombinePaths(const std::string& parent, const std::string& child, std::string& combined) {
98 FS_PREFIX::path parent_path(parent);
99 FS_PREFIX::path child_path(child);
100 FS_PREFIX::path full_path = parent_path / child_path;
101 combined = full_path.string();
102 return true;
103}
104
105bool FileSysUtilsParsePathList(std::string& path_list, std::vector<std::string>& paths) {
106 std::string::size_type start = 0;
107 std::string::size_type location = path_list.find(PATH_SEPARATOR);
108 while (location != std::string::npos) {
109 paths.push_back(path_list.substr(start, location));
110 start = location + 1;
111 location = path_list.find(PATH_SEPARATOR, start);
112 }
113 paths.push_back(path_list.substr(start, location));
114 return true;
115}
116
117bool FileSysUtilsFindFilesInPath(const std::string& path, std::vector<std::string>& files) {
118 for (auto& dir_iter : FS_PREFIX::directory_iterator(path)) {
119 files.push_back(dir_iter.path().filename().string());
120 }
121 return true;
122}
123
124#elif defined(XR_OS_WINDOWS)
125
126// For pre C++17 compiler that doesn't support experimental filesystem
127
128bool FileSysUtilsIsRegularFile(const std::string& path) {
129 const DWORD attr = GetFileAttributesW(utf8_to_wide(path).c_str());
130 return attr != INVALID_FILE_ATTRIBUTES && !(attr & FILE_ATTRIBUTE_DIRECTORY);
131}
132
133bool FileSysUtilsIsDirectory(const std::string& path) {
134 const DWORD attr = GetFileAttributesW(utf8_to_wide(path).c_str());
135 return attr != INVALID_FILE_ATTRIBUTES && (attr & FILE_ATTRIBUTE_DIRECTORY);
136}
137
138bool FileSysUtilsPathExists(const std::string& path) {
139 return (GetFileAttributesW(utf8_to_wide(path).c_str()) != INVALID_FILE_ATTRIBUTES);
140}
141
142bool FileSysUtilsIsAbsolutePath(const std::string& path) {
143 bool pathStartsWithDir = (path.size() >= 1) && ((path[0] == DIRECTORY_SYMBOL) || (path[0] == ALTERNATE_DIRECTORY_SYMBOL));
144
145 bool pathStartsWithDrive =
146 (path.size() >= 3) && (path[1] == ':' && (path[2] == DIRECTORY_SYMBOL || path[2] == ALTERNATE_DIRECTORY_SYMBOL));
147
148 return pathStartsWithDir || pathStartsWithDrive;
149}
150
151bool FileSysUtilsGetCurrentPath(std::string& path) {
152 wchar_t tmp_path[MAX_PATH];
153 if (nullptr != _wgetcwd(tmp_path, MAX_PATH - 1)) {
154 path = wide_to_utf8(tmp_path);
155 return true;
156 }
157 return false;
158}
159
160bool FileSysUtilsGetParentPath(const std::string& file_path, std::string& parent_path) {
161 std::string full_path;
162 if (FileSysUtilsGetAbsolutePath(file_path, full_path)) {
163 std::string::size_type lastSeparator = full_path.find_last_of(DIRECTORY_SYMBOL);
164 parent_path = (lastSeparator == 0) ? full_path : full_path.substr(0, lastSeparator);
165 return true;
166 }
167 return false;
168}
169
170bool FileSysUtilsGetAbsolutePath(const std::string& path, std::string& absolute) {
171 wchar_t tmp_path[MAX_PATH];
172 if (0 != GetFullPathNameW(utf8_to_wide(path).c_str(), MAX_PATH, tmp_path, NULL)) {
173 absolute = wide_to_utf8(tmp_path);
174 return true;
175 }
176 return false;
177}
178
179bool FileSysUtilsGetCanonicalPath(const std::string& path, std::string& absolute) {
180 // PathCchCanonicalize is not available on Windows 7 and PathCanonicalizeW is not available on UWP. However, symbolic links are
181 // not important on Windows since the loader uses the registry for indirection instead, and so this function can be a no-op on
182 // Windows.
183 absolute = path;
184 return true;
185}
186
187bool FileSysUtilsCombinePaths(const std::string& parent, const std::string& child, std::string& combined) {
188 std::string::size_type parent_len = parent.length();
189 if (0 == parent_len || "." == parent || ".\\" == parent || "./" == parent) {
190 combined = child;
191 return true;
192 }
193 char last_char = parent[parent_len - 1];
194 if ((last_char == DIRECTORY_SYMBOL) || (last_char == ALTERNATE_DIRECTORY_SYMBOL)) {
195 parent_len--;
196 }
197 combined = parent.substr(0, parent_len) + DIRECTORY_SYMBOL + child;
198 return true;
199}
200
201bool FileSysUtilsParsePathList(std::string& path_list, std::vector<std::string>& paths) {
202 std::string::size_type start = 0;
203 std::string::size_type location = path_list.find(PATH_SEPARATOR);
204 while (location != std::string::npos) {
205 paths.push_back(path_list.substr(start, location));
206 start = location + 1;
207 location = path_list.find(PATH_SEPARATOR, start);
208 }
209 paths.push_back(path_list.substr(start, location));
210 return true;
211}
212
213bool FileSysUtilsFindFilesInPath(const std::string& path, std::vector<std::string>& files) {
214 std::string searchPath;
215 FileSysUtilsCombinePaths(path, "*", searchPath);
216
217 WIN32_FIND_DATAW file_data;
218 HANDLE file_handle = FindFirstFileW(utf8_to_wide(searchPath).c_str(), &file_data);
219 if (file_handle != INVALID_HANDLE_VALUE) {
220 do {
221 if (!(file_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
222 files.push_back(wide_to_utf8(file_data.cFileName));
223 }
224 } while (FindNextFileW(file_handle, &file_data));
225 return true;
226 }
227 return false;
228}
229
230#else // XR_OS_LINUX/XR_OS_APPLE fallback
231
232// simple POSIX-compatible implementation of the <filesystem> pieces used by OpenXR
233
234bool FileSysUtilsIsRegularFile(const std::string& path) {
235 struct stat path_stat;
236 stat(path.c_str(), &path_stat);
237 return S_ISREG(path_stat.st_mode);
238}
239
240bool FileSysUtilsIsDirectory(const std::string& path) {
241 struct stat path_stat;
242 stat(path.c_str(), &path_stat);
243 return S_ISDIR(path_stat.st_mode);
244}
245
246bool FileSysUtilsPathExists(const std::string& path) { return (access(path.c_str(), F_OK) != -1); }
247
248bool FileSysUtilsIsAbsolutePath(const std::string& path) { return (path[0] == DIRECTORY_SYMBOL); }
249
250bool FileSysUtilsGetCurrentPath(std::string& path) {
251 char tmp_path[PATH_MAX];
252 if (nullptr != getcwd(tmp_path, PATH_MAX - 1)) {
253 path = tmp_path;
254 return true;
255 }
256 return false;
257}
258
259bool FileSysUtilsGetParentPath(const std::string& file_path, std::string& parent_path) {
260 std::string full_path;
261 if (FileSysUtilsGetAbsolutePath(file_path, full_path)) {
262 std::string::size_type lastSeparator = full_path.find_last_of(DIRECTORY_SYMBOL);
263 parent_path = (lastSeparator == 0) ? full_path : full_path.substr(0, lastSeparator);
264 return true;
265 }
266 return false;
267}
268
269bool FileSysUtilsGetAbsolutePath(const std::string& path, std::string& absolute) {
270 // canonical path is absolute
271 return FileSysUtilsGetCanonicalPath(path, absolute);
272}
273
274bool FileSysUtilsGetCanonicalPath(const std::string& path, std::string& canonical) {
275 char buf[PATH_MAX];
276 if (nullptr != realpath(path.c_str(), buf)) {
277 canonical = buf;
278 return true;
279 }
280 return false;
281}
282
283bool FileSysUtilsCombinePaths(const std::string& parent, const std::string& child, std::string& combined) {
284 std::string::size_type parent_len = parent.length();
285 if (0 == parent_len || "." == parent || "./" == parent) {
286 combined = child;
287 return true;
288 }
289 char last_char = parent[parent_len - 1];
290 if (last_char == DIRECTORY_SYMBOL) {
291 parent_len--;
292 }
293 combined = parent.substr(0, parent_len) + DIRECTORY_SYMBOL + child;
294 return true;
295}
296
297bool FileSysUtilsParsePathList(std::string& path_list, std::vector<std::string>& paths) {
298 std::string::size_type start = 0;
299 std::string::size_type location = path_list.find(PATH_SEPARATOR);
300 while (location != std::string::npos) {
301 paths.push_back(path_list.substr(start, location));
302 start = location + 1;
303 location = path_list.find(PATH_SEPARATOR, start);
304 }
305 paths.push_back(path_list.substr(start, location));
306 return true;
307}
308
309bool FileSysUtilsFindFilesInPath(const std::string& path, std::vector<std::string>& files) {
310 DIR* dir = opendir(path.c_str());
311 if (dir == nullptr) {
312 return false;
313 }
314 struct dirent* entry;
315 while ((entry = readdir(dir)) != nullptr) {
316 files.emplace_back(entry->d_name);
317 }
318 closedir(dir);
319 return true;
320}
321
322#endif
323