1 // Class filesystem::path -*- C++ -*-
3 // Copyright (C) 2014-2018 Free Software Foundation, Inc.
5 // This file is part of the GNU ISO C++ Library. This library is free
6 // software; you can redistribute it and/or modify it under the
7 // terms of the GNU General Public License as published by the
8 // Free Software Foundation; either version 3, or (at your option)
11 // This library is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 // GNU General Public License for more details.
16 // Under Section 7 of GPL version 3, you are granted additional
17 // permissions described in the GCC Runtime Library Exception, version
18 // 3.1, as published by the Free Software Foundation.
20 // You should have received a copy of the GNU General Public License and
21 // a copy of the GCC Runtime Library Exception along with this program;
22 // see the files COPYING3 and COPYING.RUNTIME respectively. If not, see
23 // <http://www.gnu.org/licenses/>.
25 #ifndef _GLIBCXX_USE_CXX11_ABI
26 # define _GLIBCXX_USE_CXX11_ABI 1
30 #ifdef _GLIBCXX_FILESYSTEM_IS_WINDOWS
34 namespace fs
= std::filesystem
;
37 constexpr path::value_type
path::preferred_separator
;
39 #ifdef _GLIBCXX_FILESYSTEM_IS_WINDOWS
41 path::operator/=(const path
& __p
)
44 || (__p
.has_root_name() && __p
.root_name() != root_name()))
45 return operator=(__p
);
47 basic_string_view
<value_type
> __lhs
= _M_pathname
;
48 bool __add_sep
= false;
50 if (__p
.has_root_directory())
52 // Remove any root directory and relative path
53 if (_M_type
!= _Type::_Root_name
)
56 && _M_cmpts
.front()._M_type
== _Type::_Root_name
)
57 __lhs
= _M_cmpts
.front()._M_pathname
;
62 else if (has_filename() || (!has_root_directory() && is_absolute()))
65 basic_string_view
<value_type
> __rhs
= __p
._M_pathname
;
66 // Omit any root-name from the generic format pathname:
67 if (__p
._M_type
== _Type::_Root_name
)
69 else if (!__p
._M_cmpts
.empty()
70 && __p
._M_cmpts
.front()._M_type
== _Type::_Root_name
)
71 __rhs
.remove_prefix(__p
._M_cmpts
.front()._M_pathname
.size());
73 const size_t __len
= __lhs
.size() + (int)__add_sep
+ __rhs
.size();
74 const size_t __maxcmpts
= _M_cmpts
.size() + __p
._M_cmpts
.size();
75 if (_M_pathname
.capacity() < __len
|| _M_cmpts
.capacity() < __maxcmpts
)
77 // Construct new path and swap (strong exception-safety guarantee).
82 __tmp
+= preferred_separator
;
84 path __newp
= std::move(__tmp
);
91 _M_pathname
+= preferred_separator
;
100 path::remove_filename()
102 if (_M_type
== _Type::_Multi
)
104 if (!_M_cmpts
.empty())
106 auto cmpt
= std::prev(_M_cmpts
.end());
107 if (cmpt
->_M_type
== _Type::_Filename
&& !cmpt
->empty())
109 _M_pathname
.erase(cmpt
->_M_pos
);
110 auto prev
= std::prev(cmpt
);
111 if (prev
->_M_type
== _Type::_Root_dir
112 || prev
->_M_type
== _Type::_Root_name
)
114 _M_cmpts
.erase(cmpt
);
122 else if (_M_type
== _Type::_Filename
)
128 path::replace_filename(const path
& replacement
)
131 operator/=(replacement
);
135 #ifdef _GLIBCXX_FILESYSTEM_IS_WINDOWS
136 const fs::path::value_type dot
= L
'.';
138 const fs::path::value_type dot
= '.';
142 path::replace_extension(const path
& replacement
)
144 auto ext
= _M_find_extension();
145 // Any existing extension() is removed
146 if (ext
.first
&& ext
.second
!= string_type::npos
)
148 if (ext
.first
== &_M_pathname
)
149 _M_pathname
.erase(ext
.second
);
152 const auto& back
= _M_cmpts
.back();
153 if (ext
.first
!= &back
._M_pathname
)
154 _GLIBCXX_THROW_OR_ABORT(
155 std::logic_error("path::replace_extension failed"));
156 _M_pathname
.erase(back
._M_pos
+ ext
.second
);
159 // If replacement is not empty and does not begin with a dot character,
160 // a dot character is appended
161 if (!replacement
.empty() && replacement
.native()[0] != dot
)
163 operator+=(replacement
);
169 template<typename Iter1
, typename Iter2
>
170 int do_compare(Iter1 begin1
, Iter1 end1
, Iter2 begin2
, Iter2 end2
)
173 while (begin1
!= end1
&& begin2
!= end2
)
175 if (begin1
->native() < begin2
->native())
177 if (begin1
->native() > begin2
->native())
194 path::compare(const path
& p
) const noexcept
199 const string_type
& native() const noexcept
{ return ptr
->native(); }
202 if (empty() && p
.empty())
204 else if (_M_type
== _Type::_Multi
&& p
._M_type
== _Type::_Multi
)
205 return do_compare(_M_cmpts
.begin(), _M_cmpts
.end(),
206 p
._M_cmpts
.begin(), p
._M_cmpts
.end());
207 else if (_M_type
== _Type::_Multi
)
209 CmptRef c
[1] = { { &p
} };
210 return do_compare(_M_cmpts
.begin(), _M_cmpts
.end(), c
, c
+1);
212 else if (p
._M_type
== _Type::_Multi
)
214 CmptRef c
[1] = { { this } };
215 return do_compare(c
, c
+1, p
._M_cmpts
.begin(), p
._M_cmpts
.end());
218 return _M_pathname
.compare(p
._M_pathname
);
222 path::root_name() const
225 if (_M_type
== _Type::_Root_name
)
227 else if (_M_cmpts
.size() && _M_cmpts
.begin()->_M_type
== _Type::_Root_name
)
228 __ret
= *_M_cmpts
.begin();
233 path::root_directory() const
236 if (_M_type
== _Type::_Root_dir
)
238 __ret
._M_type
= _Type::_Root_dir
;
239 __ret
._M_pathname
.assign(1, preferred_separator
);
241 else if (!_M_cmpts
.empty())
243 auto __it
= _M_cmpts
.begin();
244 if (__it
->_M_type
== _Type::_Root_name
)
246 if (__it
!= _M_cmpts
.end() && __it
->_M_type
== _Type::_Root_dir
)
253 path::root_path() const
256 if (_M_type
== _Type::_Root_name
)
258 else if (_M_type
== _Type::_Root_dir
)
260 __ret
._M_pathname
.assign(1, preferred_separator
);
261 __ret
._M_type
= _Type::_Root_dir
;
263 else if (!_M_cmpts
.empty())
265 auto __it
= _M_cmpts
.begin();
266 if (__it
->_M_type
== _Type::_Root_name
)
269 if (__it
!= _M_cmpts
.end() && __it
->_M_type
== _Type::_Root_dir
)
272 else if (__it
->_M_type
== _Type::_Root_dir
)
279 path::relative_path() const
282 if (_M_type
== _Type::_Filename
)
284 else if (!_M_cmpts
.empty())
286 auto __it
= _M_cmpts
.begin();
287 if (__it
->_M_type
== _Type::_Root_name
)
289 if (__it
!= _M_cmpts
.end() && __it
->_M_type
== _Type::_Root_dir
)
291 if (__it
!= _M_cmpts
.end())
292 __ret
.assign(_M_pathname
.substr(__it
->_M_pos
));
298 path::parent_path() const
301 if (!has_relative_path())
303 else if (_M_cmpts
.size() >= 2)
305 for (auto __it
= _M_cmpts
.begin(), __end
= std::prev(_M_cmpts
.end());
306 __it
!= __end
; ++__it
)
315 path::has_root_name() const
317 if (_M_type
== _Type::_Root_name
)
319 if (!_M_cmpts
.empty() && _M_cmpts
.begin()->_M_type
== _Type::_Root_name
)
325 path::has_root_directory() const
327 if (_M_type
== _Type::_Root_dir
)
329 if (!_M_cmpts
.empty())
331 auto __it
= _M_cmpts
.begin();
332 if (__it
->_M_type
== _Type::_Root_name
)
334 if (__it
!= _M_cmpts
.end() && __it
->_M_type
== _Type::_Root_dir
)
341 path::has_root_path() const
343 if (_M_type
== _Type::_Root_name
|| _M_type
== _Type::_Root_dir
)
345 if (!_M_cmpts
.empty())
347 auto __type
= _M_cmpts
.front()._M_type
;
348 if (__type
== _Type::_Root_name
|| __type
== _Type::_Root_dir
)
355 path::has_relative_path() const
357 if (_M_type
== _Type::_Filename
&& !_M_pathname
.empty())
359 if (!_M_cmpts
.empty())
361 auto __it
= _M_cmpts
.begin();
362 if (__it
->_M_type
== _Type::_Root_name
)
364 if (__it
!= _M_cmpts
.end() && __it
->_M_type
== _Type::_Root_dir
)
366 if (__it
!= _M_cmpts
.end() && !__it
->_M_pathname
.empty())
374 path::has_parent_path() const
376 if (!has_relative_path())
378 return _M_cmpts
.size() >= 2;
382 path::has_filename() const
386 if (_M_type
== _Type::_Filename
)
387 return !_M_pathname
.empty();
388 if (_M_type
== _Type::_Multi
)
390 if (_M_pathname
.back() == preferred_separator
)
392 return _M_cmpts
.back().has_filename();
399 inline bool is_dot(fs::path::value_type c
) { return c
== dot
; }
401 inline bool is_dot(const fs::path
& path
)
403 const auto& filename
= path
.native();
404 return filename
.size() == 1 && is_dot(filename
[0]);
407 inline bool is_dotdot(const fs::path
& path
)
409 const auto& filename
= path
.native();
410 return filename
.size() == 2 && is_dot(filename
[0]) && is_dot(filename
[1]);
415 path::lexically_normal() const
418 C++17 [fs.path.generic] p6
419 - If the path is empty, stop.
420 - Replace each slash character in the root-name with a preferred-separator.
421 - Replace each directory-separator with a preferred-separator.
422 - Remove each dot filename and any immediately following directory-separator.
423 - As long as any appear, remove a non-dot-dot filename immediately followed
424 by a directory-separator and a dot-dot filename, along with any immediately
425 following directory-separator.
426 - If there is a root-directory, remove all dot-dot filenames and any
427 directory-separators immediately following them.
428 - If the last filename is dot-dot, remove any trailing directory-separator.
429 - If the path is empty, add a dot.
432 // If the path is empty, stop.
435 for (auto& p
: *this)
437 #ifdef _GLIBCXX_FILESYSTEM_IS_WINDOWS
438 // Replace each slash character in the root-name
439 if (p
._M_type
== _Type::_Root_name
|| p
._M_type
== _Type::_Root_dir
)
441 string_type s
= p
.native();
442 std::replace(s
.begin(), s
.end(), L
'/', L
'\\');
449 if (ret
.has_filename())
451 // remove a non-dot-dot filename immediately followed by /..
452 if (!is_dotdot(ret
.filename()))
453 ret
.remove_filename();
457 else if (!ret
.has_relative_path())
459 // remove a dot-dot filename immediately after root-directory
460 if (!ret
.has_root_directory())
465 // Got a path with a relative path (i.e. at least one non-root
466 // element) and no filename at the end (i.e. empty last element),
467 // so must have a trailing slash. See what is before it.
468 auto elem
= std::prev(ret
.end(), 2);
469 if (elem
->has_filename() && !is_dotdot(*elem
))
471 // Remove the filename before the trailing slash
472 // (equiv. to ret = ret.parent_path().remove_filename())
474 if (elem
== ret
.begin())
478 ret
._M_pathname
.erase(elem
._M_cur
->_M_pos
);
479 // Do we still have a trailing slash?
480 if (std::prev(elem
)->_M_type
== _Type::_Filename
)
481 ret
._M_cmpts
.erase(elem
._M_cur
);
483 ret
._M_cmpts
.erase(elem
._M_cur
, ret
._M_cmpts
.end());
496 if (ret
._M_cmpts
.size() >= 2)
498 auto back
= std::prev(ret
.end());
499 // If the last filename is dot-dot, ...
500 if (back
->empty() && is_dotdot(*std::prev(back
)))
501 // ... remove any trailing directory-separator.
502 ret
= ret
.parent_path();
504 // If the path is empty, add a dot.
505 else if (ret
.empty())
512 path::lexically_relative(const path
& base
) const
515 if (root_name() != base
.root_name())
517 if (is_absolute() != base
.is_absolute())
519 if (!has_root_directory() && base
.has_root_directory())
521 auto [a
, b
] = std::mismatch(begin(), end(), base
.begin(), base
.end());
522 if (a
== end() && b
== base
.end())
527 for (; b
!= base
.end(); ++b
)
532 else if (!p
.empty() && !is_dot(p
))
535 if (n
== 0 && (a
== end() || a
->empty()))
539 const path
dotdot("..");
542 for (; a
!= end(); ++a
)
550 path::lexically_proximate(const path
& base
) const
552 path rel
= lexically_relative(base
);
558 std::pair
<const path::string_type
*, std::size_t>
559 path::_M_find_extension() const
561 const string_type
* s
= nullptr;
563 if (_M_type
== _Type::_Filename
)
565 else if (_M_type
== _Type::_Multi
&& !_M_cmpts
.empty())
567 const auto& c
= _M_cmpts
.back();
568 if (c
._M_type
== _Type::_Filename
)
574 if (auto sz
= s
->size())
576 if (sz
<= 2 && (*s
)[0] == dot
)
577 return { s
, string_type::npos
};
578 const auto pos
= s
->rfind(dot
);
579 return { s
, pos
? pos
: string_type::npos
};
586 path::_M_split_cmpts()
589 if (_M_pathname
.empty())
591 _M_type
= _Type::_Filename
;
594 _M_type
= _Type::_Multi
;
597 const size_t len
= _M_pathname
.size();
599 // look for root name or root directory
600 if (_S_is_dir_sep(_M_pathname
[0]))
603 // look for root name, such as "//foo"
604 if (len
> 2 && _M_pathname
[1] == _M_pathname
[0])
606 if (!_S_is_dir_sep(_M_pathname
[2]))
608 // got root name, find its end
610 while (pos
< len
&& !_S_is_dir_sep(_M_pathname
[pos
]))
612 _M_add_root_name(pos
);
613 if (pos
< len
) // also got root directory
614 _M_add_root_dir(pos
);
618 // got something like "///foo" which is just a root directory
619 // composed of multiple redundant directory separators
626 // got root directory
627 if (_M_pathname
.find_first_not_of('/') == string_type::npos
)
629 // entire path is just slashes
630 _M_type
= _Type::_Root_dir
;
637 #ifdef _GLIBCXX_FILESYSTEM_IS_WINDOWS
638 else if (len
> 1 && _M_pathname
[1] == L
':')
640 // got disk designator
642 if (len
> 2 && _S_is_dir_sep(_M_pathname
[2]))
651 if (_S_is_dir_sep(_M_pathname
[pos
]))
654 _M_add_filename(back
, pos
- back
);
662 _M_add_filename(back
, pos
- back
);
663 else if (_S_is_dir_sep(_M_pathname
.back()))
666 // An empty element, if trailing non-root directory-separator present.
667 if (_M_cmpts
.back()._M_type
== _Type::_Filename
)
669 pos
= _M_pathname
.size();
670 _M_cmpts
.emplace_back(string_type(), _Type::_Filename
, pos
);
678 path::_M_add_root_name(size_t n
)
680 _M_cmpts
.emplace_back(_M_pathname
.substr(0, n
), _Type::_Root_name
, 0);
684 path::_M_add_root_dir(size_t pos
)
686 _M_cmpts
.emplace_back(_M_pathname
.substr(pos
, 1), _Type::_Root_dir
, pos
);
690 path::_M_add_filename(size_t pos
, size_t n
)
692 _M_cmpts
.emplace_back(_M_pathname
.substr(pos
, n
), _Type::_Filename
, pos
);
698 if (_M_cmpts
.size() == 1)
700 _M_type
= _M_cmpts
.front()._M_type
;
706 path::_S_convert_loc(const char* __first
, const char* __last
,
707 const std::locale
& __loc
)
709 #if _GLIBCXX_USE_WCHAR_T
710 auto& __cvt
= std::use_facet
<codecvt
<wchar_t, char, mbstate_t>>(__loc
);
711 basic_string
<wchar_t> __ws
;
712 if (!__str_codecvt_in(__first
, __last
, __ws
, __cvt
))
713 _GLIBCXX_THROW_OR_ABORT(filesystem_error(
714 "Cannot convert character sequence",
715 std::make_error_code(errc::illegal_byte_sequence
)));
716 #ifdef _GLIBCXX_FILESYSTEM_IS_WINDOWS
719 return _Cvt
<wchar_t>::_S_convert(__ws
.data(), __ws
.data() + __ws
.size());
722 return {__first
, __last
};
727 fs::hash_value(const path
& p
) noexcept
730 // "If for two paths, p1 == p2 then hash_value(p1) == hash_value(p2)."
731 // Equality works as if by traversing the range [begin(), end()), meaning
732 // e.g. path("a//b") == path("a/b"), so we cannot simply hash _M_pathname
733 // but need to iterate over individual elements. Use the hash_combine from
734 // http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n3876.pdf
736 for (const auto& x
: p
)
738 seed
^= std::hash
<path::string_type
>()(x
.native()) + 0x9e3779b9
739 + (seed
<<6) + (seed
>>2);
744 struct fs::filesystem_error::_Impl
746 _Impl(const string
& what_arg
, const path
& p1
, const path
& p2
)
747 : path1(p1
), path2(p2
), what(make_what(what_arg
, &p1
, &p2
))
750 _Impl(const string
& what_arg
, const path
& p1
)
751 : path1(p1
), path2(), what(make_what(what_arg
, &p1
, nullptr))
754 _Impl(const string
& what_arg
)
755 : what(make_what(what_arg
, nullptr, nullptr))
759 make_what(const std::string
& s
, const path
* p1
, const path
* p2
)
761 const std::string pstr1
= p1
? p1
->u8string() : std::string
{};
762 const std::string pstr2
= p2
? p2
->u8string() : std::string
{};
763 const size_t len
= 18 + s
.length()
764 + (pstr1
.length() ? pstr1
.length() + 3 : 0)
765 + (pstr2
.length() ? pstr2
.length() + 3 : 0);
768 w
= "filesystem error: ";
790 template class std::__shared_ptr
<const fs::filesystem_error::_Impl
>;
792 fs::filesystem_error::
793 filesystem_error(const string
& what_arg
, error_code ec
)
794 : system_error(ec
, what_arg
),
795 _M_impl(std::__make_shared
<_Impl
>(what_arg
))
798 fs::filesystem_error::
799 filesystem_error(const string
& what_arg
, const path
& p1
, error_code ec
)
800 : system_error(ec
, what_arg
),
801 _M_impl(std::__make_shared
<_Impl
>(what_arg
, p1
))
804 fs::filesystem_error::
805 filesystem_error(const string
& what_arg
, const path
& p1
, const path
& p2
,
807 : system_error(ec
, what_arg
),
808 _M_impl(std::__make_shared
<_Impl
>(what_arg
, p1
, p2
))
811 fs::filesystem_error::~filesystem_error() = default;
814 fs::filesystem_error::path1() const noexcept
815 { return _M_impl
->path1
; }
818 fs::filesystem_error::path2() const noexcept
819 { return _M_impl
->path2
; }
822 fs::filesystem_error::what() const noexcept
823 { return _M_impl
->what
.c_str(); }