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