1 // Class filesystem::path -*- C++ -*- 2 3 // Copyright (C) 2014-2018 Free Software Foundation, Inc. 4 // 5 // This file is part of the GNU ISO C++ Library. This library is free 6 // software; you can redistribute it and/or modify it under the 7 // terms of the GNU General Public License as published by the 8 // Free Software Foundation; either version 3, or (at your option) 9 // any later version. 10 11 // This library is distributed in the hope that it will be useful, 12 // but WITHOUT ANY WARRANTY; without even the implied warranty of 13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 // GNU General Public License for more details. 15 16 // Under Section 7 of GPL version 3, you are granted additional 17 // permissions described in the GCC Runtime Library Exception, version 18 // 3.1, as published by the Free Software Foundation. 19 20 // You should have received a copy of the GNU General Public License and 21 // a copy of the GCC Runtime Library Exception along with this program; 22 // see the files COPYING3 and COPYING.RUNTIME respectively. If not, see 23 // <http://www.gnu.org/licenses/>. 24 25 #ifndef _GLIBCXX_USE_CXX11_ABI 26 # define _GLIBCXX_USE_CXX11_ABI 1 27 #endif 28 29 #include <filesystem> 30 #ifdef _GLIBCXX_FILESYSTEM_IS_WINDOWS 31 # include <algorithm> 32 #endif 33 34 namespace fs = std::filesystem; 35 using fs::path; 36 37 fs::filesystem_error::~filesystem_error() = default; 38 39 constexpr path::value_type path::preferred_separator; 40 41 path& 42 path::remove_filename() 43 { 44 if (_M_type == _Type::_Multi) 45 { 46 if (!_M_cmpts.empty()) 47 { 48 auto cmpt = std::prev(_M_cmpts.end()); 49 if (cmpt->_M_type == _Type::_Filename && !cmpt->empty()) 50 { 51 _M_pathname.erase(cmpt->_M_pos); 52 auto prev = std::prev(cmpt); 53 if (prev->_M_type == _Type::_Root_dir 54 || prev->_M_type == _Type::_Root_name) 55 { 56 _M_cmpts.erase(cmpt); 57 _M_trim(); 58 } 59 else 60 cmpt->clear(); 61 } 62 } 63 } 64 else if (_M_type == _Type::_Filename) 65 clear(); 66 return *this; 67 } 68 69 path& 70 path::replace_filename(const path& replacement) 71 { 72 remove_filename(); 73 operator/=(replacement); 74 return *this; 75 } 76 77 path& 78 path::replace_extension(const path& replacement) 79 { 80 auto ext = _M_find_extension(); 81 // Any existing extension() is removed 82 if (ext.first && ext.second != string_type::npos) 83 { 84 if (ext.first == &_M_pathname) 85 _M_pathname.erase(ext.second); 86 else 87 { 88 auto& back = _M_cmpts.back(); 89 if (ext.first != &back._M_pathname) 90 _GLIBCXX_THROW_OR_ABORT( 91 std::logic_error("path::replace_extension failed")); 92 back._M_pathname.erase(ext.second); 93 _M_pathname.erase(back._M_pos + ext.second); 94 } 95 } 96 // If replacement is not empty and does not begin with a dot character, 97 // a dot character is appended 98 if (!replacement.empty() && replacement.native()[0] != '.') 99 _M_pathname += '.'; 100 operator+=(replacement); 101 return *this; 102 } 103 104 namespace 105 { 106 template<typename Iter1, typename Iter2> 107 int do_compare(Iter1 begin1, Iter1 end1, Iter2 begin2, Iter2 end2) 108 { 109 int cmpt = 1; 110 while (begin1 != end1 && begin2 != end2) 111 { 112 if (begin1->native() < begin2->native()) 113 return -cmpt; 114 if (begin1->native() > begin2->native()) 115 return +cmpt; 116 ++begin1; 117 ++begin2; 118 ++cmpt; 119 } 120 if (begin1 == end1) 121 { 122 if (begin2 == end2) 123 return 0; 124 return -cmpt; 125 } 126 return +cmpt; 127 } 128 } 129 130 int 131 path::compare(const path& p) const noexcept 132 { 133 struct CmptRef 134 { 135 const path* ptr; 136 const string_type& native() const noexcept { return ptr->native(); } 137 }; 138 139 if (empty() && p.empty()) 140 return 0; 141 else if (_M_type == _Type::_Multi && p._M_type == _Type::_Multi) 142 return do_compare(_M_cmpts.begin(), _M_cmpts.end(), 143 p._M_cmpts.begin(), p._M_cmpts.end()); 144 else if (_M_type == _Type::_Multi) 145 { 146 CmptRef c[1] = { { &p } }; 147 return do_compare(_M_cmpts.begin(), _M_cmpts.end(), c, c+1); 148 } 149 else if (p._M_type == _Type::_Multi) 150 { 151 CmptRef c[1] = { { this } }; 152 return do_compare(c, c+1, p._M_cmpts.begin(), p._M_cmpts.end()); 153 } 154 else 155 return _M_pathname.compare(p._M_pathname); 156 } 157 158 path 159 path::root_name() const 160 { 161 path __ret; 162 if (_M_type == _Type::_Root_name) 163 __ret = *this; 164 else if (_M_cmpts.size() && _M_cmpts.begin()->_M_type == _Type::_Root_name) 165 __ret = *_M_cmpts.begin(); 166 return __ret; 167 } 168 169 path 170 path::root_directory() const 171 { 172 path __ret; 173 if (_M_type == _Type::_Root_dir) 174 { 175 __ret._M_type = _Type::_Root_dir; 176 __ret._M_pathname.assign(1, preferred_separator); 177 } 178 else if (!_M_cmpts.empty()) 179 { 180 auto __it = _M_cmpts.begin(); 181 if (__it->_M_type == _Type::_Root_name) 182 ++__it; 183 if (__it != _M_cmpts.end() && __it->_M_type == _Type::_Root_dir) 184 __ret = *__it; 185 } 186 return __ret; 187 } 188 189 path 190 path::root_path() const 191 { 192 path __ret; 193 if (_M_type == _Type::_Root_name) 194 __ret = *this; 195 else if (_M_type == _Type::_Root_dir) 196 { 197 __ret._M_pathname.assign(1, preferred_separator); 198 __ret._M_type = _Type::_Root_dir; 199 } 200 else if (!_M_cmpts.empty()) 201 { 202 auto __it = _M_cmpts.begin(); 203 if (__it->_M_type == _Type::_Root_name) 204 { 205 __ret = *__it++; 206 if (__it != _M_cmpts.end() && __it->_M_type == _Type::_Root_dir) 207 __ret /= *__it; 208 } 209 else if (__it->_M_type == _Type::_Root_dir) 210 __ret = *__it; 211 } 212 return __ret; 213 } 214 215 path 216 path::relative_path() const 217 { 218 path __ret; 219 if (_M_type == _Type::_Filename) 220 __ret = *this; 221 else if (!_M_cmpts.empty()) 222 { 223 auto __it = _M_cmpts.begin(); 224 if (__it->_M_type == _Type::_Root_name) 225 ++__it; 226 if (__it != _M_cmpts.end() && __it->_M_type == _Type::_Root_dir) 227 ++__it; 228 if (__it != _M_cmpts.end()) 229 __ret.assign(_M_pathname.substr(__it->_M_pos)); 230 } 231 return __ret; 232 } 233 234 path 235 path::parent_path() const 236 { 237 path __ret; 238 if (!has_relative_path()) 239 __ret = *this; 240 else if (_M_cmpts.size() >= 2) 241 { 242 for (auto __it = _M_cmpts.begin(), __end = std::prev(_M_cmpts.end()); 243 __it != __end; ++__it) 244 { 245 __ret /= *__it; 246 } 247 } 248 return __ret; 249 } 250 251 bool 252 path::has_root_name() const 253 { 254 if (_M_type == _Type::_Root_name) 255 return true; 256 if (!_M_cmpts.empty() && _M_cmpts.begin()->_M_type == _Type::_Root_name) 257 return true; 258 return false; 259 } 260 261 bool 262 path::has_root_directory() const 263 { 264 if (_M_type == _Type::_Root_dir) 265 return true; 266 if (!_M_cmpts.empty()) 267 { 268 auto __it = _M_cmpts.begin(); 269 if (__it->_M_type == _Type::_Root_name) 270 ++__it; 271 if (__it != _M_cmpts.end() && __it->_M_type == _Type::_Root_dir) 272 return true; 273 } 274 return false; 275 } 276 277 bool 278 path::has_root_path() const 279 { 280 if (_M_type == _Type::_Root_name || _M_type == _Type::_Root_dir) 281 return true; 282 if (!_M_cmpts.empty()) 283 { 284 auto __type = _M_cmpts.front()._M_type; 285 if (__type == _Type::_Root_name || __type == _Type::_Root_dir) 286 return true; 287 } 288 return false; 289 } 290 291 bool 292 path::has_relative_path() const 293 { 294 if (_M_type == _Type::_Filename && !_M_pathname.empty()) 295 return true; 296 if (!_M_cmpts.empty()) 297 { 298 auto __it = _M_cmpts.begin(); 299 if (__it->_M_type == _Type::_Root_name) 300 ++__it; 301 if (__it != _M_cmpts.end() && __it->_M_type == _Type::_Root_dir) 302 ++__it; 303 if (__it != _M_cmpts.end() && !__it->_M_pathname.empty()) 304 return true; 305 } 306 return false; 307 } 308 309 310 bool 311 path::has_parent_path() const 312 { 313 if (!has_relative_path()) 314 return !empty(); 315 return _M_cmpts.size() >= 2; 316 } 317 318 bool 319 path::has_filename() const 320 { 321 if (empty()) 322 return false; 323 if (_M_type == _Type::_Filename) 324 return !_M_pathname.empty(); 325 if (_M_type == _Type::_Multi) 326 { 327 if (_M_pathname.back() == preferred_separator) 328 return false; 329 return _M_cmpts.back().has_filename(); 330 } 331 return false; 332 } 333 334 namespace 335 { 336 #ifdef _GLIBCXX_FILESYSTEM_IS_WINDOWS 337 inline bool is_dot(wchar_t c) { return c == L'.'; } 338 #else 339 inline bool is_dot(char c) { return c == '.'; } 340 #endif 341 342 inline bool is_dot(const fs::path& path) 343 { 344 const auto& filename = path.native(); 345 return filename.size() == 1 && is_dot(filename[0]); 346 } 347 348 inline bool is_dotdot(const fs::path& path) 349 { 350 const auto& filename = path.native(); 351 return filename.size() == 2 && is_dot(filename[0]) && is_dot(filename[1]); 352 } 353 } // namespace 354 355 path 356 path::lexically_normal() const 357 { 358 /* 359 C++17 [fs.path.generic] p6 360 - If the path is empty, stop. 361 - Replace each slash character in the root-name with a preferred-separator. 362 - Replace each directory-separator with a preferred-separator. 363 - Remove each dot filename and any immediately following directory-separator. 364 - As long as any appear, remove a non-dot-dot filename immediately followed 365 by a directory-separator and a dot-dot filename, along with any immediately 366 following directory-separator. 367 - If there is a root-directory, remove all dot-dot filenames and any 368 directory-separators immediately following them. 369 - If the last filename is dot-dot, remove any trailing directory-separator. 370 - If the path is empty, add a dot. 371 */ 372 path ret; 373 // If the path is empty, stop. 374 if (empty()) 375 return ret; 376 for (auto& p : *this) 377 { 378 #ifdef _GLIBCXX_FILESYSTEM_IS_WINDOWS 379 // Replace each slash character in the root-name 380 if (p._M_type == _Type::_Root_name || p._M_type == _Type::_Root_dir) 381 { 382 string_type s = p.native(); 383 std::replace(s.begin(), s.end(), L'/', L'\\'); 384 ret /= s; 385 continue; 386 } 387 #endif 388 if (is_dotdot(p)) 389 { 390 if (ret.has_filename()) 391 { 392 // remove a non-dot-dot filename immediately followed by /.. 393 if (!is_dotdot(ret.filename())) 394 ret.remove_filename(); 395 else 396 ret /= p; 397 } 398 else if (!ret.has_relative_path()) 399 { 400 // remove a dot-dot filename immediately after root-directory 401 if (!ret.has_root_directory()) 402 ret /= p; 403 } 404 else 405 { 406 // Got a path with a relative path (i.e. at least one non-root 407 // element) and no filename at the end (i.e. empty last element), 408 // so must have a trailing slash. See what is before it. 409 auto elem = ret._M_cmpts.end() - 2; 410 if (elem->has_filename() && !is_dotdot(*elem)) 411 { 412 // Remove the filename before the trailing slash 413 // (equiv. to ret = ret.parent_path().remove_filename()) 414 415 if (elem == ret._M_cmpts.begin()) 416 ret.clear(); 417 else 418 { 419 ret._M_pathname.erase(elem->_M_pos); 420 // Remove empty filename at the end: 421 ret._M_cmpts.pop_back(); 422 // If we still have a trailing non-root dir separator 423 // then leave an empty filename at the end: 424 if (std::prev(elem)->_M_type == _Type::_Filename) 425 elem->clear(); 426 else // remove the component completely: 427 ret._M_cmpts.pop_back(); 428 } 429 } 430 else 431 // Append the ".." to something ending in "../" which happens 432 // when normalising paths like ".././.." and "../a/../.." 433 ret /= p; 434 } 435 } 436 else if (is_dot(p)) 437 ret /= path(); 438 else 439 ret /= p; 440 } 441 442 if (ret._M_cmpts.size() >= 2) 443 { 444 auto back = std::prev(ret.end()); 445 // If the last filename is dot-dot, ... 446 if (back->empty() && is_dotdot(*std::prev(back))) 447 // ... remove any trailing directory-separator. 448 ret = ret.parent_path(); 449 } 450 // If the path is empty, add a dot. 451 else if (ret.empty()) 452 ret = "."; 453 454 return ret; 455 } 456 457 path 458 path::lexically_relative(const path& base) const 459 { 460 path ret; 461 if (root_name() != base.root_name()) 462 return ret; 463 if (is_absolute() != base.is_absolute()) 464 return ret; 465 if (!has_root_directory() && base.has_root_directory()) 466 return ret; 467 auto [a, b] = std::mismatch(begin(), end(), base.begin(), base.end()); 468 if (a == end() && b == base.end()) 469 ret = "."; 470 else 471 { 472 int n = 0; 473 for (; b != base.end(); ++b) 474 { 475 const path& p = *b; 476 if (is_dotdot(p)) 477 --n; 478 else if (!p.empty() && !is_dot(p)) 479 ++n; 480 } 481 if (n == 0 && (a == end() || a->empty())) 482 ret = "."; 483 else if (n >= 0) 484 { 485 const path dotdot(".."); 486 while (n--) 487 ret /= dotdot; 488 for (; a != end(); ++a) 489 ret /= *a; 490 } 491 } 492 return ret; 493 } 494 495 path 496 path::lexically_proximate(const path& base) const 497 { 498 path rel = lexically_relative(base); 499 if (rel.empty()) 500 rel = *this; 501 return rel; 502 } 503 504 std::pair<const path::string_type*, std::size_t> 505 path::_M_find_extension() const 506 { 507 const std::string* s = nullptr; 508 509 if (_M_type == _Type::_Filename) 510 s = &_M_pathname; 511 else if (_M_type == _Type::_Multi && !_M_cmpts.empty()) 512 { 513 const auto& c = _M_cmpts.back(); 514 if (c._M_type == _Type::_Filename) 515 s = &c._M_pathname; 516 } 517 518 if (s) 519 { 520 if (auto sz = s->size()) 521 { 522 if (sz <= 2 && (*s)[0] == '.') 523 return { s, string_type::npos }; 524 const auto pos = s->rfind('.'); 525 return { s, pos ? pos : string_type::npos }; 526 } 527 } 528 return {}; 529 } 530 531 void 532 path::_M_split_cmpts() 533 { 534 _M_cmpts.clear(); 535 if (_M_pathname.empty()) 536 { 537 _M_type = _Type::_Filename; 538 return; 539 } 540 _M_type = _Type::_Multi; 541 542 { 543 // Approximate count of components, to reserve space in _M_cmpts vector: 544 int count = 1; 545 bool saw_sep_last = _S_is_dir_sep(_M_pathname[0]); 546 bool saw_non_sep = !saw_sep_last; 547 for (value_type c : _M_pathname) 548 { 549 if (_S_is_dir_sep(c)) 550 saw_sep_last = true; 551 else if (saw_sep_last) 552 { 553 ++count; 554 saw_sep_last = false; 555 saw_non_sep = true; 556 } 557 } 558 if (saw_non_sep && saw_sep_last) 559 ++count; // empty filename after trailing slash 560 if (count > 1) 561 _M_cmpts.reserve(count); 562 } 563 564 size_t pos = 0; 565 const size_t len = _M_pathname.size(); 566 567 // look for root name or root directory 568 if (_S_is_dir_sep(_M_pathname[0])) 569 { 570 #ifdef __CYGWIN__ 571 // look for root name, such as "//foo" 572 if (len > 2 && _M_pathname[1] == _M_pathname[0]) 573 { 574 if (!_S_is_dir_sep(_M_pathname[2])) 575 { 576 // got root name, find its end 577 pos = 3; 578 while (pos < len && !_S_is_dir_sep(_M_pathname[pos])) 579 ++pos; 580 if (pos == len) 581 { 582 _M_type = _Type::_Root_name; 583 return; 584 } 585 _M_add_root_name(pos); 586 _M_add_root_dir(pos); 587 } 588 else 589 { 590 // got something like "///foo" which is just a root directory 591 // composed of multiple redundant directory separators 592 _M_add_root_dir(0); 593 } 594 } 595 else 596 #endif 597 { 598 // got root directory 599 if (_M_pathname.find_first_not_of('/') == string_type::npos) 600 { 601 // entire path is just slashes 602 _M_type = _Type::_Root_dir; 603 return; 604 } 605 _M_add_root_dir(0); 606 ++pos; 607 } 608 } 609 #ifdef _GLIBCXX_FILESYSTEM_IS_WINDOWS 610 else if (len > 1 && _M_pathname[1] == L':') 611 { 612 // got disk designator 613 if (len == 2) 614 { 615 _M_type = _Type::_Root_name; 616 return; 617 } 618 _M_add_root_name(2); 619 if (len > 2 && _S_is_dir_sep(_M_pathname[2])) 620 _M_add_root_dir(2); 621 pos = 2; 622 } 623 #endif 624 else 625 { 626 size_t n = 1; 627 for (; n < _M_pathname.size() && !_S_is_dir_sep(_M_pathname[n]); ++n) 628 { } 629 if (n == _M_pathname.size()) 630 { 631 _M_type = _Type::_Filename; 632 return; 633 } 634 } 635 636 size_t back = pos; 637 while (pos < len) 638 { 639 if (_S_is_dir_sep(_M_pathname[pos])) 640 { 641 if (back != pos) 642 _M_add_filename(back, pos - back); 643 back = ++pos; 644 } 645 else 646 ++pos; 647 } 648 649 if (back != pos) 650 _M_add_filename(back, pos - back); 651 else if (_S_is_dir_sep(_M_pathname.back())) 652 { 653 // [fs.path.itr]/4 654 // An empty element, if trailing non-root directory-separator present. 655 if (_M_cmpts.back()._M_type == _Type::_Filename) 656 { 657 pos = _M_pathname.size(); 658 _M_cmpts.emplace_back(string_type(), _Type::_Filename, pos); 659 } 660 } 661 662 _M_trim(); 663 } 664 665 void 666 path::_M_add_root_name(size_t n) 667 { 668 _M_cmpts.emplace_back(_M_pathname.substr(0, n), _Type::_Root_name, 0); 669 } 670 671 void 672 path::_M_add_root_dir(size_t pos) 673 { 674 _M_cmpts.emplace_back(_M_pathname.substr(pos, 1), _Type::_Root_dir, pos); 675 } 676 677 void 678 path::_M_add_filename(size_t pos, size_t n) 679 { 680 _M_cmpts.emplace_back(_M_pathname.substr(pos, n), _Type::_Filename, pos); 681 } 682 683 void 684 path::_M_trim() 685 { 686 if (_M_cmpts.size() == 1) 687 { 688 _M_type = _M_cmpts.front()._M_type; 689 _M_cmpts.clear(); 690 } 691 } 692 693 path::string_type 694 path::_S_convert_loc(const char* __first, const char* __last, 695 const std::locale& __loc) 696 { 697 #if _GLIBCXX_USE_WCHAR_T 698 auto& __cvt = std::use_facet<codecvt<wchar_t, char, mbstate_t>>(__loc); 699 basic_string<wchar_t> __ws; 700 if (!__str_codecvt_in(__first, __last, __ws, __cvt)) 701 _GLIBCXX_THROW_OR_ABORT(filesystem_error( 702 "Cannot convert character sequence", 703 std::make_error_code(errc::illegal_byte_sequence))); 704 #ifdef _GLIBCXX_FILESYSTEM_IS_WINDOWS 705 return __ws; 706 #else 707 return _Cvt<wchar_t>::_S_convert(__ws.data(), __ws.data() + __ws.size()); 708 #endif 709 #else 710 return {__first, __last}; 711 #endif 712 } 713 714 std::size_t 715 fs::hash_value(const path& p) noexcept 716 { 717 // [path.non-member] 718 // "If for two paths, p1 == p2 then hash_value(p1) == hash_value(p2)." 719 // Equality works as if by traversing the range [begin(), end()), meaning 720 // e.g. path("a//b") == path("a/b"), so we cannot simply hash _M_pathname 721 // but need to iterate over individual elements. Use the hash_combine from 722 // http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n3876.pdf 723 size_t seed = 0; 724 for (const auto& x : p) 725 { 726 seed ^= std::hash<path::string_type>()(x.native()) + 0x9e3779b9 727 + (seed<<6) + (seed>>2); 728 } 729 return seed; 730 } 731 732 namespace std 733 { 734 _GLIBCXX_BEGIN_NAMESPACE_VERSION 735 namespace filesystem 736 { 737 string 738 fs_err_concat(const string& __what, const string& __path1, 739 const string& __path2) 740 { 741 const size_t __len = 18 + __what.length() 742 + (__path1.length() ? __path1.length() + 3 : 0) 743 + (__path2.length() ? __path2.length() + 3 : 0); 744 string __ret; 745 __ret.reserve(__len); 746 __ret = "filesystem error: "; 747 __ret += __what; 748 if (!__path1.empty()) 749 { 750 __ret += " ["; 751 __ret += __path1; 752 __ret += ']'; 753 } 754 if (!__path2.empty()) 755 { 756 __ret += " ["; 757 __ret += __path2; 758 __ret += ']'; 759 } 760 return __ret; 761 } 762 763 _GLIBCXX_BEGIN_NAMESPACE_CXX11 764 765 std::string filesystem_error::_M_gen_what() 766 { 767 return fs_err_concat(system_error::what(), _M_path1.native(), 768 _M_path2.native()); 769 } 770 771 _GLIBCXX_END_NAMESPACE_CXX11 772 773 } // filesystem 774 _GLIBCXX_END_NAMESPACE_VERSION 775 } // std 776