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