1 // Class filesystem::path -*- C++ -*-
3 // Copyright (C) 2014-2017 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 fs::filesystem_error::~filesystem_error() = default;
39 constexpr path::value_type
path::preferred_separator
;
42 path::remove_filename()
44 if (_M_type
== _Type::_Multi
)
46 if (!_M_cmpts
.empty())
48 auto cmpt
= std::prev(_M_cmpts
.end());
49 if (cmpt
->_M_type
== _Type::_Filename
&& !cmpt
->empty())
51 _M_pathname
.erase(cmpt
->_M_pos
);
52 auto prev
= std::prev(cmpt
);
53 if (prev
->_M_type
== _Type::_Root_dir
54 || prev
->_M_type
== _Type::_Root_name
)
64 else if (_M_type
== _Type::_Filename
)
66 if (!empty() && _M_pathname
.back() != '/')
72 path::replace_filename(const path
& replacement
)
75 operator/=(replacement
);
80 path::replace_extension(const path
& replacement
)
82 auto ext
= _M_find_extension();
83 // Any existing extension() is removed
84 if (ext
.first
&& ext
.second
!= string_type::npos
)
86 if (ext
.first
== &_M_pathname
)
87 _M_pathname
.erase(ext
.second
);
90 const auto& back
= _M_cmpts
.back();
91 if (ext
.first
!= &back
._M_pathname
)
92 _GLIBCXX_THROW_OR_ABORT(
93 std::logic_error("path::replace_extension failed"));
94 _M_pathname
.erase(back
._M_pos
+ ext
.second
);
97 // If replacement is not empty and does not begin with a dot character,
98 // a dot character is appended
99 if (!replacement
.empty() && replacement
.native()[0] != '.')
101 operator+=(replacement
);
107 template<typename Iter1
, typename Iter2
>
108 int do_compare(Iter1 begin1
, Iter1 end1
, Iter2 begin2
, Iter2 end2
)
111 while (begin1
!= end1
&& begin2
!= end2
)
113 if (begin1
->native() < begin2
->native())
115 if (begin1
->native() > begin2
->native())
132 path::compare(const path
& p
) const noexcept
137 const string_type
& native() const noexcept
{ return ptr
->native(); }
140 if (empty() && p
.empty())
142 else if (_M_type
== _Type::_Multi
&& p
._M_type
== _Type::_Multi
)
143 return do_compare(_M_cmpts
.begin(), _M_cmpts
.end(),
144 p
._M_cmpts
.begin(), p
._M_cmpts
.end());
145 else if (_M_type
== _Type::_Multi
)
147 CmptRef c
[1] = { { &p
} };
148 return do_compare(_M_cmpts
.begin(), _M_cmpts
.end(), c
, c
+1);
150 else if (p
._M_type
== _Type::_Multi
)
152 CmptRef c
[1] = { { this } };
153 return do_compare(c
, c
+1, p
._M_cmpts
.begin(), p
._M_cmpts
.end());
156 return _M_pathname
.compare(p
._M_pathname
);
160 path::root_name() const
163 if (_M_type
== _Type::_Root_name
)
165 else if (_M_cmpts
.size() && _M_cmpts
.begin()->_M_type
== _Type::_Root_name
)
166 __ret
= *_M_cmpts
.begin();
171 path::root_directory() const
174 if (_M_type
== _Type::_Root_dir
)
176 __ret
._M_type
= _Type::_Root_dir
;
177 __ret
._M_pathname
.assign(1, preferred_separator
);
179 else if (!_M_cmpts
.empty())
181 auto __it
= _M_cmpts
.begin();
182 if (__it
->_M_type
== _Type::_Root_name
)
184 if (__it
!= _M_cmpts
.end() && __it
->_M_type
== _Type::_Root_dir
)
191 path::root_path() const
194 if (_M_type
== _Type::_Root_name
)
196 else if (_M_type
== _Type::_Root_dir
)
198 __ret
._M_pathname
.assign(1, preferred_separator
);
199 __ret
._M_type
= _Type::_Root_dir
;
201 else if (!_M_cmpts
.empty())
203 auto __it
= _M_cmpts
.begin();
204 if (__it
->_M_type
== _Type::_Root_name
)
207 if (__it
!= _M_cmpts
.end() && __it
->_M_type
== _Type::_Root_dir
)
210 else if (__it
->_M_type
== _Type::_Root_dir
)
217 path::relative_path() const
220 if (_M_type
== _Type::_Filename
)
222 else if (!_M_cmpts
.empty())
224 auto __it
= _M_cmpts
.begin();
225 if (__it
->_M_type
== _Type::_Root_name
)
227 if (__it
!= _M_cmpts
.end() && __it
->_M_type
== _Type::_Root_dir
)
229 if (__it
!= _M_cmpts
.end())
230 __ret
.assign(_M_pathname
.substr(__it
->_M_pos
));
236 path::parent_path() const
239 if (!has_relative_path())
241 else if (_M_cmpts
.size() >= 2)
243 for (auto __it
= _M_cmpts
.begin(), __end
= std::prev(_M_cmpts
.end());
244 __it
!= __end
; ++__it
)
253 path::has_root_name() const
255 if (_M_type
== _Type::_Root_name
)
257 if (!_M_cmpts
.empty() && _M_cmpts
.begin()->_M_type
== _Type::_Root_name
)
263 path::has_root_directory() const
265 if (_M_type
== _Type::_Root_dir
)
267 if (!_M_cmpts
.empty())
269 auto __it
= _M_cmpts
.begin();
270 if (__it
->_M_type
== _Type::_Root_name
)
272 if (__it
!= _M_cmpts
.end() && __it
->_M_type
== _Type::_Root_dir
)
279 path::has_root_path() const
281 if (_M_type
== _Type::_Root_name
|| _M_type
== _Type::_Root_dir
)
283 if (!_M_cmpts
.empty())
285 auto __type
= _M_cmpts
.front()._M_type
;
286 if (__type
== _Type::_Root_name
|| __type
== _Type::_Root_dir
)
293 path::has_relative_path() const
295 if (_M_type
== _Type::_Filename
)
297 if (!_M_cmpts
.empty())
299 auto __it
= _M_cmpts
.begin();
300 if (__it
->_M_type
== _Type::_Root_name
)
302 if (__it
!= _M_cmpts
.end() && __it
->_M_type
== _Type::_Root_dir
)
304 if (__it
!= _M_cmpts
.end())
312 path::has_parent_path() const
314 if (!has_relative_path())
316 return _M_cmpts
.size() >= 2;
320 path::has_filename() const
324 if (_M_type
== _Type::_Filename
)
325 return !_M_pathname
.empty();
326 if (_M_type
== _Type::_Multi
)
328 if (_M_pathname
.back() == preferred_separator
)
330 return _M_cmpts
.back().has_filename();
337 #ifdef _GLIBCXX_FILESYSTEM_IS_WINDOWS
338 inline bool is_dot(wchar_t c
) { return c
== L
'.'; }
340 inline bool is_dot(char c
) { return c
== '.'; }
343 inline bool is_dot(const fs::path
& path
)
345 const auto& filename
= path
.native();
346 return filename
.size() == 1 && is_dot(filename
[0]);
349 inline bool is_dotdot(const fs::path
& path
)
351 const auto& filename
= path
.native();
352 return filename
.size() == 2 && is_dot(filename
[0]) && is_dot(filename
[1]);
357 path::lexically_normal() const
360 C++17 [fs.path.generic] p6
361 - If the path is empty, stop.
362 - Replace each slash character in the root-name with a preferred-separator.
363 - Replace each directory-separator with a preferred-separator.
364 - Remove each dot filename and any immediately following directory-separator.
365 - As long as any appear, remove a non-dot-dot filename immediately followed
366 by a directory-separator and a dot-dot filename, along with any immediately
367 following directory-separator.
368 - If there is a root-directory, remove all dot-dot filenames and any
369 directory-separators immediately following them.
370 - If the last filename is dot-dot, remove any trailing directory-separator.
371 - If the path is empty, add a dot.
374 // If the path is empty, stop.
377 for (auto& p
: *this)
379 #ifdef _GLIBCXX_FILESYSTEM_IS_WINDOWS
380 // Replace each slash character in the root-name
381 if (p
.is_root_name())
383 string_type s
= p
.native();
384 std::replace(s
.begin(), s
.end(), L
'/', L
'\\');
391 if (ret
.has_filename())
393 // remove a non-dot-dot filename immediately followed by /..
394 if (!is_dotdot(ret
.filename()))
395 ret
.remove_filename();
399 else if (!ret
.has_relative_path())
401 if (!ret
.is_absolute())
406 // Got a path with a relative path (i.e. at least one non-root
407 // element) and no filename at the end (i.e. empty last element),
408 // so must have a trailing slash. See what is before it.
409 auto elem
= std::prev(ret
.end(), 2);
410 if (elem
->has_filename() && !is_dotdot(*elem
))
412 // Remove the filename before the trailing slash
413 // (equiv. to ret = ret.parent_path().remove_filename())
414 ret
._M_pathname
.erase(elem
._M_cur
->_M_pos
);
415 ret
._M_cmpts
.erase(elem
._M_cur
, ret
._M_cmpts
.end());
427 if (ret
._M_cmpts
.size() >= 2)
429 auto back
= std::prev(ret
.end());
430 // If the last filename is dot-dot, ...
431 if (back
->empty() && is_dotdot(*std::prev(back
)))
432 // ... remove any trailing directory-separator.
433 ret
= ret
.parent_path();
435 // If the path is empty, add a dot.
436 else if (ret
.empty())
443 path::lexically_relative(const path
& base
) const
446 if (root_name() != base
.root_name())
448 if (is_absolute() != base
.is_absolute())
450 if (!has_root_directory() && base
.has_root_directory())
452 auto [a
, b
] = std::mismatch(begin(), end(), base
.begin(), base
.end());
453 if (a
== end() && b
== base
.end())
458 for (; b
!= base
.end(); ++b
)
468 const path
dotdot("..");
471 for (; a
!= end(); ++a
)
479 path::lexically_proximate(const path
& base
) const
481 path rel
= lexically_relative(base
);
487 std::pair
<const path::string_type
*, std::size_t>
488 path::_M_find_extension() const
490 const std::string
* s
= nullptr;
492 if (_M_type
== _Type::_Filename
)
494 else if (_M_type
== _Type::_Multi
&& !_M_cmpts
.empty())
496 const auto& c
= _M_cmpts
.back();
497 if (c
._M_type
== _Type::_Filename
)
503 if (auto sz
= s
->size())
505 if (sz
<= 2 && (*s
)[0] == '.')
506 return { s
, string_type::npos
};
507 const auto pos
= s
->rfind('.');
508 return { s
, pos
? pos
: string_type::npos
};
515 path::_M_split_cmpts()
517 _M_type
= _Type::_Multi
;
520 if (_M_pathname
.empty())
524 const size_t len
= _M_pathname
.size();
526 // look for root name or root directory
527 if (_S_is_dir_sep(_M_pathname
[0]))
530 // look for root name, such as "//foo"
531 if (len
> 2 && _M_pathname
[1] == _M_pathname
[0])
533 if (!_S_is_dir_sep(_M_pathname
[2]))
535 // got root name, find its end
537 while (pos
< len
&& !_S_is_dir_sep(_M_pathname
[pos
]))
539 _M_add_root_name(pos
);
540 if (pos
< len
) // also got root directory
541 _M_add_root_dir(pos
);
545 // got something like "///foo" which is just a root directory
546 // composed of multiple redundant directory separators
553 // got root directory
554 if (_M_pathname
.find_first_not_of('/') == string_type::npos
)
556 // entire path is just slashes
557 _M_type
= _Type::_Root_dir
;
564 #ifdef _GLIBCXX_FILESYSTEM_IS_WINDOWS
565 else if (len
> 1 && _M_pathname
[1] == L
':')
567 // got disk designator
569 if (len
> 2 && _S_is_dir_sep(_M_pathname
[2]))
578 if (_S_is_dir_sep(_M_pathname
[pos
]))
581 _M_add_filename(back
, pos
- back
);
589 _M_add_filename(back
, pos
- back
);
590 else if (_S_is_dir_sep(_M_pathname
.back()))
593 // An empty element, if trailing non-root directory-separator present.
594 if (_M_cmpts
.back()._M_type
== _Type::_Filename
)
596 const auto& last
= _M_cmpts
.back();
597 pos
= last
._M_pos
+ last
._M_pathname
.size();
598 _M_cmpts
.emplace_back(string_type(), _Type::_Filename
, pos
);
606 path::_M_add_root_name(size_t n
)
608 _M_cmpts
.emplace_back(_M_pathname
.substr(0, n
), _Type::_Root_name
, 0);
612 path::_M_add_root_dir(size_t pos
)
614 _M_cmpts
.emplace_back(_M_pathname
.substr(pos
, 1), _Type::_Root_dir
, pos
);
618 path::_M_add_filename(size_t pos
, size_t n
)
620 _M_cmpts
.emplace_back(_M_pathname
.substr(pos
, n
), _Type::_Filename
, pos
);
626 if (_M_cmpts
.size() == 1)
628 _M_type
= _M_cmpts
.front()._M_type
;
634 path::_S_convert_loc(const char* __first
, const char* __last
,
635 const std::locale
& __loc
)
637 #if _GLIBCXX_USE_WCHAR_T
638 auto& __cvt
= std::use_facet
<codecvt
<wchar_t, char, mbstate_t>>(__loc
);
639 basic_string
<wchar_t> __ws
;
640 if (!__str_codecvt_in(__first
, __last
, __ws
, __cvt
))
641 _GLIBCXX_THROW_OR_ABORT(filesystem_error(
642 "Cannot convert character sequence",
643 std::make_error_code(errc::illegal_byte_sequence
)));
644 #ifdef _GLIBCXX_FILESYSTEM_IS_WINDOWS
647 return _Cvt
<wchar_t>::_S_convert(__ws
.data(), __ws
.data() + __ws
.size());
650 return {__first
, __last
};
655 fs::hash_value(const path
& p
) noexcept
658 // "If for two paths, p1 == p2 then hash_value(p1) == hash_value(p2)."
659 // Equality works as if by traversing the range [begin(), end()), meaning
660 // e.g. path("a//b") == path("a/b"), so we cannot simply hash _M_pathname
661 // but need to iterate over individual elements. Use the hash_combine from
662 // http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n3876.pdf
664 for (const auto& x
: p
)
666 seed
^= std::hash
<path::string_type
>()(x
.native()) + 0x9e3779b9
667 + (seed
<<6) + (seed
>>2);
674 _GLIBCXX_BEGIN_NAMESPACE_VERSION
678 fs_err_concat(const string
& __what
, const string
& __path1
,
679 const string
& __path2
)
681 const size_t __len
= 18 + __what
.length()
682 + (__path1
.length() ? __path1
.length() + 3 : 0)
683 + (__path2
.length() ? __path2
.length() + 3 : 0);
685 __ret
.reserve(__len
);
686 __ret
= "filesystem error: ";
688 if (!__path1
.empty())
694 if (!__path2
.empty())
703 _GLIBCXX_BEGIN_NAMESPACE_CXX11
705 std::string
filesystem_error::_M_gen_what()
707 return fs_err_concat(system_error::what(), _M_path1
.native(),
711 _GLIBCXX_END_NAMESPACE_CXX11
714 _GLIBCXX_END_NAMESPACE_VERSION