PR libstdc++/82777 fix path normalization for dot-dot
[official-gcc.git] / libstdc++-v3 / src / filesystem / std-path.cc
blob330aee72b139efc96cab13539a7c2526a68fea64
1 // Class filesystem::path -*- C++ -*-
3 // Copyright (C) 2014-2017 Free Software Foundation, Inc.
4 //
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)
9 // any later version.
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
27 #endif
29 #include <filesystem>
30 #ifdef _GLIBCXX_FILESYSTEM_IS_WINDOWS
31 # include <algorithm>
32 #endif
34 namespace fs = std::filesystem;
35 using fs::path;
37 fs::filesystem_error::~filesystem_error() = default;
39 constexpr path::value_type path::preferred_separator;
41 path&
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)
56 _M_cmpts.erase(cmpt);
57 _M_trim();
59 else
60 cmpt->clear();
64 else if (_M_type == _Type::_Filename)
65 clear();
66 if (!empty() && _M_pathname.back() != '/')
67 throw 1;
68 return *this;
71 path&
72 path::replace_filename(const path& replacement)
74 remove_filename();
75 operator/=(replacement);
76 return *this;
79 path&
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);
88 else
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] != '.')
100 _M_pathname += '.';
101 operator+=(replacement);
102 return *this;
105 namespace
107 template<typename Iter1, typename Iter2>
108 int do_compare(Iter1 begin1, Iter1 end1, Iter2 begin2, Iter2 end2)
110 int cmpt = 1;
111 while (begin1 != end1 && begin2 != end2)
113 if (begin1->native() < begin2->native())
114 return -cmpt;
115 if (begin1->native() > begin2->native())
116 return +cmpt;
117 ++begin1;
118 ++begin2;
119 ++cmpt;
121 if (begin1 == end1)
123 if (begin2 == end2)
124 return 0;
125 return -cmpt;
127 return +cmpt;
132 path::compare(const path& p) const noexcept
134 struct CmptRef
136 const path* ptr;
137 const string_type& native() const noexcept { return ptr->native(); }
140 if (empty() && p.empty())
141 return 0;
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());
155 else
156 return _M_pathname.compare(p._M_pathname);
159 path
160 path::root_name() const
162 path __ret;
163 if (_M_type == _Type::_Root_name)
164 __ret = *this;
165 else if (_M_cmpts.size() && _M_cmpts.begin()->_M_type == _Type::_Root_name)
166 __ret = *_M_cmpts.begin();
167 return __ret;
170 path
171 path::root_directory() const
173 path __ret;
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)
183 ++__it;
184 if (__it != _M_cmpts.end() && __it->_M_type == _Type::_Root_dir)
185 __ret = *__it;
187 return __ret;
190 path
191 path::root_path() const
193 path __ret;
194 if (_M_type == _Type::_Root_name)
195 __ret = *this;
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)
206 __ret = *__it++;
207 if (__it != _M_cmpts.end() && __it->_M_type == _Type::_Root_dir)
208 __ret /= *__it;
210 else if (__it->_M_type == _Type::_Root_dir)
211 __ret = *__it;
213 return __ret;
216 path
217 path::relative_path() const
219 path __ret;
220 if (_M_type == _Type::_Filename)
221 __ret = *this;
222 else if (!_M_cmpts.empty())
224 auto __it = _M_cmpts.begin();
225 if (__it->_M_type == _Type::_Root_name)
226 ++__it;
227 if (__it != _M_cmpts.end() && __it->_M_type == _Type::_Root_dir)
228 ++__it;
229 if (__it != _M_cmpts.end())
230 __ret.assign(_M_pathname.substr(__it->_M_pos));
232 return __ret;
235 path
236 path::parent_path() const
238 path __ret;
239 if (!has_relative_path())
240 __ret = *this;
241 else if (_M_cmpts.size() >= 2)
243 for (auto __it = _M_cmpts.begin(), __end = std::prev(_M_cmpts.end());
244 __it != __end; ++__it)
246 __ret /= *__it;
249 return __ret;
252 bool
253 path::has_root_name() const
255 if (_M_type == _Type::_Root_name)
256 return true;
257 if (!_M_cmpts.empty() && _M_cmpts.begin()->_M_type == _Type::_Root_name)
258 return true;
259 return false;
262 bool
263 path::has_root_directory() const
265 if (_M_type == _Type::_Root_dir)
266 return true;
267 if (!_M_cmpts.empty())
269 auto __it = _M_cmpts.begin();
270 if (__it->_M_type == _Type::_Root_name)
271 ++__it;
272 if (__it != _M_cmpts.end() && __it->_M_type == _Type::_Root_dir)
273 return true;
275 return false;
278 bool
279 path::has_root_path() const
281 if (_M_type == _Type::_Root_name || _M_type == _Type::_Root_dir)
282 return true;
283 if (!_M_cmpts.empty())
285 auto __type = _M_cmpts.front()._M_type;
286 if (__type == _Type::_Root_name || __type == _Type::_Root_dir)
287 return true;
289 return false;
292 bool
293 path::has_relative_path() const
295 if (_M_type == _Type::_Filename)
296 return true;
297 if (!_M_cmpts.empty())
299 auto __it = _M_cmpts.begin();
300 if (__it->_M_type == _Type::_Root_name)
301 ++__it;
302 if (__it != _M_cmpts.end() && __it->_M_type == _Type::_Root_dir)
303 ++__it;
304 if (__it != _M_cmpts.end())
305 return true;
307 return false;
311 bool
312 path::has_parent_path() const
314 if (!has_relative_path())
315 return !empty();
316 return _M_cmpts.size() >= 2;
319 bool
320 path::has_filename() const
322 if (empty())
323 return false;
324 if (_M_type == _Type::_Filename)
325 return !_M_pathname.empty();
326 if (_M_type == _Type::_Multi)
328 if (_M_pathname.back() == preferred_separator)
329 return false;
330 return _M_cmpts.back().has_filename();
332 return false;
335 namespace
337 #ifdef _GLIBCXX_FILESYSTEM_IS_WINDOWS
338 inline bool is_dot(wchar_t c) { return c == L'.'; }
339 #else
340 inline bool is_dot(char c) { return c == '.'; }
341 #endif
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]);
354 } // namespace
356 path
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.
373 path ret;
374 // If the path is empty, stop.
375 if (empty())
376 return ret;
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'\\');
385 ret /= s;
386 continue;
388 #endif
389 if (is_dotdot(p))
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();
396 else
397 ret /= p;
399 else if (!ret.has_relative_path())
401 if (!ret.is_absolute())
402 ret /= p;
404 else
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());
417 else // ???
418 ret /= p;
421 else if (is_dot(p))
422 ret /= path();
423 else
424 ret /= p;
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())
437 ret = ".";
439 return ret;
442 path
443 path::lexically_relative(const path& base) const
445 path ret;
446 if (root_name() != base.root_name())
447 return ret;
448 if (is_absolute() != base.is_absolute())
449 return ret;
450 if (!has_root_directory() && base.has_root_directory())
451 return ret;
452 auto [a, b] = std::mismatch(begin(), end(), base.begin(), base.end());
453 if (a == end() && b == base.end())
454 ret = ".";
455 else
457 int n = 0;
458 for (; b != base.end(); ++b)
460 const path& p = *b;
461 if (is_dotdot(p))
462 --n;
463 else if (!is_dot(p))
464 ++n;
466 if (n >= 0)
468 const path dotdot("..");
469 while (n--)
470 ret /= dotdot;
471 for (; a != end(); ++a)
472 ret /= *a;
475 return ret;
478 path
479 path::lexically_proximate(const path& base) const
481 path rel = lexically_relative(base);
482 if (rel.empty())
483 rel = *this;
484 return rel;
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)
493 s = &_M_pathname;
494 else if (_M_type == _Type::_Multi && !_M_cmpts.empty())
496 const auto& c = _M_cmpts.back();
497 if (c._M_type == _Type::_Filename)
498 s = &c._M_pathname;
501 if (s)
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 };
511 return {};
514 void
515 path::_M_split_cmpts()
517 _M_type = _Type::_Multi;
518 _M_cmpts.clear();
520 if (_M_pathname.empty())
521 return;
523 size_t pos = 0;
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]))
529 #ifdef __CYGWIN__
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
536 pos = 3;
537 while (pos < len && !_S_is_dir_sep(_M_pathname[pos]))
538 ++pos;
539 _M_add_root_name(pos);
540 if (pos < len) // also got root directory
541 _M_add_root_dir(pos);
543 else
545 // got something like "///foo" which is just a root directory
546 // composed of multiple redundant directory separators
547 _M_add_root_dir(0);
550 else
551 #endif
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;
558 return;
560 _M_add_root_dir(0);
561 ++pos;
564 #ifdef _GLIBCXX_FILESYSTEM_IS_WINDOWS
565 else if (len > 1 && _M_pathname[1] == L':')
567 // got disk designator
568 _M_add_root_name(2);
569 if (len > 2 && _S_is_dir_sep(_M_pathname[2]))
570 _M_add_root_dir(2);
571 pos = 2;
573 #endif
575 size_t back = pos;
576 while (pos < len)
578 if (_S_is_dir_sep(_M_pathname[pos]))
580 if (back != pos)
581 _M_add_filename(back, pos - back);
582 back = ++pos;
584 else
585 ++pos;
588 if (back != pos)
589 _M_add_filename(back, pos - back);
590 else if (_S_is_dir_sep(_M_pathname.back()))
592 // [fs.path.itr]/4
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);
602 _M_trim();
605 void
606 path::_M_add_root_name(size_t n)
608 _M_cmpts.emplace_back(_M_pathname.substr(0, n), _Type::_Root_name, 0);
611 void
612 path::_M_add_root_dir(size_t pos)
614 _M_cmpts.emplace_back(_M_pathname.substr(pos, 1), _Type::_Root_dir, pos);
617 void
618 path::_M_add_filename(size_t pos, size_t n)
620 _M_cmpts.emplace_back(_M_pathname.substr(pos, n), _Type::_Filename, pos);
623 void
624 path::_M_trim()
626 if (_M_cmpts.size() == 1)
628 _M_type = _M_cmpts.front()._M_type;
629 _M_cmpts.clear();
633 path::string_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
645 return __ws;
646 #else
647 return _Cvt<wchar_t>::_S_convert(__ws.data(), __ws.data() + __ws.size());
648 #endif
649 #else
650 return {__first, __last};
651 #endif
654 std::size_t
655 fs::hash_value(const path& p) noexcept
657 // [path.non-member]
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
663 size_t seed = 0;
664 for (const auto& x : p)
666 seed ^= std::hash<path::string_type>()(x.native()) + 0x9e3779b9
667 + (seed<<6) + (seed>>2);
669 return seed;
672 namespace std
674 _GLIBCXX_BEGIN_NAMESPACE_VERSION
675 namespace filesystem
677 string
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);
684 string __ret;
685 __ret.reserve(__len);
686 __ret = "filesystem error: ";
687 __ret += __what;
688 if (!__path1.empty())
690 __ret += " [";
691 __ret += __path1;
692 __ret += ']';
694 if (!__path2.empty())
696 __ret += " [";
697 __ret += __path2;
698 __ret += ']';
700 return __ret;
703 _GLIBCXX_BEGIN_NAMESPACE_CXX11
705 std::string filesystem_error::_M_gen_what()
707 return fs_err_concat(system_error::what(), _M_path1.native(),
708 _M_path2.native());
711 _GLIBCXX_END_NAMESPACE_CXX11
713 } // filesystem
714 _GLIBCXX_END_NAMESPACE_VERSION
715 } // std