add basic_string_view cast operator to text::Encoded_character
[kazan.git] / src / util / filesystem.cpp
1 /*
2 * Copyright 2017 Jacob Lifshay
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining a copy
5 * of this software and associated documentation files (the "Software"), to deal
6 * in the Software without restriction, including without limitation the rights
7 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 * copies of the Software, and to permit persons to whom the Software is
9 * furnished to do so, subject to the following conditions:
10 *
11 * The above copyright notice and this permission notice shall be included in all
12 * copies or substantial portions of the Software.
13 *
14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20 * SOFTWARE.
21 *
22 */
23 #ifdef __linux__
24 #ifndef _LARGEFILE_SOURCE
25 #define _LARGEFILE_SOURCE
26 #endif
27 #ifndef _LARGEFILE64_SOURCE
28 #define _LARGEFILE64_SOURCE
29 #endif
30 #endif
31 #include "filesystem.h"
32 #include <cstdlib>
33 #include <iostream>
34 #ifdef _WIN32
35 #define NOMINMAX
36 #include <windows.h>
37 #elif defined(__linux__)
38 #include <cerrno>
39 #include <time.h>
40 #include <sys/types.h>
41 #include <sys/stat.h>
42 #include <unistd.h>
43 #include <dirent.h>
44 #else
45 #error filesystem is not implemented for your operating system
46 #endif
47
48 namespace vulkan_cpu
49 {
50 namespace util
51 {
52 namespace filesystem
53 {
54 namespace detail
55 {
56 constexpr bool Filesystem_clock::is_steady;
57
58 #ifdef __linux__
59 namespace
60 {
61 Filesystem_clock::time_point timespec_to_time_point(const timespec &ts) noexcept
62 {
63 return Filesystem_clock::time_point(
64 Filesystem_clock::duration(static_cast<std::int64_t>(ts.tv_sec) * 1'000'000'000L
65 + static_cast<std::int64_t>(ts.tv_nsec)));
66 }
67 }
68 #elif defined(_WIN32)
69 namespace
70 {
71 Filesystem_clock::time_point filetime_to_time_point(const FILETIME &ft) noexcept
72 {
73 ULARGE_INTEGER li{};
74 li.u.LowPart = ft.dwLowDateTime;
75 li.u.HighPart = ft.dwHighDateTime;
76 return Filesystem_clock::time_point(Filesystem_clock::duration(li.QuadPart));
77 }
78 }
79 #endif
80
81 Filesystem_clock::time_point Filesystem_clock::now() noexcept
82 {
83 #ifdef _WIN32
84 FILETIME ft{};
85 ::GetSystemTimeAsFileTime(&ft);
86 return filetime_to_time_point(ft);
87 #elif defined(__linux__)
88 timespec ts{};
89 ::clock_gettime(CLOCK_REALTIME, &ts);
90 return timespec_to_time_point(ts);
91 #else
92 #error Filesystem_clock::now is not implemented for your operating system
93 #endif
94 }
95
96 struct Stat_results
97 {
98 file_type type;
99 #ifdef _WIN32
100 #error Stat_results is not implemented on windows
101 #elif defined(__linux__)
102 struct ::stat64 stat_results;
103 constexpr Stat_results() noexcept : type(file_type::none), stat_results{}
104 {
105 }
106 Stat_results(const path &p, bool follow_symlink, std::error_code &ec)
107 : type(file_type::none), stat_results{}
108 {
109 ec.clear();
110 int old_errno = errno;
111 int stat_retval =
112 follow_symlink ? stat64(p.c_str(), &stat_results) : lstat64(p.c_str(), &stat_results);
113 int error = errno;
114 errno = old_errno;
115 if(stat_retval != 0)
116 {
117 switch(error)
118 {
119 case ENOENT:
120 ec = make_error_code(std::errc::no_such_file_or_directory);
121 type = file_type::not_found;
122 break;
123 default:
124 ec = std::error_code(errno, std::generic_category());
125 type = file_type::none;
126 break;
127 }
128 return;
129 }
130 if(S_ISBLK(stat_results.st_mode))
131 type = file_type::block;
132 else if(S_ISCHR(stat_results.st_mode))
133 type = file_type::character;
134 else if(S_ISDIR(stat_results.st_mode))
135 type = file_type::directory;
136 else if(S_ISFIFO(stat_results.st_mode))
137 type = file_type::fifo;
138 else if(S_ISREG(stat_results.st_mode))
139 type = file_type::regular;
140 else if(S_ISLNK(stat_results.st_mode))
141 type = file_type::symlink;
142 else if(S_ISSOCK(stat_results.st_mode))
143 type = file_type::symlink;
144 else
145 type = file_type::unknown;
146 }
147 #else
148 #error Stat_results is not implemented for your operating system
149 #endif
150 };
151
152 std::uintmax_t file_size(const path &p, std::error_code *ec)
153 {
154 #ifdef _WIN32
155 #error file_size is not implemented on windows
156 #elif defined(__linux__)
157 if(ec)
158 ec->clear();
159 std::error_code stat_error;
160 Stat_results stat_results(p, true, stat_error);
161 if(stat_error)
162 {
163 set_or_throw_error(ec, "stat failed", p, stat_error);
164 return -1;
165 }
166 return stat_results.stat_results.st_size;
167 #else
168 #error file_size is not implemented for your operating system
169 #endif
170 }
171
172 std::uintmax_t hard_link_count(const path &p, std::error_code *ec)
173 {
174 #ifdef _WIN32
175 #error hard_link_count is not implemented on windows
176 #elif defined(__linux__)
177 if(ec)
178 ec->clear();
179 std::error_code stat_error;
180 Stat_results stat_results(p, true, stat_error);
181 if(stat_error)
182 {
183 set_or_throw_error(ec, "stat failed", p, stat_error);
184 return -1;
185 }
186 return stat_results.stat_results.st_nlink;
187 #else
188 #error hard_link_count is not implemented for your operating system
189 #endif
190 }
191
192 file_time_type last_write_time(const path &p, std::error_code *ec)
193 {
194 #ifdef _WIN32
195 #error hard_link_count is not implemented on windows
196 #elif defined(__linux__)
197 if(ec)
198 ec->clear();
199 std::error_code stat_error;
200 Stat_results stat_results(p, true, stat_error);
201 if(stat_error)
202 {
203 set_or_throw_error(ec, "stat failed", p, stat_error);
204 return file_time_type::min();
205 }
206 return timespec_to_time_point(stat_results.stat_results.st_mtim);
207 #else
208 #error hard_link_count is not implemented for your operating system
209 #endif
210 }
211
212 file_status status(const path &p, bool follow_symlink, std::error_code *ec)
213 {
214 #ifdef _WIN32
215 #error status is not implemented on windows
216 #elif defined(__linux__)
217 if(ec)
218 ec->clear();
219 std::error_code stat_error;
220 Stat_results stat_results(p, follow_symlink, stat_error);
221 if(stat_error)
222 {
223 if(stat_results.type == file_type::none || ec)
224 set_or_throw_error(ec, "stat failed", p, stat_error);
225 return file_status(stat_results.type);
226 }
227 return file_status(stat_results.type,
228 static_cast<perms>(static_cast<std::uint32_t>(perms::mask)
229 & stat_results.stat_results.st_mode));
230 #else
231 #error status is not implemented for your operating system
232 #endif
233 }
234 }
235
236 void directory_entry::refresh(std::error_code *ec)
237 {
238 #ifdef _WIN32
239 #error status is not implemented on windows
240 #elif defined(__linux__)
241 if(ec)
242 ec->clear();
243 flags = Flags{};
244 std::error_code stat_error;
245 detail::Stat_results stat_results(path_value, false, stat_error);
246 if(stat_error)
247 {
248 if(stat_results.type == file_type::none)
249 {
250 detail::set_or_throw_error(ec, "stat failed", path_value, stat_error);
251 return;
252 }
253 flags.has_symlink_status_full_value = true;
254 symlink_status_value = file_status(stat_results.type);
255 return;
256 }
257 flags.has_symlink_status_full_value = true;
258 symlink_status_value = file_status(stat_results.type,
259 static_cast<perms>(static_cast<std::uint32_t>(perms::mask)
260 & stat_results.stat_results.st_mode));
261 flags.has_file_size_value = true;
262 file_size_value = stat_results.stat_results.st_size;
263 flags.has_hard_link_count_value = true;
264 hard_link_count_value = stat_results.stat_results.st_nlink;
265 flags.has_last_write_time_value = true;
266 last_write_time_value = detail::timespec_to_time_point(stat_results.stat_results.st_mtim)
267 .time_since_epoch()
268 .count();
269 #else
270 #error status is not implemented for your operating system
271 #endif
272 }
273
274 #ifdef _WIN32
275 #error directory_iterator is not implemented on windows
276 #elif defined(__linux__)
277 struct directory_iterator::Implementation
278 {
279 ::DIR *dir = nullptr;
280 const directory_options options;
281 Implementation(const Implementation &) = delete;
282 Implementation &operator=(const Implementation &) = delete;
283 Implementation(directory_entry &current_entry,
284 const path &p,
285 directory_options options_in,
286 std::error_code *ec,
287 bool &failed)
288 : options(options_in)
289 {
290 failed = false;
291 if(ec)
292 ec->clear();
293 auto old_errno = errno;
294 dir = ::opendir(p.c_str());
295 auto error = errno;
296 errno = old_errno;
297 if(!dir)
298 {
299 if(error != EACCES
300 || (options & directory_options::skip_permission_denied) == directory_options::none)
301 detail::set_or_throw_error(
302 ec, "opendir failed", p, std::error_code(error, std::generic_category()));
303 failed = true;
304 return;
305 }
306 try
307 {
308 current_entry.path_value = p / path(); // add trailing slash
309 if(!read(current_entry, ec))
310 failed = true;
311 }
312 catch(...)
313 {
314 close();
315 throw;
316 }
317 }
318 bool read(directory_entry &current_entry, std::error_code *ec)
319 {
320 if(ec)
321 ec->clear();
322 ::dirent64 *entry;
323 while(true)
324 {
325 auto old_errno = errno;
326 errno = 0;
327 // using readdir64 instead of readdir64_r: see
328 // https://www.gnu.org/software/libc/manual/html_node/Reading_002fClosing-Directory.html
329 entry = ::readdir64(dir);
330 auto error = errno;
331 errno = old_errno;
332 if(!entry)
333 {
334 if(error != 0)
335 detail::set_or_throw_error(
336 ec, "readdir failed", std::error_code(error, std::generic_category()));
337 return false;
338 }
339 if(entry->d_name == string_view(".") || entry->d_name == string_view(".."))
340 continue;
341 break;
342 }
343 current_entry.flags = {};
344 current_entry.path_value.replace_filename(entry->d_name);
345 current_entry.flags.has_symlink_status_type_value = true;
346 switch(entry->d_type)
347 {
348 case DT_FIFO:
349 current_entry.symlink_status_value.type(file_type::fifo);
350 break;
351 case DT_CHR:
352 current_entry.symlink_status_value.type(file_type::character);
353 break;
354 case DT_DIR:
355 current_entry.symlink_status_value.type(file_type::directory);
356 break;
357 case DT_BLK:
358 current_entry.symlink_status_value.type(file_type::block);
359 break;
360 case DT_LNK:
361 current_entry.symlink_status_value.type(file_type::symlink);
362 break;
363 case DT_REG:
364 current_entry.symlink_status_value.type(file_type::regular);
365 break;
366 case DT_SOCK:
367 current_entry.symlink_status_value.type(file_type::socket);
368 break;
369 case DT_UNKNOWN:
370 default:
371 current_entry.flags.has_symlink_status_type_value = false;
372 break;
373 }
374 return true;
375 }
376 void close() noexcept
377 {
378 if(!dir)
379 return;
380 auto old_errno = errno;
381 ::closedir(dir);
382 dir = nullptr;
383 // ignore any errors
384 errno = old_errno;
385 }
386 ~Implementation()
387 {
388 close();
389 }
390 };
391 #else
392 #error directory_iterator is not implemented for your operating system
393 #endif
394
395 std::shared_ptr<directory_iterator::Implementation> directory_iterator::create(
396 directory_entry &current_entry, const path &p, directory_options options, std::error_code *ec)
397 {
398 try
399 {
400 bool failed;
401 auto retval = std::make_shared<Implementation>(current_entry, p, options, ec, failed);
402 if(failed)
403 return nullptr;
404 return retval;
405 }
406 catch(std::bad_alloc &)
407 {
408 if(!ec)
409 throw;
410 *ec = std::make_error_code(std::errc::not_enough_memory);
411 return nullptr;
412 }
413 }
414
415 void directory_iterator::increment(std::shared_ptr<Implementation> &implementation,
416 directory_entry &current_entry,
417 std::error_code *ec)
418 {
419 try
420 {
421 if(!implementation->read(current_entry, ec))
422 implementation = nullptr;
423 }
424 catch(...)
425 {
426 implementation = nullptr;
427 throw;
428 }
429 }
430 }
431 }
432 }
433
434 #if 0 // change to 1 to test filesystem::path
435 namespace vulkan_cpu
436 {
437 namespace util
438 {
439 namespace filesystem
440 {
441 namespace detail
442 {
443 #warning testing util::filesystem::path
444 struct Path_tester
445 {
446 template <typename Path, bool Show_parts = true>
447 static void write_path(const Path &path)
448 {
449 std::cout << path << ": kind=";
450 switch(path.kind)
451 {
452 case Path_part_kind::file_name:
453 std::cout << "file_name";
454 break;
455 case Path_part_kind::multiple_parts:
456 std::cout << "multiple_parts";
457 break;
458 case Path_part_kind::root_dir:
459 std::cout << "root_dir";
460 break;
461 case Path_part_kind::relative_root_name:
462 std::cout << "relative_root_name";
463 break;
464 case Path_part_kind::absolute_root_name:
465 std::cout << "absolute_root_name";
466 break;
467 case Path_part_kind::path_separator:
468 std::cout << "path_separator";
469 break;
470 }
471 if(Show_parts)
472 {
473 std::cout << " parts=[";
474 auto separator = "";
475 for(auto &part : path.parts)
476 {
477 std::cout << separator;
478 separator = ", ";
479 write_path<Path, false>(part);
480 }
481 std::cout << "]";
482 }
483 }
484 template <Path_traits_kind Traits_kind>
485 static void test_path(const char *traits_kind_name)
486 {
487 #if 0
488 typedef basic_path<> Path;
489 #else
490 typedef basic_path<Traits_kind> Path;
491 #endif
492 std::cout << "testing basic_path<" << traits_kind_name << ">" << std::endl;
493 for(auto *test_path_string : {
494 "",
495 ".",
496 "..",
497 "C:",
498 "C:\\",
499 "C:/",
500 "/",
501 "//",
502 "//a",
503 "//a/",
504 "\\",
505 "\\\\",
506 "\\\\a",
507 "\\\\a\\",
508 "a/",
509 "a/.",
510 "a/..",
511 "a/...",
512 "a/a",
513 "a/.a",
514 "a/a.",
515 "a/a.a",
516 "a/.a.",
517 "a/.a.a",
518 "a/b/c/d/../.././e/../../f",
519 "C:../.",
520 "/../.././",
521 })
522 {
523 Path p(test_path_string);
524 std::cout << "'" << test_path_string << "' -> ";
525 write_path(p);
526 std::cout << std::endl;
527 std::cout << "make_preferred -> " << Path(p).make_preferred() << std::endl;
528 std::cout << "remove_filename -> " << Path(p).remove_filename() << std::endl;
529 std::cout << "lexically_normal -> " << p.lexically_normal() << std::endl;
530 std::cout << "root_name -> " << p.root_name() << std::endl;
531 std::cout << "root_directory -> " << p.root_directory() << std::endl;
532 std::cout << "root_path -> " << p.root_path() << std::endl;
533 std::cout << "relative_path -> " << p.relative_path() << std::endl;
534 std::cout << "parent_path -> " << p.parent_path() << std::endl;
535 std::cout << "filename -> " << p.filename() << std::endl;
536 std::cout << "stem -> " << p.stem() << std::endl;
537 std::cout << "extension -> " << p.extension() << std::endl;
538 std::cout << "operator/:";
539 for(auto *appended_path : {
540 "", "/abc", "C:abc", "//a/abc", "C:/abc", "abc",
541 })
542 {
543 std::cout << " \"" << appended_path << "\"->" << p / appended_path;
544 }
545 std::cout << std::endl;
546 std::cout << "lexically_proximate:";
547 for(auto *base_path : {
548 "", "/abc", "C:abc", "//a/abc", "C:/abc", "abc",
549 })
550 {
551 std::cout << " \"" << base_path << "\"->" << p.lexically_proximate(base_path);
552 }
553 std::cout << std::endl;
554 }
555 }
556 static void test()
557 {
558 test_path<Path_traits_kind::posix>("posix");
559 test_path<Path_traits_kind::windows>("windows");
560 std::string().assign("", 0);
561 }
562 Path_tester()
563 {
564 test();
565 std::exit(1);
566 }
567 };
568
569 namespace
570 {
571 Path_tester path_tester;
572 }
573 }
574 }
575 }
576 }
577 #endif