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