Calling the non-const data() member on a COW string makes it "leaked", possibly resulting in reallocating the string to ensure a unique owner. The path::_M_split_cmpts() member parses its _M_pathname string using string_view objects and then calls _M_pathname.data() to find the offset of each string_view from the start of the string. However because _M_pathname is non-const that will cause a COW string to reallocate if it happens to be shared with another string object. This results in the offsets calculated for each component being wrong (i.e. undefined) because the string views no longer refer to substrings of the _M_pathname member. The fix is to use the parse.offset(c) member which gets the offset safely. The bug only happens for the path(string_type&&) constructor and only for COW strings. When constructed from an lvalue string the string's contents are copied rather than just incrementing the refcount, so there's no reallocation when calling the non-const data() member. The testsuite changes check the lvalue case anyway, because we should probably change the deep copying to just be a refcount increment (by adding a path(const string_type&) constructor or an overload for __effective_range(const string_type&), for COW strings only). libstdc++-v3/ChangeLog: PR libstdc++/99805 * src/c++17/fs_path.cc (path::_M_split_cmpts): Do not call non-const member on _M_pathname, to avoid copy-on-write. * testsuite/27_io/filesystem/path/decompose/parent_path.cc: Check construction from strings that might be shared.
2054 lines
50 KiB
C++
2054 lines
50 KiB
C++
// Class filesystem::path -*- C++ -*-
|
|
|
|
// Copyright (C) 2014-2021 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
|
|
|
|
#ifdef __CYGWIN__
|
|
// Interpret "//x" as a root-name, not root-dir + filename
|
|
# define SLASHSLASH_IS_ROOTNAME 1
|
|
#endif
|
|
|
|
#include <filesystem>
|
|
#include <algorithm>
|
|
#include <bits/stl_uninitialized.h>
|
|
|
|
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<value_type>;
|
|
|
|
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<cmpt, cmpt> root_path() noexcept
|
|
{
|
|
pos = 0;
|
|
pair<cmpt, cmpt> 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<value_type*>(this + 1); }
|
|
iterator end() { return begin() + size(); }
|
|
|
|
const_iterator begin() const
|
|
{ return reinterpret_cast<const value_type*>(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<uintptr_t>(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<uintptr_t>(_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<value_type> __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<value_type> __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<value_type>;
|
|
|
|
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<value_type> 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<value_type> 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<value_type> 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<value_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 (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<value_type> 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<value_type> 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<value_type> 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<value_type> 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<value_type> 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<value_type> 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<value_type> 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<value_type> s) const noexcept
|
|
{
|
|
if (_M_pathname == s)
|
|
return 0;
|
|
|
|
_Parser parser(s);
|
|
|
|
basic_string_view<value_type> 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<const path::string_type*, std::size_t>
|
|
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;
|
|
}
|
|
if (_M_pathname.length() == 1 && _M_pathname[0] == preferred_separator)
|
|
{
|
|
_M_cmpts.type(_Type::_Root_dir);
|
|
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<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)));
|
|
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<path::string_type>()(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<const fs::filesystem_error::_Impl>;
|
|
|
|
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(); }
|