1 // Class experimental::filesystem::path -*- C++ -*-
2 
3 // Copyright (C) 2014-2022 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 <experimental/filesystem>
30 
31 namespace fs = std::experimental::filesystem;
32 using fs::path;
33 
34 fs::filesystem_error::~filesystem_error() = default;
35 
36 constexpr path::value_type path::preferred_separator [[gnu::used]];
37 
38 path&
remove_filename()39 path::remove_filename()
40 {
41   if (_M_type == _Type::_Multi)
42     {
43       if (!_M_cmpts.empty())
44 	{
45 	  auto cmpt = std::prev(_M_cmpts.end());
46 	  _M_pathname.erase(cmpt->_M_pos);
47 	  _M_cmpts.erase(cmpt);
48 	  _M_trim();
49 	}
50     }
51   else
52     clear();
53   return *this;
54 }
55 
56 path&
replace_filename(const path & replacement)57 path::replace_filename(const path& replacement)
58 {
59   remove_filename();
60   operator/=(replacement);
61   return *this;
62 }
63 
64 #ifdef _GLIBCXX_FILESYSTEM_IS_WINDOWS
65 const fs::path::value_type dot = L'.';
66 #else
67 const fs::path::value_type dot = '.';
68 #endif
69 
70 path&
replace_extension(const path & replacement)71 path::replace_extension(const path& replacement)
72 {
73   auto ext = _M_find_extension();
74   if (ext.first && ext.second != string_type::npos)
75     {
76       if (ext.first == &_M_pathname)
77 	_M_pathname.erase(ext.second);
78       else
79 	{
80 	  const auto& back = _M_cmpts.back();
81 	  if (ext.first != &back._M_pathname)
82 	    _GLIBCXX_THROW_OR_ABORT(
83 		std::logic_error("path::replace_extension failed"));
84 	  _M_pathname.erase(back._M_pos + ext.second);
85 	}
86     }
87   if (!replacement.empty() && replacement.native()[0] != dot)
88     _M_pathname += dot;
89   _M_pathname += replacement.native();
90   _M_split_cmpts();
91   return *this;
92 }
93 
94 namespace
95 {
96   template<typename Iter1, typename Iter2>
do_compare(Iter1 begin1,Iter1 end1,Iter2 begin2,Iter2 end2)97     int do_compare(Iter1 begin1, Iter1 end1, Iter2 begin2, Iter2 end2)
98     {
99       int cmpt = 1;
100       while (begin1 != end1 && begin2 != end2)
101 	{
102 	  if (begin1->native() < begin2->native())
103 	    return -cmpt;
104 	  if (begin1->native() > begin2->native())
105 	    return +cmpt;
106 	  ++begin1;
107 	  ++begin2;
108 	  ++cmpt;
109 	}
110       if (begin1 == end1)
111 	{
112 	  if (begin2 == end2)
113 	    return 0;
114 	  return -cmpt;
115 	}
116       return +cmpt;
117     }
118 }
119 
120 int
compare(const path & p) const121 path::compare(const path& p) const noexcept
122 {
123   struct CmptRef
124   {
125     const path* ptr;
126     const string_type& native() const noexcept { return ptr->native(); }
127   };
128 
129   if (_M_type == _Type::_Multi && p._M_type == _Type::_Multi)
130     return do_compare(_M_cmpts.begin(), _M_cmpts.end(),
131 		      p._M_cmpts.begin(), p._M_cmpts.end());
132   else if (_M_type == _Type::_Multi)
133     {
134       CmptRef c[1] = { { &p } };
135       return do_compare(_M_cmpts.begin(), _M_cmpts.end(), c, c+1);
136     }
137   else if (p._M_type == _Type::_Multi)
138     {
139       CmptRef c[1] = { { this } };
140       return do_compare(c, c+1, p._M_cmpts.begin(), p._M_cmpts.end());
141     }
142   else
143     return _M_pathname.compare(p._M_pathname);
144 }
145 
146 path
root_name() const147 path::root_name() const
148 {
149   path __ret;
150   if (_M_type == _Type::_Root_name)
151     __ret = *this;
152   else if (_M_cmpts.size() && _M_cmpts.begin()->_M_type == _Type::_Root_name)
153     __ret = *_M_cmpts.begin();
154   return __ret;
155 }
156 
157 path
root_directory() const158 path::root_directory() const
159 {
160   path __ret;
161   if (_M_type == _Type::_Root_dir)
162     __ret = *this;
163   else if (!_M_cmpts.empty())
164     {
165       auto __it = _M_cmpts.begin();
166       if (__it->_M_type == _Type::_Root_name)
167         ++__it;
168       if (__it != _M_cmpts.end() && __it->_M_type == _Type::_Root_dir)
169         __ret = *__it;
170     }
171   return __ret;
172 }
173 
174 
175 path
root_path() const176 path::root_path() const
177 {
178   path __ret;
179   if (_M_type == _Type::_Root_name || _M_type == _Type::_Root_dir)
180     __ret = *this;
181   else if (!_M_cmpts.empty())
182     {
183       auto __it = _M_cmpts.begin();
184       if (__it->_M_type == _Type::_Root_name)
185         {
186           __ret = *__it++;
187           if (__it != _M_cmpts.end() && __it->_M_type == _Type::_Root_dir)
188             {
189               __ret._M_pathname += preferred_separator;
190               __ret._M_split_cmpts();
191             }
192         }
193       else if (__it->_M_type == _Type::_Root_dir)
194         __ret = *__it;
195     }
196   return __ret;
197 }
198 
199 path
relative_path() const200 path::relative_path() const
201 {
202   path __ret;
203   if (_M_type == _Type::_Filename)
204     __ret = *this;
205   else if (!_M_cmpts.empty())
206     {
207       auto __it = _M_cmpts.begin();
208       if (__it->_M_type == _Type::_Root_name)
209         ++__it;
210       if (__it != _M_cmpts.end() && __it->_M_type == _Type::_Root_dir)
211         ++__it;
212       if (__it != _M_cmpts.end())
213         __ret.assign(_M_pathname.substr(__it->_M_pos));
214     }
215   return __ret;
216 }
217 
218 path
parent_path() const219 path::parent_path() const
220 {
221   path __ret;
222   if (_M_cmpts.size() < 2)
223     return __ret;
224   for (auto __it = _M_cmpts.begin(), __end = std::prev(_M_cmpts.end());
225        __it != __end; ++__it)
226     {
227       __ret /= *__it;
228     }
229   return __ret;
230 }
231 
232 bool
has_root_name() const233 path::has_root_name() const
234 {
235   if (_M_type == _Type::_Root_name)
236     return true;
237   if (!_M_cmpts.empty() && _M_cmpts.begin()->_M_type == _Type::_Root_name)
238     return true;
239   return false;
240 }
241 
242 bool
has_root_directory() const243 path::has_root_directory() const
244 {
245   if (_M_type == _Type::_Root_dir)
246     return true;
247   if (!_M_cmpts.empty())
248     {
249       auto __it = _M_cmpts.begin();
250       if (__it->_M_type == _Type::_Root_name)
251         ++__it;
252       if (__it != _M_cmpts.end() && __it->_M_type == _Type::_Root_dir)
253         return true;
254     }
255   return false;
256 }
257 
258 bool
has_root_path() const259 path::has_root_path() const
260 {
261   if (_M_type == _Type::_Root_name || _M_type == _Type::_Root_dir)
262     return true;
263   if (!_M_cmpts.empty())
264     {
265       auto __type = _M_cmpts.front()._M_type;
266       if (__type == _Type::_Root_name || __type == _Type::_Root_dir)
267         return true;
268     }
269   return false;
270 }
271 
272 bool
has_relative_path() const273 path::has_relative_path() const
274 {
275   if (_M_type == _Type::_Filename)
276     return true;
277   if (!_M_cmpts.empty())
278     {
279       auto __it = _M_cmpts.begin();
280       if (__it->_M_type == _Type::_Root_name)
281         ++__it;
282       if (__it != _M_cmpts.end() && __it->_M_type == _Type::_Root_dir)
283         ++__it;
284       if (__it != _M_cmpts.end())
285         return true;
286     }
287   return false;
288 }
289 
290 
291 bool
has_parent_path() const292 path::has_parent_path() const
293 {
294   return _M_cmpts.size() > 1;
295 }
296 
297 bool
has_filename() const298 path::has_filename() const
299 {
300   return !empty();
301 }
302 
303 std::pair<const path::string_type*, std::size_t>
_M_find_extension() const304 path::_M_find_extension() const
305 {
306   const string_type* s = nullptr;
307 
308   if (_M_type != _Type::_Multi)
309     s = &_M_pathname;
310   else if (!_M_cmpts.empty())
311     {
312       const auto& c = _M_cmpts.back();
313       if (c._M_type == _Type::_Filename)
314 	s = &c._M_pathname;
315     }
316 
317   if (s)
318     {
319       if (auto sz = s->size())
320 	{
321 	  if (sz <= 2 && (*s)[0] == dot)
322 	    {
323 	      if (sz == 1 || (*s)[1] == dot)  // filename is "." or ".."
324 		return { s, string_type::npos };
325 	      else
326 		return { s, 0 };  // filename is like ".?"
327 	    }
328 	  return { s, s->rfind(dot) };
329 	}
330     }
331   return {};
332 }
333 
334 void
_M_split_cmpts()335 path::_M_split_cmpts()
336 {
337   _M_type = _Type::_Multi;
338   _M_cmpts.clear();
339 
340   // Use const-reference to access _M_pathname, to avoid "leaking" COW string.
341   const auto& pathname = _M_pathname;
342 
343   if (pathname.empty())
344     return;
345 
346   {
347     // Approximate count of components, to reserve space in _M_cmpts vector:
348     int count = 1;
349     bool saw_sep_last = _S_is_dir_sep(pathname[0]);
350     bool saw_non_sep = !saw_sep_last;
351     for (value_type c : pathname)
352       {
353        if (_S_is_dir_sep(c))
354          saw_sep_last = true;
355        else if (saw_sep_last)
356          {
357            ++count;
358            saw_sep_last = false;
359            saw_non_sep = true;
360          }
361       }
362     if (saw_non_sep && saw_sep_last)
363       ++count; // empty filename after trailing slash
364     if (count > 1)
365       _M_cmpts.reserve(count);
366   }
367 
368   size_t pos = 0;
369   const size_t len = pathname.size();
370 
371   // look for root name or root directory
372   if (_S_is_dir_sep(pathname[0]))
373     {
374       // look for root name, such as "//" or "//foo"
375       if (len > 1 && pathname[1] == pathname[0])
376 	{
377 	  if (len == 2)
378 	    {
379 	      // entire path is just "//"
380 	      _M_type = _Type::_Root_name;
381 	      return;
382 	    }
383 
384 	  if (!_S_is_dir_sep(pathname[2]))
385 	    {
386 	      // got root name, find its end
387 	      pos = 3;
388 	      while (pos < len && !_S_is_dir_sep(pathname[pos]))
389 		++pos;
390 	      if (pos == len)
391 		{
392 		  _M_type = _Type::_Root_name;
393 		  return;
394 		}
395 	      _M_add_root_name(pos);
396 	      _M_add_root_dir(pos);
397 	    }
398 	  else
399 	    {
400 	      // got something like "///foo" which is just a root directory
401 	      // composed of multiple redundant directory separators
402 	      _M_add_root_dir(0);
403 	    }
404 	}
405       else if (len == 1) // got root directory only
406 	{
407 	  _M_type = _Type::_Root_dir;
408 	  return;
409 	}
410       else // got root directory
411 	_M_add_root_dir(0);
412       ++pos;
413     }
414 #ifdef _GLIBCXX_FILESYSTEM_IS_WINDOWS
415   else if (len > 1 && pathname[1] == L':')
416     {
417       // got disk designator
418       if (len == 2)
419 	{
420 	  _M_type = _Type::_Root_name;
421 	  return;
422 	}
423       _M_add_root_name(2);
424       if (len > 2 && _S_is_dir_sep(pathname[2]))
425 	_M_add_root_dir(2);
426       pos = 2;
427     }
428 #endif
429   else
430     {
431       size_t n = 1;
432       for (; n < pathname.size() && !_S_is_dir_sep(pathname[n]); ++n)
433 	{ }
434       if (n == pathname.size())
435 	{
436 	  _M_type = _Type::_Filename;
437 	  return;
438 	}
439     }
440 
441   size_t back = pos;
442   while (pos < len)
443     {
444       if (_S_is_dir_sep(pathname[pos]))
445 	{
446 	  if (back != pos)
447 	    _M_add_filename(back, pos - back);
448 	  back = ++pos;
449 	}
450       else
451 	++pos;
452     }
453 
454   if (back != pos)
455     _M_add_filename(back, pos - back);
456   else if (_S_is_dir_sep(pathname.back()))
457     {
458       // [path.itr]/8
459       // "Dot, if one or more trailing non-root slash characters are present."
460       if (_M_cmpts.back()._M_type == _Type::_Filename)
461 	{
462 	  const auto& last = _M_cmpts.back();
463 	  pos = last._M_pos + last._M_pathname.size();
464 	  _M_cmpts.emplace_back(string_type(1, dot), _Type::_Filename, pos);
465 	}
466     }
467 
468   _M_trim();
469 }
470 
471 void
_M_add_root_name(size_t n)472 path::_M_add_root_name(size_t n)
473 {
474   _M_cmpts.emplace_back(_M_pathname.substr(0, n), _Type::_Root_name, 0);
475 }
476 
477 void
_M_add_root_dir(size_t pos)478 path::_M_add_root_dir(size_t pos)
479 {
480   _M_cmpts.emplace_back(_M_pathname.substr(pos, 1), _Type::_Root_dir, pos);
481 }
482 
483 void
_M_add_filename(size_t pos,size_t n)484 path::_M_add_filename(size_t pos, size_t n)
485 {
486   _M_cmpts.emplace_back(_M_pathname.substr(pos, n), _Type::_Filename, pos);
487 }
488 
489 void
_M_trim()490 path::_M_trim()
491 {
492   if (_M_cmpts.size() == 1)
493     {
494       _M_type = _M_cmpts.front()._M_type;
495       _M_cmpts.clear();
496     }
497 }
498 
499 path::string_type
_S_convert_loc(const char * __first,const char * __last,const std::locale & __loc)500 path::_S_convert_loc(const char* __first, const char* __last,
501 		     const std::locale& __loc)
502 {
503 #if _GLIBCXX_USE_WCHAR_T
504   auto& __cvt = std::use_facet<codecvt<wchar_t, char, mbstate_t>>(__loc);
505   basic_string<wchar_t> __ws;
506   if (!__str_codecvt_in_all(__first, __last, __ws, __cvt))
507     _GLIBCXX_THROW_OR_ABORT(filesystem_error(
508 	  "Cannot convert character sequence",
509 	  std::make_error_code(errc::illegal_byte_sequence)));
510 #ifdef _GLIBCXX_FILESYSTEM_IS_WINDOWS
511   return __ws;
512 #else
513   return _Cvt<wchar_t>::_S_convert(__ws.data(), __ws.data() + __ws.size());
514 #endif
515 #else
516   return {__first, __last};
517 #endif
518 }
519 
520 std::size_t
hash_value(const path & p)521 fs::hash_value(const path& p) noexcept
522 {
523   // [path.non-member]
524   // "If for two paths, p1 == p2 then hash_value(p1) == hash_value(p2)."
525   // Equality works as if by traversing the range [begin(), end()), meaning
526   // e.g. path("a//b") == path("a/b"), so we cannot simply hash _M_pathname
527   // but need to iterate over individual elements. Use the hash_combine from
528   // http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n3876.pdf
529   size_t seed = 0;
530   for (const auto& x : p)
531     {
532       seed ^= std::hash<path::string_type>()(x.native()) + 0x9e3779b9
533 	+ (seed<<6) + (seed>>2);
534     }
535   return seed;
536 }
537 
538 #include <experimental/string_view>
539 
540 std::string
_M_gen_what()541 fs::filesystem_error::_M_gen_what()
542 {
543   const std::string pstr1 = _M_path1.u8string();
544   const std::string pstr2 = _M_path2.u8string();
545   experimental::string_view s = this->system_error::what();
546   const size_t len = 18 + s.length()
547     + (pstr1.length() || pstr2.length() ? pstr1.length() + 3 : 0)
548     + (pstr2.length() ? pstr2.length() + 3 : 0);
549   std::string w;
550   w.reserve(len);
551   w = "filesystem error: ";
552   w.append(s.data(), s.length());
553   if (!pstr1.empty())
554     {
555       w += " [";
556       w += pstr1;
557       w += ']';
558     }
559   if (!pstr2.empty())
560     {
561       if (pstr1.empty())
562 	w += " []";
563       w += " [";
564       w += pstr2;
565       w += ']';
566     }
567   return w;
568 }
569