| // Class experimental::filesystem::path -*- C++ -*- | |
| // Copyright (C) 2014-2026 Free Software Foundation, Inc. | |
| // | |
| // This file is part of the GNU ISO C++ Library. This library is free | |
| // software; you can redistribute it and/or modify it under the | |
| // terms of the GNU General Public License as published by the | |
| // Free Software Foundation; either version 3, or (at your option) | |
| // any later version. | |
| // This library is distributed in the hope that it will be useful, | |
| // but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
| // GNU General Public License for more details. | |
| // Under Section 7 of GPL version 3, you are granted additional | |
| // permissions described in the GCC Runtime Library Exception, version | |
| // 3.1, as published by the Free Software Foundation. | |
| // You should have received a copy of the GNU General Public License and | |
| // a copy of the GCC Runtime Library Exception along with this program; | |
| // see the files COPYING3 and COPYING.RUNTIME respectively. If not, see | |
| // <http://www.gnu.org/licenses/>. | |
| #ifndef _GLIBCXX_USE_CXX11_ABI | |
| # define _GLIBCXX_USE_CXX11_ABI 1 | |
| #endif | |
| #include <experimental/filesystem> | |
| namespace fs = std::experimental::filesystem; | |
| using fs::path; | |
| fs::filesystem_error::~filesystem_error() = default; | |
| constexpr path::value_type path::preferred_separator [[gnu::used]]; | |
| path& | |
| path::remove_filename() | |
| { | |
| if (_M_type == _Type::_Multi) | |
| { | |
| if (!_M_cmpts.empty()) | |
| { | |
| auto cmpt = std::prev(_M_cmpts.end()); | |
| _M_pathname.erase(cmpt->_M_pos); | |
| _M_cmpts.erase(cmpt); | |
| _M_trim(); | |
| } | |
| } | |
| else | |
| clear(); | |
| return *this; | |
| } | |
| path& | |
| path::replace_filename(const path& replacement) | |
| { | |
| remove_filename(); | |
| operator/=(replacement); | |
| return *this; | |
| } | |
| #ifdef _GLIBCXX_FILESYSTEM_IS_WINDOWS | |
| const fs::path::value_type dot = L'.'; | |
| #else | |
| const fs::path::value_type dot = '.'; | |
| #endif | |
| path& | |
| path::replace_extension(const path& replacement) | |
| { | |
| auto ext = _M_find_extension(); | |
| if (ext.first && ext.second != string_type::npos) | |
| { | |
| if (ext.first == &_M_pathname) | |
| _M_pathname.erase(ext.second); | |
| else | |
| { | |
| const auto& back = _M_cmpts.back(); | |
| if (ext.first != &back._M_pathname) | |
| _GLIBCXX_THROW_OR_ABORT( | |
| std::logic_error("path::replace_extension failed")); | |
| _M_pathname.erase(back._M_pos + ext.second); | |
| } | |
| } | |
| if (!replacement.empty() && replacement.native()[0] != dot) | |
| _M_pathname += dot; | |
| _M_pathname += replacement.native(); | |
| _M_split_cmpts(); | |
| return *this; | |
| } | |
| namespace | |
| { | |
| template<typename Iter1, typename Iter2> | |
| int do_compare(Iter1 begin1, Iter1 end1, Iter2 begin2, Iter2 end2) | |
| { | |
| int cmpt = 1; | |
| while (begin1 != end1 && begin2 != end2) | |
| { | |
| if (begin1->native() < begin2->native()) | |
| return -cmpt; | |
| if (begin1->native() > begin2->native()) | |
| return +cmpt; | |
| ++begin1; | |
| ++begin2; | |
| ++cmpt; | |
| } | |
| if (begin1 == end1) | |
| { | |
| if (begin2 == end2) | |
| return 0; | |
| return -cmpt; | |
| } | |
| return +cmpt; | |
| } | |
| } | |
| int | |
| path::compare(const path& p) const noexcept | |
| { | |
| struct CmptRef | |
| { | |
| const path* ptr; | |
| const string_type& native() const noexcept { return ptr->native(); } | |
| }; | |
| if (_M_type == _Type::_Multi && p._M_type == _Type::_Multi) | |
| return do_compare(_M_cmpts.begin(), _M_cmpts.end(), | |
| p._M_cmpts.begin(), p._M_cmpts.end()); | |
| else if (_M_type == _Type::_Multi) | |
| { | |
| CmptRef c[1] = { { &p } }; | |
| return do_compare(_M_cmpts.begin(), _M_cmpts.end(), c, c+1); | |
| } | |
| else if (p._M_type == _Type::_Multi) | |
| { | |
| CmptRef c[1] = { { this } }; | |
| return do_compare(c, c+1, p._M_cmpts.begin(), p._M_cmpts.end()); | |
| } | |
| else | |
| return _M_pathname.compare(p._M_pathname); | |
| } | |
| path | |
| path::root_name() const | |
| { | |
| path __ret; | |
| if (_M_type == _Type::_Root_name) | |
| __ret = *this; | |
| else if (_M_cmpts.size() && _M_cmpts.begin()->_M_type == _Type::_Root_name) | |
| __ret = *_M_cmpts.begin(); | |
| return __ret; | |
| } | |
| path | |
| path::root_directory() const | |
| { | |
| path __ret; | |
| if (_M_type == _Type::_Root_dir) | |
| __ret = *this; | |
| else if (!_M_cmpts.empty()) | |
| { | |
| auto __it = _M_cmpts.begin(); | |
| if (__it->_M_type == _Type::_Root_name) | |
| ++__it; | |
| if (__it != _M_cmpts.end() && __it->_M_type == _Type::_Root_dir) | |
| __ret = *__it; | |
| } | |
| return __ret; | |
| } | |
| path | |
| path::root_path() const | |
| { | |
| path __ret; | |
| if (_M_type == _Type::_Root_name || _M_type == _Type::_Root_dir) | |
| __ret = *this; | |
| else if (!_M_cmpts.empty()) | |
| { | |
| auto __it = _M_cmpts.begin(); | |
| if (__it->_M_type == _Type::_Root_name) | |
| { | |
| __ret = *__it++; | |
| if (__it != _M_cmpts.end() && __it->_M_type == _Type::_Root_dir) | |
| { | |
| __ret._M_pathname += preferred_separator; | |
| __ret._M_split_cmpts(); | |
| } | |
| } | |
| else if (__it->_M_type == _Type::_Root_dir) | |
| __ret = *__it; | |
| } | |
| return __ret; | |
| } | |
| path | |
| path::relative_path() const | |
| { | |
| path __ret; | |
| if (_M_type == _Type::_Filename) | |
| __ret = *this; | |
| else if (!_M_cmpts.empty()) | |
| { | |
| auto __it = _M_cmpts.begin(); | |
| if (__it->_M_type == _Type::_Root_name) | |
| ++__it; | |
| if (__it != _M_cmpts.end() && __it->_M_type == _Type::_Root_dir) | |
| ++__it; | |
| if (__it != _M_cmpts.end()) | |
| __ret.assign(_M_pathname.substr(__it->_M_pos)); | |
| } | |
| return __ret; | |
| } | |
| path | |
| path::parent_path() const | |
| { | |
| path __ret; | |
| if (_M_cmpts.size() < 2) | |
| return __ret; | |
| for (auto __it = _M_cmpts.begin(), __end = std::prev(_M_cmpts.end()); | |
| __it != __end; ++__it) | |
| { | |
| __ret /= *__it; | |
| } | |
| return __ret; | |
| } | |
| bool | |
| path::has_root_name() const | |
| { | |
| if (_M_type == _Type::_Root_name) | |
| return true; | |
| if (!_M_cmpts.empty() && _M_cmpts.begin()->_M_type == _Type::_Root_name) | |
| return true; | |
| return false; | |
| } | |
| bool | |
| path::has_root_directory() const | |
| { | |
| if (_M_type == _Type::_Root_dir) | |
| return true; | |
| if (!_M_cmpts.empty()) | |
| { | |
| auto __it = _M_cmpts.begin(); | |
| if (__it->_M_type == _Type::_Root_name) | |
| ++__it; | |
| if (__it != _M_cmpts.end() && __it->_M_type == _Type::_Root_dir) | |
| return true; | |
| } | |
| return false; | |
| } | |
| bool | |
| path::has_root_path() const | |
| { | |
| if (_M_type == _Type::_Root_name || _M_type == _Type::_Root_dir) | |
| return true; | |
| if (!_M_cmpts.empty()) | |
| { | |
| auto __type = _M_cmpts.front()._M_type; | |
| if (__type == _Type::_Root_name || __type == _Type::_Root_dir) | |
| return true; | |
| } | |
| return false; | |
| } | |
| bool | |
| path::has_relative_path() const | |
| { | |
| if (_M_type == _Type::_Filename) | |
| return true; | |
| if (!_M_cmpts.empty()) | |
| { | |
| auto __it = _M_cmpts.begin(); | |
| if (__it->_M_type == _Type::_Root_name) | |
| ++__it; | |
| if (__it != _M_cmpts.end() && __it->_M_type == _Type::_Root_dir) | |
| ++__it; | |
| if (__it != _M_cmpts.end()) | |
| return true; | |
| } | |
| return false; | |
| } | |
| bool | |
| path::has_parent_path() const | |
| { | |
| return _M_cmpts.size() > 1; | |
| } | |
| bool | |
| path::has_filename() const | |
| { | |
| return !empty(); | |
| } | |
| std::pair<const path::string_type*, std::size_t> | |
| path::_M_find_extension() const | |
| { | |
| const string_type* s = nullptr; | |
| if (_M_type != _Type::_Multi) | |
| s = &_M_pathname; | |
| else if (!_M_cmpts.empty()) | |
| { | |
| const auto& c = _M_cmpts.back(); | |
| if (c._M_type == _Type::_Filename) | |
| s = &c._M_pathname; | |
| } | |
| if (s) | |
| { | |
| if (auto sz = s->size()) | |
| { | |
| if (sz <= 2 && (*s)[0] == dot) | |
| { | |
| if (sz == 1 || (*s)[1] == dot) // filename is "." or ".." | |
| return { s, string_type::npos }; | |
| else | |
| return { s, 0 }; // filename is like ".?" | |
| } | |
| return { s, s->rfind(dot) }; | |
| } | |
| } | |
| return {}; | |
| } | |
| void | |
| path::_M_split_cmpts() | |
| { | |
| _M_type = _Type::_Multi; | |
| _M_cmpts.clear(); | |
| // Use const-reference to access _M_pathname, to avoid "leaking" COW string. | |
| const auto& pathname = _M_pathname; | |
| if (pathname.empty()) | |
| return; | |
| { | |
| // Approximate count of components, to reserve space in _M_cmpts vector: | |
| int count = 1; | |
| bool saw_sep_last = _S_is_dir_sep(pathname[0]); | |
| bool saw_non_sep = !saw_sep_last; | |
| for (value_type c : pathname) | |
| { | |
| if (_S_is_dir_sep(c)) | |
| saw_sep_last = true; | |
| else if (saw_sep_last) | |
| { | |
| ++count; | |
| saw_sep_last = false; | |
| saw_non_sep = true; | |
| } | |
| } | |
| if (saw_non_sep && saw_sep_last) | |
| ++count; // empty filename after trailing slash | |
| if (count > 1) | |
| _M_cmpts.reserve(count); | |
| } | |
| size_t pos = 0; | |
| const size_t len = pathname.size(); | |
| // look for root name or root directory | |
| if (_S_is_dir_sep(pathname[0])) | |
| { | |
| // look for root name, such as "//" or "//foo" | |
| if (len > 1 && pathname[1] == pathname[0]) | |
| { | |
| if (len == 2) | |
| { | |
| // entire path is just "//" | |
| _M_type = _Type::_Root_name; | |
| return; | |
| } | |
| if (!_S_is_dir_sep(pathname[2])) | |
| { | |
| // got root name, find its end | |
| pos = 3; | |
| while (pos < len && !_S_is_dir_sep(pathname[pos])) | |
| ++pos; | |
| if (pos == len) | |
| { | |
| _M_type = _Type::_Root_name; | |
| return; | |
| } | |
| _M_add_root_name(pos); | |
| _M_add_root_dir(pos); | |
| } | |
| else | |
| { | |
| // got something like "///foo" which is just a root directory | |
| // composed of multiple redundant directory separators | |
| _M_add_root_dir(0); | |
| } | |
| } | |
| else if (len == 1) // got root directory only | |
| { | |
| _M_type = _Type::_Root_dir; | |
| return; | |
| } | |
| else // got root directory | |
| _M_add_root_dir(0); | |
| ++pos; | |
| } | |
| #ifdef _GLIBCXX_FILESYSTEM_IS_WINDOWS | |
| else if (len > 1 && pathname[1] == L':') | |
| { | |
| // got disk designator | |
| if (len == 2) | |
| { | |
| _M_type = _Type::_Root_name; | |
| return; | |
| } | |
| _M_add_root_name(2); | |
| if (len > 2 && _S_is_dir_sep(pathname[2])) | |
| _M_add_root_dir(2); | |
| pos = 2; | |
| } | |
| #endif | |
| else | |
| { | |
| size_t n = 1; | |
| for (; n < pathname.size() && !_S_is_dir_sep(pathname[n]); ++n) | |
| { } | |
| if (n == pathname.size()) | |
| { | |
| _M_type = _Type::_Filename; | |
| return; | |
| } | |
| } | |
| size_t back = pos; | |
| while (pos < len) | |
| { | |
| if (_S_is_dir_sep(pathname[pos])) | |
| { | |
| if (back != pos) | |
| _M_add_filename(back, pos - back); | |
| back = ++pos; | |
| } | |
| else | |
| ++pos; | |
| } | |
| if (back != pos) | |
| _M_add_filename(back, pos - back); | |
| else if (_S_is_dir_sep(pathname.back())) | |
| { | |
| // [path.itr]/8 | |
| // "Dot, if one or more trailing non-root slash characters are present." | |
| if (_M_cmpts.back()._M_type == _Type::_Filename) | |
| { | |
| const auto& last = _M_cmpts.back(); | |
| pos = last._M_pos + last._M_pathname.size(); | |
| _M_cmpts.emplace_back(string_type(1, dot), _Type::_Filename, pos); | |
| } | |
| } | |
| _M_trim(); | |
| } | |
| void | |
| path::_M_add_root_name(size_t n) | |
| { | |
| _M_cmpts.emplace_back(_M_pathname.substr(0, n), _Type::_Root_name, 0); | |
| } | |
| void | |
| path::_M_add_root_dir(size_t pos) | |
| { | |
| _M_cmpts.emplace_back(_M_pathname.substr(pos, 1), _Type::_Root_dir, pos); | |
| } | |
| void | |
| path::_M_add_filename(size_t pos, size_t n) | |
| { | |
| _M_cmpts.emplace_back(_M_pathname.substr(pos, n), _Type::_Filename, pos); | |
| } | |
| void | |
| path::_M_trim() | |
| { | |
| if (_M_cmpts.size() == 1) | |
| { | |
| _M_type = _M_cmpts.front()._M_type; | |
| _M_cmpts.clear(); | |
| } | |
| } | |
| path::string_type | |
| path::_S_convert_loc(const char* __first, const char* __last, | |
| [[maybe_unused]] const std::locale& __loc) | |
| { | |
| #if _GLIBCXX_USE_WCHAR_T | |
| auto& __cvt = std::use_facet<codecvt<wchar_t, char, mbstate_t>>(__loc); | |
| basic_string<wchar_t> __ws; | |
| if (!__str_codecvt_in_all(__first, __last, __ws, __cvt)) | |
| _GLIBCXX_THROW_OR_ABORT(filesystem_error( | |
| "Cannot convert character sequence", | |
| std::make_error_code(errc::illegal_byte_sequence))); | |
| #ifdef _GLIBCXX_FILESYSTEM_IS_WINDOWS | |
| return __ws; | |
| #else | |
| return _Cvt<wchar_t>::_S_convert(__ws.data(), __ws.data() + __ws.size()); | |
| #endif | |
| #else | |
| return {__first, __last}; | |
| #endif | |
| } | |
| std::size_t | |
| fs::hash_value(const path& p) noexcept | |
| { | |
| // [path.non-member] | |
| // "If for two paths, p1 == p2 then hash_value(p1) == hash_value(p2)." | |
| // Equality works as if by traversing the range [begin(), end()), meaning | |
| // e.g. path("a//b") == path("a/b"), so we cannot simply hash _M_pathname | |
| // but need to iterate over individual elements. Use the hash_combine from | |
| // http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n3876.pdf | |
| size_t seed = 0; | |
| for (const auto& x : p) | |
| { | |
| seed ^= std::hash<path::string_type>()(x.native()) + 0x9e3779b9 | |
| + (seed<<6) + (seed>>2); | |
| } | |
| return seed; | |
| } | |
| #include <experimental/string_view> | |
| std::string | |
| fs::filesystem_error::_M_gen_what() | |
| { | |
| const std::string pstr1 = _M_path1.u8string(); | |
| const std::string pstr2 = _M_path2.u8string(); | |
| experimental::string_view s = this->system_error::what(); | |
| const size_t len = 18 + s.length() | |
| + (pstr1.length() || pstr2.length() ? pstr1.length() + 3 : 0) | |
| + (pstr2.length() ? pstr2.length() + 3 : 0); | |
| std::string w; | |
| w.reserve(len); | |
| w = "filesystem error: "; | |
| w.append(s.data(), s.length()); | |
| if (!pstr1.empty()) | |
| { | |
| w += " ["; | |
| w += pstr1; | |
| w += ']'; | |
| } | |
| if (!pstr2.empty()) | |
| { | |
| if (pstr1.empty()) | |
| w += " []"; | |
| w += " ["; | |
| w += pstr2; | |
| w += ']'; | |
| } | |
| return w; | |
| } |