1 // Class experimental::filesystem::path -*- C++ -*- 2 3 // Copyright (C) 2014-2022 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 <experimental/filesystem> 30 31 namespace fs = std::experimental::filesystem; 32 using fs::path; 33 34 fs::filesystem_error::~filesystem_error() = default; 35 36 constexpr path::value_type path::preferred_separator [[gnu::used]]; 37 38 path& 39 path::remove_filename() 40 { 41 if (_M_type == _Type::_Multi) 42 { 43 if (!_M_cmpts.empty()) 44 { 45 auto cmpt = std::prev(_M_cmpts.end()); 46 _M_pathname.erase(cmpt->_M_pos); 47 _M_cmpts.erase(cmpt); 48 _M_trim(); 49 } 50 } 51 else 52 clear(); 53 return *this; 54 } 55 56 path& 57 path::replace_filename(const path& replacement) 58 { 59 remove_filename(); 60 operator/=(replacement); 61 return *this; 62 } 63 64 #ifdef _GLIBCXX_FILESYSTEM_IS_WINDOWS 65 const fs::path::value_type dot = L'.'; 66 #else 67 const fs::path::value_type dot = '.'; 68 #endif 69 70 path& 71 path::replace_extension(const path& replacement) 72 { 73 auto ext = _M_find_extension(); 74 if (ext.first && ext.second != string_type::npos) 75 { 76 if (ext.first == &_M_pathname) 77 _M_pathname.erase(ext.second); 78 else 79 { 80 const auto& back = _M_cmpts.back(); 81 if (ext.first != &back._M_pathname) 82 _GLIBCXX_THROW_OR_ABORT( 83 std::logic_error("path::replace_extension failed")); 84 _M_pathname.erase(back._M_pos + ext.second); 85 } 86 } 87 if (!replacement.empty() && replacement.native()[0] != dot) 88 _M_pathname += dot; 89 _M_pathname += replacement.native(); 90 _M_split_cmpts(); 91 return *this; 92 } 93 94 namespace 95 { 96 template<typename Iter1, typename Iter2> 97 int do_compare(Iter1 begin1, Iter1 end1, Iter2 begin2, Iter2 end2) 98 { 99 int cmpt = 1; 100 while (begin1 != end1 && begin2 != end2) 101 { 102 if (begin1->native() < begin2->native()) 103 return -cmpt; 104 if (begin1->native() > begin2->native()) 105 return +cmpt; 106 ++begin1; 107 ++begin2; 108 ++cmpt; 109 } 110 if (begin1 == end1) 111 { 112 if (begin2 == end2) 113 return 0; 114 return -cmpt; 115 } 116 return +cmpt; 117 } 118 } 119 120 int 121 path::compare(const path& p) const noexcept 122 { 123 struct CmptRef 124 { 125 const path* ptr; 126 const string_type& native() const noexcept { return ptr->native(); } 127 }; 128 129 if (_M_type == _Type::_Multi && p._M_type == _Type::_Multi) 130 return do_compare(_M_cmpts.begin(), _M_cmpts.end(), 131 p._M_cmpts.begin(), p._M_cmpts.end()); 132 else if (_M_type == _Type::_Multi) 133 { 134 CmptRef c[1] = { { &p } }; 135 return do_compare(_M_cmpts.begin(), _M_cmpts.end(), c, c+1); 136 } 137 else if (p._M_type == _Type::_Multi) 138 { 139 CmptRef c[1] = { { this } }; 140 return do_compare(c, c+1, p._M_cmpts.begin(), p._M_cmpts.end()); 141 } 142 else 143 return _M_pathname.compare(p._M_pathname); 144 } 145 146 path 147 path::root_name() const 148 { 149 path __ret; 150 if (_M_type == _Type::_Root_name) 151 __ret = *this; 152 else if (_M_cmpts.size() && _M_cmpts.begin()->_M_type == _Type::_Root_name) 153 __ret = *_M_cmpts.begin(); 154 return __ret; 155 } 156 157 path 158 path::root_directory() const 159 { 160 path __ret; 161 if (_M_type == _Type::_Root_dir) 162 __ret = *this; 163 else if (!_M_cmpts.empty()) 164 { 165 auto __it = _M_cmpts.begin(); 166 if (__it->_M_type == _Type::_Root_name) 167 ++__it; 168 if (__it != _M_cmpts.end() && __it->_M_type == _Type::_Root_dir) 169 __ret = *__it; 170 } 171 return __ret; 172 } 173 174 175 path 176 path::root_path() const 177 { 178 path __ret; 179 if (_M_type == _Type::_Root_name || _M_type == _Type::_Root_dir) 180 __ret = *this; 181 else if (!_M_cmpts.empty()) 182 { 183 auto __it = _M_cmpts.begin(); 184 if (__it->_M_type == _Type::_Root_name) 185 { 186 __ret = *__it++; 187 if (__it != _M_cmpts.end() && __it->_M_type == _Type::_Root_dir) 188 { 189 __ret._M_pathname += preferred_separator; 190 __ret._M_split_cmpts(); 191 } 192 } 193 else if (__it->_M_type == _Type::_Root_dir) 194 __ret = *__it; 195 } 196 return __ret; 197 } 198 199 path 200 path::relative_path() const 201 { 202 path __ret; 203 if (_M_type == _Type::_Filename) 204 __ret = *this; 205 else if (!_M_cmpts.empty()) 206 { 207 auto __it = _M_cmpts.begin(); 208 if (__it->_M_type == _Type::_Root_name) 209 ++__it; 210 if (__it != _M_cmpts.end() && __it->_M_type == _Type::_Root_dir) 211 ++__it; 212 if (__it != _M_cmpts.end()) 213 __ret.assign(_M_pathname.substr(__it->_M_pos)); 214 } 215 return __ret; 216 } 217 218 path 219 path::parent_path() const 220 { 221 path __ret; 222 if (_M_cmpts.size() < 2) 223 return __ret; 224 for (auto __it = _M_cmpts.begin(), __end = std::prev(_M_cmpts.end()); 225 __it != __end; ++__it) 226 { 227 __ret /= *__it; 228 } 229 return __ret; 230 } 231 232 bool 233 path::has_root_name() const 234 { 235 if (_M_type == _Type::_Root_name) 236 return true; 237 if (!_M_cmpts.empty() && _M_cmpts.begin()->_M_type == _Type::_Root_name) 238 return true; 239 return false; 240 } 241 242 bool 243 path::has_root_directory() const 244 { 245 if (_M_type == _Type::_Root_dir) 246 return true; 247 if (!_M_cmpts.empty()) 248 { 249 auto __it = _M_cmpts.begin(); 250 if (__it->_M_type == _Type::_Root_name) 251 ++__it; 252 if (__it != _M_cmpts.end() && __it->_M_type == _Type::_Root_dir) 253 return true; 254 } 255 return false; 256 } 257 258 bool 259 path::has_root_path() const 260 { 261 if (_M_type == _Type::_Root_name || _M_type == _Type::_Root_dir) 262 return true; 263 if (!_M_cmpts.empty()) 264 { 265 auto __type = _M_cmpts.front()._M_type; 266 if (__type == _Type::_Root_name || __type == _Type::_Root_dir) 267 return true; 268 } 269 return false; 270 } 271 272 bool 273 path::has_relative_path() const 274 { 275 if (_M_type == _Type::_Filename) 276 return true; 277 if (!_M_cmpts.empty()) 278 { 279 auto __it = _M_cmpts.begin(); 280 if (__it->_M_type == _Type::_Root_name) 281 ++__it; 282 if (__it != _M_cmpts.end() && __it->_M_type == _Type::_Root_dir) 283 ++__it; 284 if (__it != _M_cmpts.end()) 285 return true; 286 } 287 return false; 288 } 289 290 291 bool 292 path::has_parent_path() const 293 { 294 return _M_cmpts.size() > 1; 295 } 296 297 bool 298 path::has_filename() const 299 { 300 return !empty(); 301 } 302 303 std::pair<const path::string_type*, std::size_t> 304 path::_M_find_extension() const 305 { 306 const string_type* s = nullptr; 307 308 if (_M_type != _Type::_Multi) 309 s = &_M_pathname; 310 else if (!_M_cmpts.empty()) 311 { 312 const auto& c = _M_cmpts.back(); 313 if (c._M_type == _Type::_Filename) 314 s = &c._M_pathname; 315 } 316 317 if (s) 318 { 319 if (auto sz = s->size()) 320 { 321 if (sz <= 2 && (*s)[0] == dot) 322 { 323 if (sz == 1 || (*s)[1] == dot) // filename is "." or ".." 324 return { s, string_type::npos }; 325 else 326 return { s, 0 }; // filename is like ".?" 327 } 328 return { s, s->rfind(dot) }; 329 } 330 } 331 return {}; 332 } 333 334 void 335 path::_M_split_cmpts() 336 { 337 _M_type = _Type::_Multi; 338 _M_cmpts.clear(); 339 340 // Use const-reference to access _M_pathname, to avoid "leaking" COW string. 341 const auto& pathname = _M_pathname; 342 343 if (pathname.empty()) 344 return; 345 346 { 347 // Approximate count of components, to reserve space in _M_cmpts vector: 348 int count = 1; 349 bool saw_sep_last = _S_is_dir_sep(pathname[0]); 350 bool saw_non_sep = !saw_sep_last; 351 for (value_type c : pathname) 352 { 353 if (_S_is_dir_sep(c)) 354 saw_sep_last = true; 355 else if (saw_sep_last) 356 { 357 ++count; 358 saw_sep_last = false; 359 saw_non_sep = true; 360 } 361 } 362 if (saw_non_sep && saw_sep_last) 363 ++count; // empty filename after trailing slash 364 if (count > 1) 365 _M_cmpts.reserve(count); 366 } 367 368 size_t pos = 0; 369 const size_t len = pathname.size(); 370 371 // look for root name or root directory 372 if (_S_is_dir_sep(pathname[0])) 373 { 374 // look for root name, such as "//" or "//foo" 375 if (len > 1 && pathname[1] == pathname[0]) 376 { 377 if (len == 2) 378 { 379 // entire path is just "//" 380 _M_type = _Type::_Root_name; 381 return; 382 } 383 384 if (!_S_is_dir_sep(pathname[2])) 385 { 386 // got root name, find its end 387 pos = 3; 388 while (pos < len && !_S_is_dir_sep(pathname[pos])) 389 ++pos; 390 if (pos == len) 391 { 392 _M_type = _Type::_Root_name; 393 return; 394 } 395 _M_add_root_name(pos); 396 _M_add_root_dir(pos); 397 } 398 else 399 { 400 // got something like "///foo" which is just a root directory 401 // composed of multiple redundant directory separators 402 _M_add_root_dir(0); 403 } 404 } 405 else if (len == 1) // got root directory only 406 { 407 _M_type = _Type::_Root_dir; 408 return; 409 } 410 else // got root directory 411 _M_add_root_dir(0); 412 ++pos; 413 } 414 #ifdef _GLIBCXX_FILESYSTEM_IS_WINDOWS 415 else if (len > 1 && pathname[1] == L':') 416 { 417 // got disk designator 418 if (len == 2) 419 { 420 _M_type = _Type::_Root_name; 421 return; 422 } 423 _M_add_root_name(2); 424 if (len > 2 && _S_is_dir_sep(pathname[2])) 425 _M_add_root_dir(2); 426 pos = 2; 427 } 428 #endif 429 else 430 { 431 size_t n = 1; 432 for (; n < pathname.size() && !_S_is_dir_sep(pathname[n]); ++n) 433 { } 434 if (n == pathname.size()) 435 { 436 _M_type = _Type::_Filename; 437 return; 438 } 439 } 440 441 size_t back = pos; 442 while (pos < len) 443 { 444 if (_S_is_dir_sep(pathname[pos])) 445 { 446 if (back != pos) 447 _M_add_filename(back, pos - back); 448 back = ++pos; 449 } 450 else 451 ++pos; 452 } 453 454 if (back != pos) 455 _M_add_filename(back, pos - back); 456 else if (_S_is_dir_sep(pathname.back())) 457 { 458 // [path.itr]/8 459 // "Dot, if one or more trailing non-root slash characters are present." 460 if (_M_cmpts.back()._M_type == _Type::_Filename) 461 { 462 const auto& last = _M_cmpts.back(); 463 pos = last._M_pos + last._M_pathname.size(); 464 _M_cmpts.emplace_back(string_type(1, dot), _Type::_Filename, pos); 465 } 466 } 467 468 _M_trim(); 469 } 470 471 void 472 path::_M_add_root_name(size_t n) 473 { 474 _M_cmpts.emplace_back(_M_pathname.substr(0, n), _Type::_Root_name, 0); 475 } 476 477 void 478 path::_M_add_root_dir(size_t pos) 479 { 480 _M_cmpts.emplace_back(_M_pathname.substr(pos, 1), _Type::_Root_dir, pos); 481 } 482 483 void 484 path::_M_add_filename(size_t pos, size_t n) 485 { 486 _M_cmpts.emplace_back(_M_pathname.substr(pos, n), _Type::_Filename, pos); 487 } 488 489 void 490 path::_M_trim() 491 { 492 if (_M_cmpts.size() == 1) 493 { 494 _M_type = _M_cmpts.front()._M_type; 495 _M_cmpts.clear(); 496 } 497 } 498 499 path::string_type 500 path::_S_convert_loc(const char* __first, const char* __last, 501 const std::locale& __loc) 502 { 503 #if _GLIBCXX_USE_WCHAR_T 504 auto& __cvt = std::use_facet<codecvt<wchar_t, char, mbstate_t>>(__loc); 505 basic_string<wchar_t> __ws; 506 if (!__str_codecvt_in_all(__first, __last, __ws, __cvt)) 507 _GLIBCXX_THROW_OR_ABORT(filesystem_error( 508 "Cannot convert character sequence", 509 std::make_error_code(errc::illegal_byte_sequence))); 510 #ifdef _GLIBCXX_FILESYSTEM_IS_WINDOWS 511 return __ws; 512 #else 513 return _Cvt<wchar_t>::_S_convert(__ws.data(), __ws.data() + __ws.size()); 514 #endif 515 #else 516 return {__first, __last}; 517 #endif 518 } 519 520 std::size_t 521 fs::hash_value(const path& p) noexcept 522 { 523 // [path.non-member] 524 // "If for two paths, p1 == p2 then hash_value(p1) == hash_value(p2)." 525 // Equality works as if by traversing the range [begin(), end()), meaning 526 // e.g. path("a//b") == path("a/b"), so we cannot simply hash _M_pathname 527 // but need to iterate over individual elements. Use the hash_combine from 528 // http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n3876.pdf 529 size_t seed = 0; 530 for (const auto& x : p) 531 { 532 seed ^= std::hash<path::string_type>()(x.native()) + 0x9e3779b9 533 + (seed<<6) + (seed>>2); 534 } 535 return seed; 536 } 537 538 #include <experimental/string_view> 539 540 std::string 541 fs::filesystem_error::_M_gen_what() 542 { 543 const std::string pstr1 = _M_path1.u8string(); 544 const std::string pstr2 = _M_path2.u8string(); 545 experimental::string_view s = this->system_error::what(); 546 const size_t len = 18 + s.length() 547 + (pstr1.length() || pstr2.length() ? pstr1.length() + 3 : 0) 548 + (pstr2.length() ? pstr2.length() + 3 : 0); 549 std::string w; 550 w.reserve(len); 551 w = "filesystem error: "; 552 w.append(s.data(), s.length()); 553 if (!pstr1.empty()) 554 { 555 w += " ["; 556 w += pstr1; 557 w += ']'; 558 } 559 if (!pstr2.empty()) 560 { 561 if (pstr1.empty()) 562 w += " []"; 563 w += " ["; 564 w += pstr2; 565 w += ']'; 566 } 567 return w; 568 } 569