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 | |
57 | bool FileSysUtilsIsRegularFile(const std::string& path) { return FS_PREFIX::is_regular_file(path); } |
58 | |
59 | bool FileSysUtilsIsDirectory(const std::string& path) { return FS_PREFIX::is_directory(path); } |
60 | |
61 | bool FileSysUtilsPathExists(const std::string& path) { return FS_PREFIX::exists(path); } |
62 | |
63 | bool FileSysUtilsIsAbsolutePath(const std::string& path) { |
64 | FS_PREFIX::path file_path(path); |
65 | return file_path.is_absolute(); |
66 | } |
67 | |
68 | bool FileSysUtilsGetCurrentPath(std::string& path) { |
69 | FS_PREFIX::path cur_path = FS_PREFIX::current_path(); |
70 | path = cur_path.string(); |
71 | return true; |
72 | } |
73 | |
74 | bool 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 | |
80 | bool FileSysUtilsGetAbsolutePath(const std::string& path, std::string& absolute) { |
81 | absolute = FS_PREFIX::absolute(path).string(); |
82 | return true; |
83 | } |
84 | |
85 | bool 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 | |
97 | bool 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 | |
105 | bool 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 | |
117 | bool 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 | |
128 | bool 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 | |
133 | bool 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 | |
138 | bool FileSysUtilsPathExists(const std::string& path) { |
139 | return (GetFileAttributesW(utf8_to_wide(path).c_str()) != INVALID_FILE_ATTRIBUTES); |
140 | } |
141 | |
142 | bool 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 | |
151 | bool 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 | |
160 | bool 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 | |
170 | bool 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 | |
179 | bool 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 | |
187 | bool 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 | |
201 | bool 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 | |
213 | bool 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 | |
234 | bool 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 | |
240 | bool 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 | |
246 | bool FileSysUtilsPathExists(const std::string& path) { return (access(path.c_str(), F_OK) != -1); } |
247 | |
248 | bool FileSysUtilsIsAbsolutePath(const std::string& path) { return (path[0] == DIRECTORY_SYMBOL); } |
249 | |
250 | bool 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 | |
259 | bool 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 | |
269 | bool FileSysUtilsGetAbsolutePath(const std::string& path, std::string& absolute) { |
270 | // canonical path is absolute |
271 | return FileSysUtilsGetCanonicalPath(path, absolute); |
272 | } |
273 | |
274 | bool 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 | |
283 | bool 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 | |
297 | bool 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 | |
309 | bool 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 | |