8sa1-gcc/libstdc++-v3/src/c++17/fs_path.cc
Jonathan Wakely e06d3f5dd7 libstdc++: Fix filesystem::path construction from COW string [PR 99805]
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.
2021-04-07 16:39:24 +01:00

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(); }