mirror of git://gcc.gnu.org/git/gcc.git
2113 lines
51 KiB
C++
2113 lines
51 KiB
C++
// Class filesystem::path -*- C++ -*-
|
|
|
|
// Copyright (C) 2014-2025 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 <array>
|
|
#include <new>
|
|
#include <bits/stl_uninitialized.h>
|
|
#include <ext/numeric_traits.h> // __gnu_cxx::__int_traits
|
|
|
|
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(); }
|
|
};
|
|
|
|
inline
|
|
path::path(basic_string_view<value_type> __str, _Type __type)
|
|
: _M_pathname(__str)
|
|
{
|
|
__glibcxx_assert(__type != _Type::_Multi);
|
|
_M_cmpts.type(__type);
|
|
}
|
|
|
|
inline
|
|
path::_Cmpt::_Cmpt(basic_string_view<value_type> __s, _Type __t, size_t __pos)
|
|
: path(__s, __t), _M_pos(__pos)
|
|
{ }
|
|
|
|
struct path::_List::_Impl
|
|
{
|
|
using value_type = _Cmpt;
|
|
|
|
_Impl(int cap) : _M_size(0), _M_capacity(cap) { }
|
|
|
|
~_Impl() { clear(); }
|
|
|
|
// Align the first member like the value_type so that we can store one or
|
|
// more objects of that type immediately after the memory occupied by *this.
|
|
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();
|
|
// *this already has n elements so don't need to check if n overflows:
|
|
auto newptr = create_unchecked(n);
|
|
std::uninitialized_copy_n(begin(), n, newptr->begin());
|
|
newptr->_M_size = n;
|
|
return newptr;
|
|
}
|
|
|
|
// We use the two least significant bits to store a _Type value so
|
|
// require memory aligned to at least 4 bytes:
|
|
static_assert(__STDCPP_DEFAULT_NEW_ALIGNMENT__ >= 4);
|
|
// Require memory suitably aligned for an _Impl and its value types:
|
|
static_assert(__STDCPP_DEFAULT_NEW_ALIGNMENT__ >= alignof(value_type));
|
|
|
|
// 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);
|
|
}
|
|
|
|
// Create a new _Impl with capacity for n components.
|
|
static unique_ptr<_Impl, _Impl_deleter>
|
|
create(int n)
|
|
{
|
|
using __gnu_cxx::__int_traits;
|
|
// Nobody should need paths with this many components.
|
|
if (n >= __int_traits<int>::__max / 4)
|
|
std::__throw_bad_alloc();
|
|
|
|
if constexpr (__int_traits<int>::__max >= __int_traits<size_t>::__max)
|
|
{
|
|
// Check that the calculation in create_unchecked(n) won't overflow.
|
|
size_t bytes;
|
|
if (__builtin_mul_overflow(n, sizeof(value_type), &bytes)
|
|
|| __builtin_add_overflow(sizeof(_Impl), bytes, &bytes))
|
|
std::__throw_bad_alloc();
|
|
}
|
|
// Otherwise, it can't overflow, even for 20-bit size_t on msp430.
|
|
|
|
return create_unchecked(n);
|
|
}
|
|
|
|
// pre: no overflow in Si + n * Sv
|
|
static unique_ptr<_Impl, _Impl_deleter>
|
|
create_unchecked(int n)
|
|
{
|
|
void* p = ::operator new(sizeof(_Impl) + n * sizeof(value_type));
|
|
return std::unique_ptr<_Impl, _Impl_deleter>(::new(p) _Impl{n});
|
|
}
|
|
};
|
|
|
|
// Destroy and deallocate an _Impl.
|
|
void
|
|
path::_List::_Impl_deleter::operator()(_Impl* p) const noexcept
|
|
{
|
|
p = _Impl::notype(p);
|
|
if (p)
|
|
{
|
|
const auto n = p->_M_capacity;
|
|
p->~_Impl();
|
|
::operator delete(p, sizeof(_Impl) + n * sizeof(_Impl::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)
|
|
{
|
|
const int nextcap = curcap + curcap / 2;
|
|
if (newcap < nextcap)
|
|
newcap = nextcap;
|
|
}
|
|
|
|
auto newptr = _Impl::create(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
|
|
|
|
_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;
|
|
}
|
|
|
|
// Handle p += p which would otherwise access dangling pointers after
|
|
// reallocating _M_cmpts and _M_pathname.
|
|
if (&p == this) [[unlikely]]
|
|
return *this += p.native();
|
|
// Handle p += *i where i is in [p.begin(),p.end()), for the same reason.
|
|
if (_M_type() == _Type::_Multi && p._M_type() != _Type::_Multi)
|
|
for (const path& cmpt : *this)
|
|
if (&cmpt == &p) [[unlikely]]
|
|
return *this += p.native();
|
|
|
|
#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;
|
|
}
|
|
|
|
_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,
|
|
[[maybe_unused]] const std::locale& __loc)
|
|
{
|
|
#if _GLIBCXX_USE_WCHAR_T
|
|
auto& __cvt = std::use_facet<codecvt<wchar_t, char, mbstate_t>>(__loc);
|
|
basic_string<wchar_t> __ws;
|
|
if (!__str_codecvt_in_all(__first, __last, __ws, __cvt))
|
|
_GLIBCXX_THROW_OR_ABORT(filesystem_error(
|
|
"Cannot convert character sequence",
|
|
std::make_error_code(errc::illegal_byte_sequence)));
|
|
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(); }
|