// Class filesystem::path -*- C++ -*- // Copyright (C) 2014-2022 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 // . #ifndef _GLIBCXX_USE_CXX11_ABI # define _GLIBCXX_USE_CXX11_ABI 1 #endif #ifdef __CYGWIN__ // Interpret "//x" as a root-name, not root-dir + filename # define SLASHSLASH_IS_ROOTNAME 1 #endif #include #include #include #include namespace fs = std::filesystem; using fs::path; static inline bool is_dir_sep(path::value_type ch) { #ifdef _GLIBCXX_FILESYSTEM_IS_WINDOWS return ch == L'/' || ch == path::preferred_separator; #else return ch == '/'; #endif } #ifdef _GLIBCXX_FILESYSTEM_IS_WINDOWS static inline bool is_disk_designator(std::wstring_view s) { return s.length() == 2 && s[1] == L':'; } #endif struct path::_Parser { using string_view_type = std::basic_string_view; struct cmpt { string_view_type str; _Type type = _Type::_Multi; bool valid() const { return type != _Type::_Multi; } }; string_view_type input; string_view_type::size_type pos = 0; size_t origin; _Type last_type = _Type::_Multi; _Parser(string_view_type s, size_t o = 0) : input(s), origin(o) { } pair root_path() noexcept { pos = 0; pair root; const size_t len = input.size(); // look for root name or root directory if (len && is_dir_sep(input[0])) { #if SLASHSLASH_IS_ROOTNAME // look for root name, such as "//foo" if (len > 2 && input[1] == input[0]) { if (!is_dir_sep(input[2])) { // got root name, find its end pos = 3; while (pos < len && !is_dir_sep(input[pos])) ++pos; root.first.str = input.substr(0, pos); root.first.type = _Type::_Root_name; if (pos < len) // also got root directory { root.second.str = input.substr(pos, 1); root.second.type = _Type::_Root_dir; ++pos; } } else { // got something like "///foo" which is just a root directory // composed of multiple redundant directory separators root.first.str = input.substr(0, 1); root.first.type = _Type::_Root_dir; pos += 2; } } else #endif { root.first.str = input.substr(0, 1); root.first.type = _Type::_Root_dir; ++pos; } // Find the start of the first filename while (pos < len && is_dir_sep(input[pos])) ++pos; } #ifdef _GLIBCXX_FILESYSTEM_IS_WINDOWS else if (is_disk_designator(input.substr(0, 2))) { // got disk designator root.first.str = input.substr(0, 2); root.first.type = _Type::_Root_name; if (len > 2 && is_dir_sep(input[2])) { root.second.str = input.substr(2, 1); root.second.type = _Type::_Root_dir; } pos = input.find_first_not_of(L"/\\", 2); } #endif if (root.second.valid()) last_type = root.second.type; else last_type = root.first.type; return root; } cmpt next() noexcept { #ifdef _GLIBCXX_FILESYSTEM_IS_WINDOWS string_view_type sep = L"/\\"; #else char sep = '/'; #endif const int last_pos = pos; cmpt f; if (pos != input.npos) { pos = input.find_first_not_of(sep, pos); if (pos != input.npos) { const auto end = input.find_first_of(sep, pos); f.str = input.substr(pos, end - pos); f.type = _Type::_Filename; pos = end; } else if (last_type == _Type::_Filename || (last_pos == 0 && !input.empty())) { // [fs.path.itr]/4 An empty element, if trailing non-root // directory-separator present. __glibcxx_assert(is_dir_sep(input.back())); f.str = input.substr(input.length(), 0); f.type = _Type::_Filename; } } last_type = f.type; return f; } string_view_type::size_type offset(const cmpt& c) const noexcept { return origin + c.str.data() - input.data(); } }; struct path::_List::_Impl { using value_type = _Cmpt; _Impl(int cap) : _M_size(0), _M_capacity(cap) { } alignas(value_type) int _M_size; int _M_capacity; using iterator = value_type*; using const_iterator = const value_type*; iterator begin() { return reinterpret_cast(this + 1); } iterator end() { return begin() + size(); } const_iterator begin() const { return reinterpret_cast(this + 1); } const_iterator end() const { return begin() + size(); } const value_type& front() const { return *begin(); } const value_type& back() const { return end()[-1]; } int size() const { return _M_size; } int capacity() const { return _M_capacity; } bool empty() const { return _M_size == 0; } void clear() { std::destroy_n(begin(), _M_size); _M_size = 0; } void pop_back() { back().~_Cmpt(); --_M_size; } void _M_erase_from(const_iterator pos) { iterator first = begin() + (pos - begin()); iterator last = end(); std::destroy(first, last); _M_size -= last - first; } unique_ptr<_Impl, _Impl_deleter> copy() const { const auto n = size(); void* p = ::operator new(sizeof(_Impl) + n * sizeof(value_type)); unique_ptr<_Impl, _Impl_deleter> newptr(::new (p) _Impl{n}); std::uninitialized_copy_n(begin(), n, newptr->begin()); newptr->_M_size = n; return newptr; } // Clear the lowest two bits from the pointer (i.e. remove the _Type value) static _Impl* notype(_Impl* p) { constexpr uintptr_t mask = ~(uintptr_t)0x3; return reinterpret_cast<_Impl*>(reinterpret_cast(p) & mask); } }; void path::_List::_Impl_deleter::operator()(_Impl* p) const noexcept { p = _Impl::notype(p); if (p) { __glibcxx_assert(p->_M_size <= p->_M_capacity); p->clear(); ::operator delete(p, sizeof(*p) + p->_M_capacity * sizeof(value_type)); } } path::_List::_List() : _M_impl(reinterpret_cast<_Impl*>(_Type::_Filename)) { } path::_List::_List(const _List& other) { if (!other.empty()) _M_impl = other._M_impl->copy(); else type(other.type()); } path::_List& path::_List::operator=(const _List& other) { if (!other.empty()) { // copy in-place if there is capacity const int newsize = other._M_impl->size(); auto impl = _Impl::notype(_M_impl.get()); if (impl && impl->capacity() >= newsize) { const int oldsize = impl->_M_size; auto to = impl->begin(); auto from = other._M_impl->begin(); const int minsize = std::min(newsize, oldsize); for (int i = 0; i < minsize; ++i) to[i]._M_pathname.reserve(from[i]._M_pathname.length()); if (newsize > oldsize) { std::uninitialized_copy_n(from + oldsize, newsize - oldsize, to + oldsize); impl->_M_size = newsize; } else if (newsize < oldsize) impl->_M_erase_from(impl->begin() + newsize); std::copy_n(from, minsize, to); type(_Type::_Multi); } else _M_impl = other._M_impl->copy(); } else { clear(); type(other.type()); } return *this; } inline void path::_List::type(_Type t) noexcept { auto val = reinterpret_cast(_Impl::notype(_M_impl.release())); _M_impl.reset(reinterpret_cast<_Impl*>(val | (unsigned char)t)); } inline int path::_List::size() const noexcept { if (auto* ptr = _Impl::notype(_M_impl.get())) return ptr->size(); return 0; } inline int path::_List::capacity() const noexcept { if (auto* ptr = _Impl::notype(_M_impl.get())) return ptr->capacity(); return 0; } inline bool path::_List::empty() const noexcept { return size() == 0; } inline auto path::_List::begin() noexcept -> iterator { __glibcxx_assert(!empty()); if (auto* ptr = _Impl::notype(_M_impl.get())) return ptr->begin(); return nullptr; } inline auto path::_List::end() noexcept -> iterator { __glibcxx_assert(!empty()); if (auto* ptr = _Impl::notype(_M_impl.get())) return ptr->end(); return nullptr; } auto path::_List::begin() const noexcept -> const_iterator { __glibcxx_assert(!empty()); if (auto* ptr = _Impl::notype(_M_impl.get())) return ptr->begin(); return nullptr; } auto path::_List::end() const noexcept -> const_iterator { __glibcxx_assert(!empty()); if (auto* ptr = _Impl::notype(_M_impl.get())) return ptr->end(); return nullptr; } inline auto path::_List::front() noexcept -> value_type& { return *_M_impl->begin(); } inline auto path::_List::back() noexcept -> value_type& { return _M_impl->begin()[_M_impl->size() - 1]; } inline auto path::_List::front() const noexcept -> const value_type& { return *_M_impl->begin(); } inline auto path::_List::back() const noexcept -> const value_type& { return _M_impl->begin()[_M_impl->size() - 1]; } inline void path::_List::pop_back() { __glibcxx_assert(size() > 0); _M_impl->pop_back(); } inline void path::_List::_M_erase_from(const_iterator pos) { _M_impl->_M_erase_from(pos); } inline void path::_List::clear() { if (auto ptr = _Impl::notype(_M_impl.get())) ptr->clear(); } void path::_List::reserve(int newcap, bool exact = false) { // __glibcxx_assert(type() == _Type::_Multi); _Impl* curptr = _Impl::notype(_M_impl.get()); int curcap = curptr ? curptr->capacity() : 0; if (curcap < newcap) { if (!exact && newcap < int(1.5 * curcap)) newcap = 1.5 * curcap; void* p = ::operator new(sizeof(_Impl) + newcap * sizeof(value_type)); std::unique_ptr<_Impl, _Impl_deleter> newptr(::new(p) _Impl{newcap}); const int cursize = curptr ? curptr->size() : 0; if (cursize) { std::uninitialized_move_n(curptr->begin(), cursize, newptr->begin()); newptr->_M_size = cursize; } std::swap(newptr, _M_impl); } } path& path::operator=(const path& p) { if (&p == this) [[__unlikely__]] return *this; _M_pathname.reserve(p._M_pathname.length()); _M_cmpts = p._M_cmpts; // might throw _M_pathname = p._M_pathname; // won't throw because we reserved enough space return *this; } path& path::operator/=(const path& __p) { #ifdef _GLIBCXX_FILESYSTEM_IS_WINDOWS if (__p.is_absolute() || (__p.has_root_name() && __p.root_name() != root_name())) return operator=(__p); basic_string_view __lhs = _M_pathname; bool __add_sep = false; if (__p.has_root_directory()) { // Remove any root directory and relative path if (_M_type() != _Type::_Root_name) { if (!_M_cmpts.empty() && _M_cmpts.front()._M_type() == _Type::_Root_name) __lhs = _M_cmpts.front()._M_pathname; else __lhs = {}; } } else if (has_filename() || (!has_root_directory() && is_absolute())) __add_sep = true; basic_string_view __rhs = __p._M_pathname; // Omit any root-name from the generic format pathname: if (__p._M_type() == _Type::_Root_name) __rhs = {}; else if (!__p._M_cmpts.empty() && __p._M_cmpts.front()._M_type() == _Type::_Root_name) __rhs.remove_prefix(__p._M_cmpts.front()._M_pathname.size()); const size_t __len = __lhs.size() + (int)__add_sep + __rhs.size(); const int __maxcmpts = _M_cmpts.size() + __p._M_cmpts.size(); if (_M_pathname.capacity() < __len || _M_cmpts.capacity() < __maxcmpts) { // Construct new path and swap (strong exception-safety guarantee). string_type __tmp; __tmp.reserve(__len); __tmp = __lhs; if (__add_sep) __tmp += preferred_separator; __tmp += __rhs; path __newp = std::move(__tmp); swap(__newp); } else { _M_pathname = __lhs; if (__add_sep) _M_pathname += preferred_separator; _M_pathname += __rhs; __try { _M_split_cmpts(); } __catch (...) { __try { // try to restore original state _M_pathname.resize(__lhs.length()); _M_split_cmpts(); } __catch (...) { // give up, basic exception safety guarantee only: clear(); __throw_exception_again; } } } #else // POSIX version is simpler than the specification in the standard, // as any path with root-name or root-dir is absolute. if (__p.is_absolute() || this->empty()) { return operator=(__p); } using string_view_type = basic_string_view; string_view_type sep; if (has_filename()) sep = { &preferred_separator, 1 }; // need to add a separator #if SLASHSLASH_IS_ROOTNAME else if (_M_type() == _Type::_Root_name) // root-name with no root-dir sep = { &preferred_separator, 1 }; // need to add a separator #endif else if (__p.empty()) return *this; // nothing to do const auto orig_pathlen = _M_pathname.length(); const auto orig_size = _M_cmpts.size(); const auto orig_type = _M_type(); int capacity = 0; if (_M_type() == _Type::_Multi) capacity += _M_cmpts.size(); else if (!empty()) capacity += 1; if (__p._M_type() == _Type::_Multi) capacity += __p._M_cmpts.size(); else if (!__p.empty() || !sep.empty()) capacity += 1; #if SLASHSLASH_IS_ROOTNAME if (orig_type == _Type::_Root_name) ++capacity; // Need to insert root-directory after root-name #endif if (orig_type == _Type::_Multi) { const int curcap = _M_cmpts._M_impl->capacity(); if (capacity > curcap) capacity = std::max(capacity, (int) (curcap * 1.5)); } _M_pathname.reserve(_M_pathname.length() + sep.length() + __p._M_pathname.length()); __try { _M_pathname += sep; const auto basepos = _M_pathname.length(); _M_pathname += __p.native(); _M_cmpts.type(_Type::_Multi); _M_cmpts.reserve(capacity); _Cmpt* output = _M_cmpts._M_impl->end(); if (orig_type == _Type::_Multi) { // Remove empty final component if (_M_cmpts._M_impl->back().empty()) { _M_cmpts.pop_back(); --output; } } else if (orig_pathlen != 0) { // Create single component from original path string_view_type s(_M_pathname.data(), orig_pathlen); ::new(output++) _Cmpt(s, orig_type, 0); ++_M_cmpts._M_impl->_M_size; #if SLASHSLASH_IS_ROOTNAME if (orig_type == _Type::_Root_name) { ::new(output++) _Cmpt(sep, _Type::_Root_dir, orig_pathlen + sep.length()); ++_M_cmpts._M_impl->_M_size; } #endif } if (__p._M_type() == _Type::_Multi) { for (auto& c : *__p._M_cmpts._M_impl) { ::new(output++) _Cmpt(c._M_pathname, _Type::_Filename, c._M_pos + basepos); ++_M_cmpts._M_impl->_M_size; } } else if (!__p.empty() || !sep.empty()) { __glibcxx_assert(__p._M_type() == _Type::_Filename); ::new(output) _Cmpt(__p._M_pathname, __p._M_type(), basepos); ++_M_cmpts._M_impl->_M_size; } } __catch (...) { _M_pathname.resize(orig_pathlen); if (orig_type == _Type::_Multi) _M_cmpts._M_erase_from(_M_cmpts.begin() + orig_size); else _M_cmpts.clear(); _M_cmpts.type(orig_type); __throw_exception_again; } #endif return *this; } // [fs.path.append] void path::_M_append(basic_string_view s) { _Parser parser(s); auto root_path = parser.root_path(); #ifdef _GLIBCXX_FILESYSTEM_IS_WINDOWS bool is_absolute = root_path.second.type == _Type::_Root_dir; bool has_root_name = root_path.first.type == _Type::_Root_name; if (is_absolute || (has_root_name && root_path.first.str != root_name())) { operator=(s); return; } basic_string_view lhs = _M_pathname; bool add_sep = false; bool has_root_directory = root_path.first.type == _Type::_Root_dir || root_path.second.type == _Type::_Root_dir; if (has_root_directory) { // Remove any root directory and relative path if (_M_type() != _Type::_Root_name) { if (!_M_cmpts.empty() && _M_cmpts.front()._M_type() == _Type::_Root_name) lhs = _M_cmpts.front()._M_pathname; else lhs = {}; } } else if (has_filename() || (!has_root_directory && is_absolute)) add_sep = true; basic_string_view rhs = s; // Omit any root-name from the generic format pathname: if (has_root_name) rhs.remove_prefix(root_path.first.str.length()); // Construct new path and swap (strong exception-safety guarantee). string_type tmp; tmp.reserve(lhs.size() + (int)add_sep + rhs.size()); tmp = lhs; if (add_sep) tmp += preferred_separator; tmp += rhs; path newp = std::move(tmp); swap(newp); #else bool is_absolute = root_path.first.type == _Type::_Root_dir || root_path.second.type == _Type::_Root_dir; if (is_absolute || this->empty()) { operator=(s); return; } const auto orig_pathlen = _M_pathname.length(); const auto orig_size = _M_cmpts.size(); const auto orig_type = _M_type(); basic_string_view sep; if (has_filename()) sep = { &preferred_separator, 1 }; // need to add a separator #if SLASHSLASH_IS_ROOTNAME else if (_M_type() == _Type::_Root_name) // root-name with no root-dir sep = { &preferred_separator, 1 }; // need to add a separator #endif else if (s.empty()) return; // nothing to do // Copy the input into _M_pathname: _M_pathname += s; _M_pathname.insert(orig_pathlen, sep); // Update s to refer to the new copy (this ensures s is not a dangling // reference to deallocated characters, in the case where it was referring // into _M_pathname or a member of _M_cmpts). s = _M_pathname; const auto orig_pathname = s.substr(0, orig_pathlen); s.remove_prefix(orig_pathlen + sep.length()); parser.input = s; // reset parser to use updated string view const auto basepos = orig_pathname.length() + sep.length(); parser.origin = basepos; std::array<_Parser::cmpt, 64> buf; auto next = buf.begin(); int capacity = 0; if (_M_type() == _Type::_Multi) capacity += _M_cmpts.size(); else if (!empty()) capacity += 1; auto cmpt = parser.next(); if (cmpt.valid()) { do { *next++ = cmpt; cmpt = parser.next(); } while (cmpt.valid() && next != buf.end()); capacity += next - buf.begin(); if (cmpt.valid()) // filled buffer before parsing whole input { ++capacity; _Parser parser2(parser); while (parser2.next().valid()) ++capacity; } } else if (!sep.empty()) ++capacity; #if SLASHSLASH_IS_ROOTNAME if (orig_type == _Type::_Root_name) ++capacity; // Need to insert root-directory after root-name #endif __try { _M_cmpts.type(_Type::_Multi); _M_cmpts.reserve(capacity); _Cmpt* output = _M_cmpts._M_impl->end(); if (orig_type == _Type::_Multi) { // Remove empty final component if (_M_cmpts._M_impl->back().empty()) { _M_cmpts.pop_back(); --output; } } else if (orig_pathlen != 0) { // Create single component from original path ::new(output++) _Cmpt(orig_pathname, orig_type, 0); ++_M_cmpts._M_impl->_M_size; #if SLASHSLASH_IS_ROOTNAME if (!sep.empty() && orig_type == _Type::_Root_name) { ::new(output++) _Cmpt(sep, _Type::_Root_dir, orig_pathlen + sep.length()); ++_M_cmpts._M_impl->_M_size; } #endif } if (next != buf.begin()) { for (auto it = buf.begin(); it != next; ++it) { auto c = *it; ::new(output++) _Cmpt(c.str, c.type, parser.offset(c)); ++_M_cmpts._M_impl->_M_size; } while (cmpt.valid()) { ::new(output++) _Cmpt(cmpt.str, cmpt.type, parser.offset(cmpt)); ++_M_cmpts._M_impl->_M_size; cmpt = parser.next(); } } else if (!sep.empty()) { // Empty filename at the end: ::new(output) _Cmpt({}, _Type::_Filename, basepos); ++_M_cmpts._M_impl->_M_size; } } __catch (...) { _M_pathname.resize(orig_pathlen); if (orig_type == _Type::_Multi) _M_cmpts._M_erase_from(_M_cmpts.begin() + orig_size); else _M_cmpts.clear(); _M_cmpts.type(orig_type); __throw_exception_again; } #endif } // [fs.path.concat] path& path::operator+=(const path& p) { if (p.empty()) return *this; if (this->empty()) { operator=(p); return *this; } #if _GLIBCXX_FILESYSTEM_IS_WINDOWS if (_M_type() == _Type::_Root_name || (_M_type() == _Type::_Filename && _M_pathname.size() == 1)) { // Handle path("C") += path(":") and path("C:") += path("/x") // FIXME: do this more efficiently *this = path(_M_pathname + p._M_pathname); return *this; } #endif #if SLASHSLASH_IS_ROOTNAME if (_M_type() == _Type::_Root_dir) { // Handle path("/") += path("/x") and path("//") += path("x") // FIXME: do this more efficiently *this = path(_M_pathname + p._M_pathname); return *this; } #endif const auto orig_pathlen = _M_pathname.length(); const auto orig_type = _M_type(); const auto orig_size = _M_cmpts.size(); int orig_filenamelen = -1; basic_string_view extra; // Ensure that '_M_pathname += p._M_pathname' won't throw: _M_pathname.reserve(orig_pathlen + p._M_pathname.length()); _Cmpt c; _Cmpt* it = nullptr; _Cmpt* last = nullptr; if (p._M_type() == _Type::_Multi) { it = p._M_cmpts._M_impl->begin(); last = p._M_cmpts._M_impl->end(); } else { c = _Cmpt(p._M_pathname, p._M_type(), 0); it = &c; last = it + 1; } if (it->_M_type() == _Type::_Filename) { // See if there's a filename or root-name at the end of the original path // that we can add to. if (_M_type() == _Type::_Filename #if SLASHSLASH_IS_ROOTNAME || _M_type() == _Type::_Root_name #endif ) { if (p._M_type() == _Type::_Filename) { // Simplest case where we just add the whole of p to the // original path. _M_pathname += p._M_pathname; return *this; } // Only the first component of s should be appended, do so below: extra = it->_M_pathname; ++it; } else if (_M_type() == _Type::_Multi && _M_cmpts.back()._M_type() == _Type::_Filename) { auto& back = _M_cmpts.back(); if (p._M_type() == _Type::_Filename) { basic_string_view s = p._M_pathname; back._M_pathname += s; _M_pathname += s; return *this; } orig_filenamelen = back._M_pathname.length(); back._M_pathname += it->_M_pathname; extra = it->_M_pathname; ++it; } } else if (is_dir_sep(_M_pathname.back()) && _M_type() == _Type::_Multi && _M_cmpts.back()._M_type() == _Type::_Filename) orig_filenamelen = 0; // current path has empty filename at end int capacity = 0; if (_M_type() == _Type::_Multi) capacity += _M_cmpts.size(); else capacity += 1; if (p._M_type() == _Type::_Multi) capacity += p._M_cmpts.size(); else capacity += 1; __try { _M_cmpts.type(_Type::_Multi); _M_cmpts.reserve(capacity); _Cmpt* output = _M_cmpts._M_impl->end(); if (orig_type != _Type::_Multi) { // Create single component from original path auto ptr = ::new(output++) _Cmpt({}, orig_type, 0); ++_M_cmpts._M_impl->_M_size; ptr->_M_pathname.reserve(_M_pathname.length() + extra.length()); ptr->_M_pathname = _M_pathname; ptr->_M_pathname += extra; #if SLASHSLASH_IS_ROOTNAME if (orig_type == _Type::_Root_name) { basic_string_view s(p._M_pathname); ::new(output++) _Cmpt(s.substr(extra.length(), 1), _Type::_Root_dir, orig_pathlen + extra.length()); ++_M_cmpts._M_impl->_M_size; } #endif } else if (orig_filenamelen == 0 && it != last) { // Remove empty filename at end of original path. _M_cmpts.pop_back(); --output; } if (it != last && it->_M_type() == _Type::_Root_name) { basic_string_view s = it->_M_pathname; auto pos = orig_pathlen; #if SLASHSLASH_IS_ROOTNAME s.remove_prefix(2); pos += 2; #endif ::new(output++) _Cmpt(s, _Type::_Filename, pos); ++_M_cmpts._M_impl->_M_size; ++it; } if (it != last && it->_M_type() == _Type::_Root_dir) ++it; while (it != last) { auto pos = it->_M_pos + orig_pathlen; ::new(output++) _Cmpt(it->_M_pathname, _Type::_Filename, pos); ++_M_cmpts._M_impl->_M_size; ++it; } _M_pathname += p._M_pathname; if (is_dir_sep(_M_pathname.back())) { ::new(output++) _Cmpt({}, _Type::_Filename, _M_pathname.length()); ++_M_cmpts._M_impl->_M_size; } } __catch (...) { _M_pathname.resize(orig_pathlen); if (orig_type == _Type::_Multi) { if (_M_cmpts.size() > orig_size) _M_cmpts._M_erase_from(_M_cmpts.begin() + orig_size); if (orig_filenamelen != -1) { if (_M_cmpts.size() == orig_size) { auto& back = _M_cmpts.back(); back._M_pathname.resize(orig_filenamelen); if (orig_filenamelen == 0) back._M_pos = orig_pathlen; } else { auto output = _M_cmpts._M_impl->end(); ::new(output) _Cmpt({}, _Type::_Filename, orig_pathlen); ++_M_cmpts._M_impl->_M_size; } } } else _M_cmpts.clear(); _M_cmpts.type(orig_type); __throw_exception_again; } return *this; } // [fs.path.concat] void path::_M_concat(basic_string_view s) { if (s.empty()) return; if (this->empty()) { operator=(s); return; } #if _GLIBCXX_FILESYSTEM_IS_WINDOWS if (_M_type() == _Type::_Root_name || (_M_type() == _Type::_Filename && _M_pathname.size() == 1)) { // Handle path("C") += ":" and path("C:") += "/x" // FIXME: do this more efficiently *this = path(_M_pathname + string_type(s)); return; } #endif #if SLASHSLASH_IS_ROOTNAME if (_M_type() == _Type::_Root_dir) { // Handle path("/") += "/x" and path("//") += "x" // FIXME: do this more efficiently *this = path(_M_pathname + string_type(s)); return; } #endif const auto orig_pathlen = _M_pathname.length(); const auto orig_type = _M_type(); const auto orig_size = _M_cmpts.size(); int orig_filenamelen = -1; basic_string_view extra; // Copy the input into _M_pathname: _M_pathname += s; // Update s to refer to the new copy (this ensures s is not a dangling // reference to deallocated characters, in the case where it was referring // into _M_pathname or a member of _M_cmpts). s = _M_pathname; const auto orig_pathname = s.substr(0, orig_pathlen); s.remove_prefix(orig_pathlen); _Parser parser(s, orig_pathlen); auto cmpt = parser.next(); if (cmpt.str.data() == s.data()) { // See if there's a filename or root-name at the end of the original path // that we can add to. if (_M_type() == _Type::_Filename #if SLASHSLASH_IS_ROOTNAME || _M_type() == _Type::_Root_name #endif ) { if (cmpt.str.length() == s.length()) { // Simplest case where we just need to add the whole of s // to the original path, which was already done above. return; } // Only the first component of s should be appended, do so below: extra = cmpt.str; cmpt = {}; // so we don't process it again } else if (_M_type() == _Type::_Multi && _M_cmpts.back()._M_type() == _Type::_Filename) { auto& back = _M_cmpts.back(); if (cmpt.str.length() == s.length()) { back._M_pathname += s; return; } orig_filenamelen = back._M_pathname.length(); back._M_pathname += cmpt.str; extra = cmpt.str; cmpt = {}; } } else if (is_dir_sep(orig_pathname.back()) && _M_type() == _Type::_Multi && _M_cmpts.back()._M_type() == _Type::_Filename) orig_filenamelen = 0; // original path had empty filename at end std::array<_Parser::cmpt, 64> buf; auto next = buf.begin(); if (cmpt.valid()) *next++ = cmpt; cmpt = parser.next(); while (cmpt.valid() && next != buf.end()) { *next++ = cmpt; cmpt = parser.next(); } int capacity = 0; if (_M_type() == _Type::_Multi) capacity += _M_cmpts.size(); else capacity += 1; capacity += next - buf.begin(); if (cmpt.valid()) // filled buffer before parsing whole input { ++capacity; _Parser parser2(parser); while (parser2.next().valid()) ++capacity; } #if SLASHSLASH_IS_ROOTNAME if (orig_type == _Type::_Root_name) ++capacity; // Need to insert root-directory after root-name #endif __try { _M_cmpts.type(_Type::_Multi); _M_cmpts.reserve(capacity); _Cmpt* output = _M_cmpts._M_impl->end(); auto it = buf.begin(); if (orig_type != _Type::_Multi) { // Create single component from original path auto p = ::new(output++) _Cmpt({}, orig_type, 0); ++_M_cmpts._M_impl->_M_size; p->_M_pathname.reserve(orig_pathname.length() + extra.length()); p->_M_pathname = orig_pathname; p->_M_pathname += extra; #if SLASHSLASH_IS_ROOTNAME if (orig_type == _Type::_Root_name) { ::new(output++) _Cmpt(s.substr(extra.length(), 1), _Type::_Root_dir, orig_pathlen + extra.length()); ++_M_cmpts._M_impl->_M_size; } #endif } else if (orig_filenamelen == 0 && extra.empty()) { // Replace empty filename at end of original path. std::prev(output)->_M_pathname = it->str; std::prev(output)->_M_pos = parser.offset(*it); ++it; } while (it != next) { ::new(output++) _Cmpt(it->str, _Type::_Filename, parser.offset(*it)); ++_M_cmpts._M_impl->_M_size; ++it; } if (next == buf.end()) { while (cmpt.valid()) { auto pos = parser.offset(cmpt); ::new(output++) _Cmpt(cmpt.str, _Type::_Filename, pos); ++_M_cmpts._M_impl->_M_size; cmpt = parser.next(); } } } __catch (...) { _M_pathname.resize(orig_pathlen); if (orig_type == _Type::_Multi) { _M_cmpts._M_erase_from(_M_cmpts.begin() + orig_size); if (orig_filenamelen != -1) { auto& back = _M_cmpts.back(); back._M_pathname.resize(orig_filenamelen); if (orig_filenamelen == 0) back._M_pos = orig_pathlen; } } else _M_cmpts.clear(); _M_cmpts.type(orig_type); __throw_exception_again; } } path& path::remove_filename() { if (_M_type() == _Type::_Multi) { if (!_M_cmpts.empty()) { auto cmpt = std::prev(_M_cmpts.end()); if (cmpt->_M_type() == _Type::_Filename && !cmpt->empty()) { _M_pathname.erase(cmpt->_M_pos); auto prev = std::prev(cmpt); if (prev->_M_type() == _Type::_Root_dir || prev->_M_type() == _Type::_Root_name) { _M_cmpts.pop_back(); if (_M_cmpts.size() == 1) { _M_cmpts.type(_M_cmpts.front()._M_type()); _M_cmpts.clear(); } } else cmpt->clear(); } } } else if (_M_type() == _Type::_Filename) 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(); // Any existing extension() is removed if (ext.first && ext.second != string_type::npos) { if (ext.first == &_M_pathname) _M_pathname.erase(ext.second); else { auto& back = _M_cmpts.back(); __glibcxx_assert( ext.first == &back._M_pathname ); back._M_pathname.erase(ext.second); _M_pathname.erase(back._M_pos + ext.second); } } // If replacement is not empty and does not begin with a dot character, // a dot character is appended if (!replacement.empty() && replacement.native()[0] != dot) operator+=("."); operator+=(replacement); return *this; } int path::compare(const path& p) const noexcept { if (_M_pathname == p._M_pathname) return 0; basic_string_view lroot, rroot; if (_M_type() == _Type::_Root_name) lroot = _M_pathname; else if (_M_type() == _Type::_Multi && _M_cmpts.front()._M_type() == _Type::_Root_name) lroot = _M_cmpts.front()._M_pathname; if (p._M_type() == _Type::_Root_name) rroot = p._M_pathname; else if (p._M_type() == _Type::_Multi && p._M_cmpts.front()._M_type() == _Type::_Root_name) rroot = p._M_cmpts.front()._M_pathname; if (int rootNameComparison = lroot.compare(rroot)) return rootNameComparison; if (!this->has_root_directory() && p.has_root_directory()) return -1; else if (this->has_root_directory() && !p.has_root_directory()) return +1; using Iterator = const _Cmpt*; Iterator begin1, end1, begin2, end2; if (_M_type() == _Type::_Multi) { begin1 = _M_cmpts.begin(); end1 = _M_cmpts.end(); // Find start of this->relative_path() while (begin1 != end1 && begin1->_M_type() != _Type::_Filename) ++begin1; } else begin1 = end1 = nullptr; if (p._M_type() == _Type::_Multi) { begin2 = p._M_cmpts.begin(); end2 = p._M_cmpts.end(); // Find start of p.relative_path() while (begin2 != end2 && begin2->_M_type() != _Type::_Filename) ++begin2; } else begin2 = end2 = nullptr; if (_M_type() == _Type::_Filename) { if (p._M_type() == _Type::_Filename) return native().compare(p.native()); else if (begin2 != end2) { if (int ret = native().compare(begin2->native())) return ret; else return ++begin2 == end2 ? 0 : -1; } else return +1; } else if (p._M_type() == _Type::_Filename) { if (begin1 != end1) { if (int ret = begin1->native().compare(p.native())) return ret; else return ++begin1 == end1 ? 0 : +1; } else return -1; } int count = 1; while (begin1 != end1 && begin2 != end2) { if (int i = begin1->native().compare(begin2->native())) return i; ++begin1; ++begin2; ++count; } if (begin1 == end1) { if (begin2 == end2) return 0; return -count; } return count; } int path::compare(basic_string_view s) const noexcept { if (_M_pathname == s) return 0; _Parser parser(s); basic_string_view lroot, rroot; if (_M_type() == _Type::_Root_name) lroot = _M_pathname; else if (_M_type() == _Type::_Multi && _M_cmpts.front()._M_type() == _Type::_Root_name) lroot = _M_cmpts.front()._M_pathname; auto root_path = parser.root_path(); if (root_path.first.type == _Type::_Root_name) rroot = root_path.first.str; if (int rootNameComparison = lroot.compare(rroot)) return rootNameComparison; const bool has_root_dir = root_path.first.type == _Type::_Root_dir || root_path.second.type == _Type::_Root_dir; if (!this->has_root_directory() && has_root_dir) return -1; else if (this->has_root_directory() && !has_root_dir) return +1; using Iterator = const _Cmpt*; Iterator begin1, end1; if (_M_type() == _Type::_Filename) { auto cmpt = parser.next(); if (cmpt.valid()) { if (int ret = this->native().compare(cmpt.str)) return ret; return parser.next().valid() ? -1 : 0; } else return +1; } else if (_M_type() == _Type::_Multi) { begin1 = _M_cmpts.begin(); end1 = _M_cmpts.end(); while (begin1 != end1 && begin1->_M_type() != _Type::_Filename) ++begin1; } else begin1 = end1 = nullptr; int count = 1; auto cmpt = parser.next(); while (begin1 != end1 && cmpt.valid()) { if (int i = begin1->native().compare(cmpt.str)) return i; ++begin1; cmpt = parser.next(); ++count; } if (begin1 == end1) { if (!cmpt.valid()) return 0; return -count; } return +count; } 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._M_cmpts.type(_Type::_Root_dir); __ret._M_pathname.assign(1, preferred_separator); } 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) __ret = *this; else if (_M_type() == _Type::_Root_dir) { __ret._M_pathname.assign(1, preferred_separator); __ret._M_cmpts.type(_Type::_Root_dir); } 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 /= *__it; } 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 (!has_relative_path()) __ret = *this; else if (_M_cmpts.size() >= 2) { const auto parent = std::prev(_M_cmpts.end(), 2); const auto len = parent->_M_pos + parent->_M_pathname.length(); __ret.assign(_M_pathname.substr(0, len)); } return __ret; } bool path::has_root_name() const noexcept { 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 noexcept { 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 noexcept { 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 noexcept { if (_M_type() == _Type::_Filename && !_M_pathname.empty()) 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() && !__it->_M_pathname.empty()) return true; } return false; } bool path::has_parent_path() const noexcept { if (!has_relative_path()) return !empty(); return _M_cmpts.size() >= 2; } bool path::has_filename() const noexcept { if (empty()) return false; if (_M_type() == _Type::_Filename) return !_M_pathname.empty(); if (_M_type() == _Type::_Multi) { if (_M_pathname.back() == preferred_separator) return false; return _M_cmpts.back().has_filename(); } return false; } namespace { inline bool is_dot(fs::path::value_type c) { return c == dot; } inline bool is_dot(const fs::path& path) { const auto& filename = path.native(); return filename.size() == 1 && is_dot(filename[0]); } inline bool is_dotdot(const fs::path& path) { const auto& filename = path.native(); return filename.size() == 2 && is_dot(filename[0]) && is_dot(filename[1]); } } // namespace path path::lexically_normal() const { /* C++17 [fs.path.generic] p6 - If the path is empty, stop. - Replace each slash character in the root-name with a preferred-separator. - Replace each directory-separator with a preferred-separator. - Remove each dot filename and any immediately following directory-separator. - As long as any appear, remove a non-dot-dot filename immediately followed by a directory-separator and a dot-dot filename, along with any immediately following directory-separator. - If there is a root-directory, remove all dot-dot filenames and any directory-separators immediately following them. - If the last filename is dot-dot, remove any trailing directory-separator. - If the path is empty, add a dot. */ path ret; // If the path is empty, stop. if (empty()) return ret; for (auto& p : *this) { #ifdef _GLIBCXX_FILESYSTEM_IS_WINDOWS // Replace each slash character in the root-name if (p._M_type() == _Type::_Root_name || p._M_type() == _Type::_Root_dir) { string_type s = p.native(); std::replace(s.begin(), s.end(), L'/', L'\\'); ret /= s; continue; } #endif if (is_dotdot(p)) { if (ret.has_filename()) { // remove a non-dot-dot filename immediately followed by /.. if (!is_dotdot(ret.filename())) ret.remove_filename(); else ret /= p; } else if (!ret.has_relative_path()) { // remove a dot-dot filename immediately after root-directory if (!ret.has_root_directory()) ret /= p; } else { // Got a path with a relative path (i.e. at least one non-root // element) and no filename at the end (i.e. empty last element), // so must have a trailing slash. See what is before it. auto elem = ret._M_cmpts.end() - 2; if (elem->has_filename() && !is_dotdot(*elem)) { // Remove the filename before the trailing slash // (equiv. to ret = ret.parent_path().remove_filename()) if (elem == ret._M_cmpts.begin()) ret.clear(); else { ret._M_pathname.erase(elem->_M_pos); // Remove empty filename at the end: ret._M_cmpts.pop_back(); // If we still have a trailing non-root dir separator // then leave an empty filename at the end: if (std::prev(elem)->_M_type() == _Type::_Filename) elem->clear(); else // remove the component completely: ret._M_cmpts.pop_back(); } } else // Append the ".." to something ending in "../" which happens // when normalising paths like ".././.." and "../a/../.." ret /= p; } } else if (is_dot(p)) ret /= path(); #if SLASHSLASH_IS_ROOTNAME else if (p._M_type() == _Type::_Root_dir) ret += '/'; // using operator/=('/') would replace whole of ret #endif else ret /= p; } if (ret._M_cmpts.size() >= 2) { auto back = std::prev(ret.end()); // If the last filename is dot-dot, ... if (back->empty() && is_dotdot(*std::prev(back))) // ... remove any trailing directory-separator. ret = ret.parent_path(); } // If the path is empty, add a dot. else if (ret.empty()) ret = "."; return ret; } path path::lexically_relative(const path& base) const { path ret; if (root_name() != base.root_name()) return ret; if (is_absolute() != base.is_absolute()) return ret; if (!has_root_directory() && base.has_root_directory()) return ret; auto [a, b] = std::mismatch(begin(), end(), base.begin(), base.end()); #ifdef _GLIBCXX_FILESYSTEM_IS_WINDOWS // _GLIBCXX_RESOLVE_LIB_DEFECTS // 3070. path::lexically_relative causes surprising results if a filename // can also be a root-name if (!empty()) for (auto& p : _M_cmpts) if (p._M_type() == _Type::_Filename && is_disk_designator(p.native())) return ret; if (!base.empty()) for (auto i = b, end = base.end(); i != end; ++i) if (i->_M_type() == _Type::_Filename && is_disk_designator(i->native())) return ret; #endif if (a == end() && b == base.end()) ret = "."; else { int n = 0; for (; b != base.end(); ++b) { const path& p = *b; if (is_dotdot(p)) --n; else if (!p.empty() && !is_dot(p)) ++n; } if (n == 0 && (a == end() || a->empty())) ret = "."; else if (n >= 0) { const path dotdot(".."); while (n--) ret /= dotdot; for (; a != end(); ++a) ret /= *a; } } return ret; } path path::lexically_proximate(const path& base) const { path rel = lexically_relative(base); if (rel.empty()) rel = *this; return rel; } std::pair path::_M_find_extension() const noexcept { const string_type* s = nullptr; if (_M_type() == _Type::_Filename) s = &_M_pathname; else if (_M_type() == _Type::_Multi && !_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) return { s, string_type::npos }; if (const auto pos = s->rfind(dot)) return { s , pos }; return { s, string_type::npos }; } } return {}; } void path::_M_split_cmpts() { _M_cmpts.clear(); if (_M_pathname.empty()) { _M_cmpts.type(_Type::_Filename); return; } _Parser parser(_M_pathname); std::array<_Parser::cmpt, 64> buf; auto next = buf.begin(); // look for root name or root directory auto root_path = parser.root_path(); if (root_path.first.valid()) { *next++ = root_path.first; if (root_path.second.valid()) *next++ = root_path.second; } auto cmpt = parser.next(); while (cmpt.valid()) { do { *next++ = cmpt; cmpt = parser.next(); } while (cmpt.valid() && next != buf.end()); if (next == buf.end()) { _M_cmpts.type(_Type::_Multi); _M_cmpts.reserve(_M_cmpts.size() + buf.size()); auto output = _M_cmpts._M_impl->end(); for (const auto& c : buf) { ::new(output++) _Cmpt(c.str, c.type, parser.offset(c)); ++_M_cmpts._M_impl->_M_size; } next = buf.begin(); } } if (auto n = next - buf.begin()) { if (n == 1 && _M_cmpts.empty()) { _M_cmpts.type(buf.front().type); return; } _M_cmpts.type(_Type::_Multi); _M_cmpts.reserve(_M_cmpts.size() + n, true); auto output = _M_cmpts._M_impl->end(); for (int i = 0; i < n; ++i) { const auto& c = buf[i]; ::new(output++) _Cmpt(c.str, c.type, parser.offset(c)); ++_M_cmpts._M_impl->_M_size; } } } path::string_type path::_S_convert_loc(const char* __first, const char* __last, const std::locale& __loc) { #if _GLIBCXX_USE_WCHAR_T auto& __cvt = std::use_facet>(__loc); basic_string __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))); return _S_convert(std::move(__ws)); #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()(x.native()) + 0x9e3779b9 + (seed<<6) + (seed>>2); } return seed; } struct fs::filesystem_error::_Impl { _Impl(string_view what_arg, const path& p1, const path& p2) : path1(p1), path2(p2), what(make_what(what_arg, &p1, &p2)) { } _Impl(string_view what_arg, const path& p1) : path1(p1), path2(), what(make_what(what_arg, &p1, nullptr)) { } _Impl(string_view what_arg) : what(make_what(what_arg, nullptr, nullptr)) { } static std::string make_what(string_view s, const path* p1, const path* p2) { const std::string pstr1 = p1 ? p1->u8string() : std::string{}; const std::string pstr2 = p2 ? p2->u8string() : std::string{}; const size_t len = 18 + s.length() + (pstr1.length() ? pstr1.length() + 3 : 0) + (pstr2.length() ? pstr2.length() + 3 : 0); std::string w; w.reserve(len); w = "filesystem error: "; w += s; if (p1) { w += " ["; w += pstr1; w += ']'; if (p2) { w += " ["; w += pstr2; w += ']'; } } return w; } path path1; path path2; std::string what; }; template class std::__shared_ptr; fs::filesystem_error:: filesystem_error(const string& what_arg, error_code ec) : system_error(ec, what_arg), _M_impl(std::__make_shared<_Impl>(system_error::what())) { } fs::filesystem_error:: filesystem_error(const string& what_arg, const path& p1, error_code ec) : system_error(ec, what_arg), _M_impl(std::__make_shared<_Impl>(system_error::what(), p1)) { } fs::filesystem_error:: filesystem_error(const string& what_arg, const path& p1, const path& p2, error_code ec) : system_error(ec, what_arg), _M_impl(std::__make_shared<_Impl>(system_error::what(), p1, p2)) { } fs::filesystem_error::~filesystem_error() = default; const fs::path& fs::filesystem_error::path1() const noexcept { return _M_impl->path1; } const fs::path& fs::filesystem_error::path2() const noexcept { return _M_impl->path2; } const char* fs::filesystem_error::what() const noexcept { return _M_impl->what.c_str(); }