1 // Class filesystem::path -*- C++ -*-
2
3 // Copyright (C) 2014-2018 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.
10
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.
15
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.
19
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/>.
24
25 #ifndef _GLIBCXX_USE_CXX11_ABI
26 # define _GLIBCXX_USE_CXX11_ABI 1
27 #endif
28
29 #include <filesystem>
30 #ifdef _GLIBCXX_FILESYSTEM_IS_WINDOWS
31 # include <algorithm>
32 #endif
33
34 namespace fs = std::filesystem;
35 using fs::path;
36
37 fs::filesystem_error::~filesystem_error() = default;
38
39 constexpr path::value_type path::preferred_separator;
40
41 path&
remove_filename()42 path::remove_filename()
43 {
44 if (_M_type == _Type::_Multi)
45 {
46 if (!_M_cmpts.empty())
47 {
48 auto cmpt = std::prev(_M_cmpts.end());
49 if (cmpt->_M_type == _Type::_Filename && !cmpt->empty())
50 {
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)
55 {
56 _M_cmpts.erase(cmpt);
57 _M_trim();
58 }
59 else
60 cmpt->clear();
61 }
62 }
63 }
64 else if (_M_type == _Type::_Filename)
65 clear();
66 return *this;
67 }
68
69 path&
replace_filename(const path & replacement)70 path::replace_filename(const path& replacement)
71 {
72 remove_filename();
73 operator/=(replacement);
74 return *this;
75 }
76
77 path&
replace_extension(const path & replacement)78 path::replace_extension(const path& replacement)
79 {
80 auto ext = _M_find_extension();
81 // Any existing extension() is removed
82 if (ext.first && ext.second != string_type::npos)
83 {
84 if (ext.first == &_M_pathname)
85 _M_pathname.erase(ext.second);
86 else
87 {
88 auto& back = _M_cmpts.back();
89 if (ext.first != &back._M_pathname)
90 _GLIBCXX_THROW_OR_ABORT(
91 std::logic_error("path::replace_extension failed"));
92 back._M_pathname.erase(ext.second);
93 _M_pathname.erase(back._M_pos + ext.second);
94 }
95 }
96 // If replacement is not empty and does not begin with a dot character,
97 // a dot character is appended
98 if (!replacement.empty() && replacement.native()[0] != '.')
99 _M_pathname += '.';
100 operator+=(replacement);
101 return *this;
102 }
103
104 namespace
105 {
106 template<typename Iter1, typename Iter2>
do_compare(Iter1 begin1,Iter1 end1,Iter2 begin2,Iter2 end2)107 int do_compare(Iter1 begin1, Iter1 end1, Iter2 begin2, Iter2 end2)
108 {
109 int cmpt = 1;
110 while (begin1 != end1 && begin2 != end2)
111 {
112 if (begin1->native() < begin2->native())
113 return -cmpt;
114 if (begin1->native() > begin2->native())
115 return +cmpt;
116 ++begin1;
117 ++begin2;
118 ++cmpt;
119 }
120 if (begin1 == end1)
121 {
122 if (begin2 == end2)
123 return 0;
124 return -cmpt;
125 }
126 return +cmpt;
127 }
128 }
129
130 int
compare(const path & p) const131 path::compare(const path& p) const noexcept
132 {
133 struct CmptRef
134 {
135 const path* ptr;
136 const string_type& native() const noexcept { return ptr->native(); }
137 };
138
139 if (empty() && p.empty())
140 return 0;
141 else if (_M_type == _Type::_Multi && p._M_type == _Type::_Multi)
142 return do_compare(_M_cmpts.begin(), _M_cmpts.end(),
143 p._M_cmpts.begin(), p._M_cmpts.end());
144 else if (_M_type == _Type::_Multi)
145 {
146 CmptRef c[1] = { { &p } };
147 return do_compare(_M_cmpts.begin(), _M_cmpts.end(), c, c+1);
148 }
149 else if (p._M_type == _Type::_Multi)
150 {
151 CmptRef c[1] = { { this } };
152 return do_compare(c, c+1, p._M_cmpts.begin(), p._M_cmpts.end());
153 }
154 else
155 return _M_pathname.compare(p._M_pathname);
156 }
157
158 path
root_name() const159 path::root_name() const
160 {
161 path __ret;
162 if (_M_type == _Type::_Root_name)
163 __ret = *this;
164 else if (_M_cmpts.size() && _M_cmpts.begin()->_M_type == _Type::_Root_name)
165 __ret = *_M_cmpts.begin();
166 return __ret;
167 }
168
169 path
root_directory() const170 path::root_directory() const
171 {
172 path __ret;
173 if (_M_type == _Type::_Root_dir)
174 {
175 __ret._M_type = _Type::_Root_dir;
176 __ret._M_pathname.assign(1, preferred_separator);
177 }
178 else if (!_M_cmpts.empty())
179 {
180 auto __it = _M_cmpts.begin();
181 if (__it->_M_type == _Type::_Root_name)
182 ++__it;
183 if (__it != _M_cmpts.end() && __it->_M_type == _Type::_Root_dir)
184 __ret = *__it;
185 }
186 return __ret;
187 }
188
189 path
root_path() const190 path::root_path() const
191 {
192 path __ret;
193 if (_M_type == _Type::_Root_name)
194 __ret = *this;
195 else if (_M_type == _Type::_Root_dir)
196 {
197 __ret._M_pathname.assign(1, preferred_separator);
198 __ret._M_type = _Type::_Root_dir;
199 }
200 else if (!_M_cmpts.empty())
201 {
202 auto __it = _M_cmpts.begin();
203 if (__it->_M_type == _Type::_Root_name)
204 {
205 __ret = *__it++;
206 if (__it != _M_cmpts.end() && __it->_M_type == _Type::_Root_dir)
207 __ret /= *__it;
208 }
209 else if (__it->_M_type == _Type::_Root_dir)
210 __ret = *__it;
211 }
212 return __ret;
213 }
214
215 path
relative_path() const216 path::relative_path() const
217 {
218 path __ret;
219 if (_M_type == _Type::_Filename)
220 __ret = *this;
221 else if (!_M_cmpts.empty())
222 {
223 auto __it = _M_cmpts.begin();
224 if (__it->_M_type == _Type::_Root_name)
225 ++__it;
226 if (__it != _M_cmpts.end() && __it->_M_type == _Type::_Root_dir)
227 ++__it;
228 if (__it != _M_cmpts.end())
229 __ret.assign(_M_pathname.substr(__it->_M_pos));
230 }
231 return __ret;
232 }
233
234 path
parent_path() const235 path::parent_path() const
236 {
237 path __ret;
238 if (!has_relative_path())
239 __ret = *this;
240 else if (_M_cmpts.size() >= 2)
241 {
242 for (auto __it = _M_cmpts.begin(), __end = std::prev(_M_cmpts.end());
243 __it != __end; ++__it)
244 {
245 __ret /= *__it;
246 }
247 }
248 return __ret;
249 }
250
251 bool
has_root_name() const252 path::has_root_name() const
253 {
254 if (_M_type == _Type::_Root_name)
255 return true;
256 if (!_M_cmpts.empty() && _M_cmpts.begin()->_M_type == _Type::_Root_name)
257 return true;
258 return false;
259 }
260
261 bool
has_root_directory() const262 path::has_root_directory() const
263 {
264 if (_M_type == _Type::_Root_dir)
265 return true;
266 if (!_M_cmpts.empty())
267 {
268 auto __it = _M_cmpts.begin();
269 if (__it->_M_type == _Type::_Root_name)
270 ++__it;
271 if (__it != _M_cmpts.end() && __it->_M_type == _Type::_Root_dir)
272 return true;
273 }
274 return false;
275 }
276
277 bool
has_root_path() const278 path::has_root_path() const
279 {
280 if (_M_type == _Type::_Root_name || _M_type == _Type::_Root_dir)
281 return true;
282 if (!_M_cmpts.empty())
283 {
284 auto __type = _M_cmpts.front()._M_type;
285 if (__type == _Type::_Root_name || __type == _Type::_Root_dir)
286 return true;
287 }
288 return false;
289 }
290
291 bool
has_relative_path() const292 path::has_relative_path() const
293 {
294 if (_M_type == _Type::_Filename && !_M_pathname.empty())
295 return true;
296 if (!_M_cmpts.empty())
297 {
298 auto __it = _M_cmpts.begin();
299 if (__it->_M_type == _Type::_Root_name)
300 ++__it;
301 if (__it != _M_cmpts.end() && __it->_M_type == _Type::_Root_dir)
302 ++__it;
303 if (__it != _M_cmpts.end() && !__it->_M_pathname.empty())
304 return true;
305 }
306 return false;
307 }
308
309
310 bool
has_parent_path() const311 path::has_parent_path() const
312 {
313 if (!has_relative_path())
314 return !empty();
315 return _M_cmpts.size() >= 2;
316 }
317
318 bool
has_filename() const319 path::has_filename() const
320 {
321 if (empty())
322 return false;
323 if (_M_type == _Type::_Filename)
324 return !_M_pathname.empty();
325 if (_M_type == _Type::_Multi)
326 {
327 if (_M_pathname.back() == preferred_separator)
328 return false;
329 return _M_cmpts.back().has_filename();
330 }
331 return false;
332 }
333
334 namespace
335 {
336 #ifdef _GLIBCXX_FILESYSTEM_IS_WINDOWS
is_dot(wchar_t c)337 inline bool is_dot(wchar_t c) { return c == L'.'; }
338 #else
339 inline bool is_dot(char c) { return c == '.'; }
340 #endif
341
is_dot(const fs::path & path)342 inline bool is_dot(const fs::path& path)
343 {
344 const auto& filename = path.native();
345 return filename.size() == 1 && is_dot(filename[0]);
346 }
347
is_dotdot(const fs::path & path)348 inline bool is_dotdot(const fs::path& path)
349 {
350 const auto& filename = path.native();
351 return filename.size() == 2 && is_dot(filename[0]) && is_dot(filename[1]);
352 }
353 } // namespace
354
355 path
lexically_normal() const356 path::lexically_normal() const
357 {
358 /*
359 C++17 [fs.path.generic] p6
360 - If the path is empty, stop.
361 - Replace each slash character in the root-name with a preferred-separator.
362 - Replace each directory-separator with a preferred-separator.
363 - Remove each dot filename and any immediately following directory-separator.
364 - As long as any appear, remove a non-dot-dot filename immediately followed
365 by a directory-separator and a dot-dot filename, along with any immediately
366 following directory-separator.
367 - If there is a root-directory, remove all dot-dot filenames and any
368 directory-separators immediately following them.
369 - If the last filename is dot-dot, remove any trailing directory-separator.
370 - If the path is empty, add a dot.
371 */
372 path ret;
373 // If the path is empty, stop.
374 if (empty())
375 return ret;
376 for (auto& p : *this)
377 {
378 #ifdef _GLIBCXX_FILESYSTEM_IS_WINDOWS
379 // Replace each slash character in the root-name
380 if (p._M_type == _Type::_Root_name || p._M_type == _Type::_Root_dir)
381 {
382 string_type s = p.native();
383 std::replace(s.begin(), s.end(), L'/', L'\\');
384 ret /= s;
385 continue;
386 }
387 #endif
388 if (is_dotdot(p))
389 {
390 if (ret.has_filename())
391 {
392 // remove a non-dot-dot filename immediately followed by /..
393 if (!is_dotdot(ret.filename()))
394 ret.remove_filename();
395 else
396 ret /= p;
397 }
398 else if (!ret.has_relative_path())
399 {
400 // remove a dot-dot filename immediately after root-directory
401 if (!ret.has_root_directory())
402 ret /= p;
403 }
404 else
405 {
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 = ret._M_cmpts.end() - 2;
410 if (elem->has_filename() && !is_dotdot(*elem))
411 {
412 // Remove the filename before the trailing slash
413 // (equiv. to ret = ret.parent_path().remove_filename())
414
415 if (elem == ret._M_cmpts.begin())
416 ret.clear();
417 else
418 {
419 ret._M_pathname.erase(elem->_M_pos);
420 // Remove empty filename at the end:
421 ret._M_cmpts.pop_back();
422 // If we still have a trailing non-root dir separator
423 // then leave an empty filename at the end:
424 if (std::prev(elem)->_M_type == _Type::_Filename)
425 elem->clear();
426 else // remove the component completely:
427 ret._M_cmpts.pop_back();
428 }
429 }
430 else
431 // Append the ".." to something ending in "../" which happens
432 // when normalising paths like ".././.." and "../a/../.."
433 ret /= p;
434 }
435 }
436 else if (is_dot(p))
437 ret /= path();
438 else
439 ret /= p;
440 }
441
442 if (ret._M_cmpts.size() >= 2)
443 {
444 auto back = std::prev(ret.end());
445 // If the last filename is dot-dot, ...
446 if (back->empty() && is_dotdot(*std::prev(back)))
447 // ... remove any trailing directory-separator.
448 ret = ret.parent_path();
449 }
450 // If the path is empty, add a dot.
451 else if (ret.empty())
452 ret = ".";
453
454 return ret;
455 }
456
457 path
lexically_relative(const path & base) const458 path::lexically_relative(const path& base) const
459 {
460 path ret;
461 if (root_name() != base.root_name())
462 return ret;
463 if (is_absolute() != base.is_absolute())
464 return ret;
465 if (!has_root_directory() && base.has_root_directory())
466 return ret;
467 auto [a, b] = std::mismatch(begin(), end(), base.begin(), base.end());
468 if (a == end() && b == base.end())
469 ret = ".";
470 else
471 {
472 int n = 0;
473 for (; b != base.end(); ++b)
474 {
475 const path& p = *b;
476 if (is_dotdot(p))
477 --n;
478 else if (!p.empty() && !is_dot(p))
479 ++n;
480 }
481 if (n == 0 && (a == end() || a->empty()))
482 ret = ".";
483 else if (n >= 0)
484 {
485 const path dotdot("..");
486 while (n--)
487 ret /= dotdot;
488 for (; a != end(); ++a)
489 ret /= *a;
490 }
491 }
492 return ret;
493 }
494
495 path
lexically_proximate(const path & base) const496 path::lexically_proximate(const path& base) const
497 {
498 path rel = lexically_relative(base);
499 if (rel.empty())
500 rel = *this;
501 return rel;
502 }
503
504 std::pair<const path::string_type*, std::size_t>
_M_find_extension() const505 path::_M_find_extension() const
506 {
507 const std::string* s = nullptr;
508
509 if (_M_type == _Type::_Filename)
510 s = &_M_pathname;
511 else if (_M_type == _Type::_Multi && !_M_cmpts.empty())
512 {
513 const auto& c = _M_cmpts.back();
514 if (c._M_type == _Type::_Filename)
515 s = &c._M_pathname;
516 }
517
518 if (s)
519 {
520 if (auto sz = s->size())
521 {
522 if (sz <= 2 && (*s)[0] == '.')
523 return { s, string_type::npos };
524 const auto pos = s->rfind('.');
525 return { s, pos ? pos : string_type::npos };
526 }
527 }
528 return {};
529 }
530
531 void
_M_split_cmpts()532 path::_M_split_cmpts()
533 {
534 _M_cmpts.clear();
535 if (_M_pathname.empty())
536 {
537 _M_type = _Type::_Filename;
538 return;
539 }
540 _M_type = _Type::_Multi;
541
542 {
543 // Approximate count of components, to reserve space in _M_cmpts vector:
544 int count = 1;
545 bool saw_sep_last = _S_is_dir_sep(_M_pathname[0]);
546 bool saw_non_sep = !saw_sep_last;
547 for (value_type c : _M_pathname)
548 {
549 if (_S_is_dir_sep(c))
550 saw_sep_last = true;
551 else if (saw_sep_last)
552 {
553 ++count;
554 saw_sep_last = false;
555 saw_non_sep = true;
556 }
557 }
558 if (saw_non_sep && saw_sep_last)
559 ++count; // empty filename after trailing slash
560 if (count > 1)
561 _M_cmpts.reserve(count);
562 }
563
564 size_t pos = 0;
565 const size_t len = _M_pathname.size();
566
567 // look for root name or root directory
568 if (_S_is_dir_sep(_M_pathname[0]))
569 {
570 #ifdef __CYGWIN__
571 // look for root name, such as "//foo"
572 if (len > 2 && _M_pathname[1] == _M_pathname[0])
573 {
574 if (!_S_is_dir_sep(_M_pathname[2]))
575 {
576 // got root name, find its end
577 pos = 3;
578 while (pos < len && !_S_is_dir_sep(_M_pathname[pos]))
579 ++pos;
580 if (pos == len)
581 {
582 _M_type = _Type::_Root_name;
583 return;
584 }
585 _M_add_root_name(pos);
586 _M_add_root_dir(pos);
587 }
588 else
589 {
590 // got something like "///foo" which is just a root directory
591 // composed of multiple redundant directory separators
592 _M_add_root_dir(0);
593 }
594 }
595 else
596 #endif
597 {
598 // got root directory
599 if (_M_pathname.find_first_not_of('/') == string_type::npos)
600 {
601 // entire path is just slashes
602 _M_type = _Type::_Root_dir;
603 return;
604 }
605 _M_add_root_dir(0);
606 ++pos;
607 }
608 }
609 #ifdef _GLIBCXX_FILESYSTEM_IS_WINDOWS
610 else if (len > 1 && _M_pathname[1] == L':')
611 {
612 // got disk designator
613 if (len == 2)
614 {
615 _M_type = _Type::_Root_name;
616 return;
617 }
618 _M_add_root_name(2);
619 if (len > 2 && _S_is_dir_sep(_M_pathname[2]))
620 _M_add_root_dir(2);
621 pos = 2;
622 }
623 #endif
624 else
625 {
626 size_t n = 1;
627 for (; n < _M_pathname.size() && !_S_is_dir_sep(_M_pathname[n]); ++n)
628 { }
629 if (n == _M_pathname.size())
630 {
631 _M_type = _Type::_Filename;
632 return;
633 }
634 }
635
636 size_t back = pos;
637 while (pos < len)
638 {
639 if (_S_is_dir_sep(_M_pathname[pos]))
640 {
641 if (back != pos)
642 _M_add_filename(back, pos - back);
643 back = ++pos;
644 }
645 else
646 ++pos;
647 }
648
649 if (back != pos)
650 _M_add_filename(back, pos - back);
651 else if (_S_is_dir_sep(_M_pathname.back()))
652 {
653 // [fs.path.itr]/4
654 // An empty element, if trailing non-root directory-separator present.
655 if (_M_cmpts.back()._M_type == _Type::_Filename)
656 {
657 pos = _M_pathname.size();
658 _M_cmpts.emplace_back(string_type(), _Type::_Filename, pos);
659 }
660 }
661
662 _M_trim();
663 }
664
665 void
_M_add_root_name(size_t n)666 path::_M_add_root_name(size_t n)
667 {
668 _M_cmpts.emplace_back(_M_pathname.substr(0, n), _Type::_Root_name, 0);
669 }
670
671 void
_M_add_root_dir(size_t pos)672 path::_M_add_root_dir(size_t pos)
673 {
674 _M_cmpts.emplace_back(_M_pathname.substr(pos, 1), _Type::_Root_dir, pos);
675 }
676
677 void
_M_add_filename(size_t pos,size_t n)678 path::_M_add_filename(size_t pos, size_t n)
679 {
680 _M_cmpts.emplace_back(_M_pathname.substr(pos, n), _Type::_Filename, pos);
681 }
682
683 void
_M_trim()684 path::_M_trim()
685 {
686 if (_M_cmpts.size() == 1)
687 {
688 _M_type = _M_cmpts.front()._M_type;
689 _M_cmpts.clear();
690 }
691 }
692
693 path::string_type
_S_convert_loc(const char * __first,const char * __last,const std::locale & __loc)694 path::_S_convert_loc(const char* __first, const char* __last,
695 const std::locale& __loc)
696 {
697 #if _GLIBCXX_USE_WCHAR_T
698 auto& __cvt = std::use_facet<codecvt<wchar_t, char, mbstate_t>>(__loc);
699 basic_string<wchar_t> __ws;
700 if (!__str_codecvt_in(__first, __last, __ws, __cvt))
701 _GLIBCXX_THROW_OR_ABORT(filesystem_error(
702 "Cannot convert character sequence",
703 std::make_error_code(errc::illegal_byte_sequence)));
704 #ifdef _GLIBCXX_FILESYSTEM_IS_WINDOWS
705 return __ws;
706 #else
707 return _Cvt<wchar_t>::_S_convert(__ws.data(), __ws.data() + __ws.size());
708 #endif
709 #else
710 return {__first, __last};
711 #endif
712 }
713
714 std::size_t
hash_value(const path & p)715 fs::hash_value(const path& p) noexcept
716 {
717 // [path.non-member]
718 // "If for two paths, p1 == p2 then hash_value(p1) == hash_value(p2)."
719 // Equality works as if by traversing the range [begin(), end()), meaning
720 // e.g. path("a//b") == path("a/b"), so we cannot simply hash _M_pathname
721 // but need to iterate over individual elements. Use the hash_combine from
722 // http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n3876.pdf
723 size_t seed = 0;
724 for (const auto& x : p)
725 {
726 seed ^= std::hash<path::string_type>()(x.native()) + 0x9e3779b9
727 + (seed<<6) + (seed>>2);
728 }
729 return seed;
730 }
731
732 namespace std
733 {
734 _GLIBCXX_BEGIN_NAMESPACE_VERSION
735 namespace filesystem
736 {
737 string
fs_err_concat(const string & __what,const string & __path1,const string & __path2)738 fs_err_concat(const string& __what, const string& __path1,
739 const string& __path2)
740 {
741 const size_t __len = 18 + __what.length()
742 + (__path1.length() ? __path1.length() + 3 : 0)
743 + (__path2.length() ? __path2.length() + 3 : 0);
744 string __ret;
745 __ret.reserve(__len);
746 __ret = "filesystem error: ";
747 __ret += __what;
748 if (!__path1.empty())
749 {
750 __ret += " [";
751 __ret += __path1;
752 __ret += ']';
753 }
754 if (!__path2.empty())
755 {
756 __ret += " [";
757 __ret += __path2;
758 __ret += ']';
759 }
760 return __ret;
761 }
762
763 _GLIBCXX_BEGIN_NAMESPACE_CXX11
764
_M_gen_what()765 std::string filesystem_error::_M_gen_what()
766 {
767 return fs_err_concat(system_error::what(), _M_path1.native(),
768 _M_path2.native());
769 }
770
771 _GLIBCXX_END_NAMESPACE_CXX11
772
773 } // filesystem
774 _GLIBCXX_END_NAMESPACE_VERSION
775 } // std
776