1 // Class experimental::filesystem::path -*- C++ -*-
2 
3 // Copyright (C) 2014-2020 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   if (_M_pathname.empty())
341     return;
342 
343   {
344     // Approximate count of components, to reserve space in _M_cmpts vector:
345     int count = 1;
346     bool saw_sep_last = _S_is_dir_sep(_M_pathname[0]);
347     bool saw_non_sep = !saw_sep_last;
348     for (value_type c : _M_pathname)
349       {
350        if (_S_is_dir_sep(c))
351          saw_sep_last = true;
352        else if (saw_sep_last)
353          {
354            ++count;
355            saw_sep_last = false;
356            saw_non_sep = true;
357          }
358       }
359     if (saw_non_sep && saw_sep_last)
360       ++count; // empty filename after trailing slash
361     if (count > 1)
362       _M_cmpts.reserve(count);
363   }
364 
365   size_t pos = 0;
366   const size_t len = _M_pathname.size();
367 
368   // look for root name or root directory
369   if (_S_is_dir_sep(_M_pathname[0]))
370     {
371       // look for root name, such as "//" or "//foo"
372       if (len > 1 && _M_pathname[1] == _M_pathname[0])
373 	{
374 	  if (len == 2)
375 	    {
376 	      // entire path is just "//"
377 	      _M_type = _Type::_Root_name;
378 	      return;
379 	    }
380 
381 	  if (!_S_is_dir_sep(_M_pathname[2]))
382 	    {
383 	      // got root name, find its end
384 	      pos = 3;
385 	      while (pos < len && !_S_is_dir_sep(_M_pathname[pos]))
386 		++pos;
387 	      if (pos == len)
388 		{
389 		  _M_type = _Type::_Root_name;
390 		  return;
391 		}
392 	      _M_add_root_name(pos);
393 	      _M_add_root_dir(pos);
394 	    }
395 	  else
396 	    {
397 	      // got something like "///foo" which is just a root directory
398 	      // composed of multiple redundant directory separators
399 	      _M_add_root_dir(0);
400 	    }
401 	}
402       else if (len == 1) // got root directory only
403 	{
404 	  _M_type = _Type::_Root_dir;
405 	  return;
406 	}
407       else // got root directory
408 	_M_add_root_dir(0);
409       ++pos;
410     }
411 #ifdef _GLIBCXX_FILESYSTEM_IS_WINDOWS
412   else if (len > 1 && _M_pathname[1] == L':')
413     {
414       // got disk designator
415       if (len == 2)
416 	{
417 	  _M_type = _Type::_Root_name;
418 	  return;
419 	}
420       _M_add_root_name(2);
421       if (len > 2 && _S_is_dir_sep(_M_pathname[2]))
422 	_M_add_root_dir(2);
423       pos = 2;
424     }
425 #endif
426   else
427     {
428       size_t n = 1;
429       for (; n < _M_pathname.size() && !_S_is_dir_sep(_M_pathname[n]); ++n)
430 	{ }
431       if (n == _M_pathname.size())
432 	{
433 	  _M_type = _Type::_Filename;
434 	  return;
435 	}
436     }
437 
438   size_t back = pos;
439   while (pos < len)
440     {
441       if (_S_is_dir_sep(_M_pathname[pos]))
442 	{
443 	  if (back != pos)
444 	    _M_add_filename(back, pos - back);
445 	  back = ++pos;
446 	}
447       else
448 	++pos;
449     }
450 
451   if (back != pos)
452     _M_add_filename(back, pos - back);
453   else if (_S_is_dir_sep(_M_pathname.back()))
454     {
455       // [path.itr]/8
456       // "Dot, if one or more trailing non-root slash characters are present."
457       if (_M_cmpts.back()._M_type == _Type::_Filename)
458 	{
459 	  const auto& last = _M_cmpts.back();
460 	  pos = last._M_pos + last._M_pathname.size();
461 	  _M_cmpts.emplace_back(string_type(1, dot), _Type::_Filename, pos);
462 	}
463     }
464 
465   _M_trim();
466 }
467 
468 void
_M_add_root_name(size_t n)469 path::_M_add_root_name(size_t n)
470 {
471   _M_cmpts.emplace_back(_M_pathname.substr(0, n), _Type::_Root_name, 0);
472 }
473 
474 void
_M_add_root_dir(size_t pos)475 path::_M_add_root_dir(size_t pos)
476 {
477   _M_cmpts.emplace_back(_M_pathname.substr(pos, 1), _Type::_Root_dir, pos);
478 }
479 
480 void
_M_add_filename(size_t pos,size_t n)481 path::_M_add_filename(size_t pos, size_t n)
482 {
483   _M_cmpts.emplace_back(_M_pathname.substr(pos, n), _Type::_Filename, pos);
484 }
485 
486 void
_M_trim()487 path::_M_trim()
488 {
489   if (_M_cmpts.size() == 1)
490     {
491       _M_type = _M_cmpts.front()._M_type;
492       _M_cmpts.clear();
493     }
494 }
495 
496 path::string_type
_S_convert_loc(const char * __first,const char * __last,const std::locale & __loc)497 path::_S_convert_loc(const char* __first, const char* __last,
498 		     const std::locale& __loc)
499 {
500 #if _GLIBCXX_USE_WCHAR_T
501   auto& __cvt = std::use_facet<codecvt<wchar_t, char, mbstate_t>>(__loc);
502   basic_string<wchar_t> __ws;
503   if (!__str_codecvt_in_all(__first, __last, __ws, __cvt))
504     _GLIBCXX_THROW_OR_ABORT(filesystem_error(
505 	  "Cannot convert character sequence",
506 	  std::make_error_code(errc::illegal_byte_sequence)));
507 #ifdef _GLIBCXX_FILESYSTEM_IS_WINDOWS
508   return __ws;
509 #else
510   return _Cvt<wchar_t>::_S_convert(__ws.data(), __ws.data() + __ws.size());
511 #endif
512 #else
513   return {__first, __last};
514 #endif
515 }
516 
517 std::size_t
hash_value(const path & p)518 fs::hash_value(const path& p) noexcept
519 {
520   // [path.non-member]
521   // "If for two paths, p1 == p2 then hash_value(p1) == hash_value(p2)."
522   // Equality works as if by traversing the range [begin(), end()), meaning
523   // e.g. path("a//b") == path("a/b"), so we cannot simply hash _M_pathname
524   // but need to iterate over individual elements. Use the hash_combine from
525   // http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n3876.pdf
526   size_t seed = 0;
527   for (const auto& x : p)
528     {
529       seed ^= std::hash<path::string_type>()(x.native()) + 0x9e3779b9
530 	+ (seed<<6) + (seed>>2);
531     }
532   return seed;
533 }
534 
535 #include <experimental/string_view>
536 
537 std::string
_M_gen_what()538 fs::filesystem_error::_M_gen_what()
539 {
540   const std::string pstr1 = _M_path1.u8string();
541   const std::string pstr2 = _M_path2.u8string();
542   experimental::string_view s = this->system_error::what();
543   const size_t len = 18 + s.length()
544     + (pstr1.length() || pstr2.length() ? pstr1.length() + 3 : 0)
545     + (pstr2.length() ? pstr2.length() + 3 : 0);
546   std::string w;
547   w.reserve(len);
548   w = "filesystem error: ";
549   w.append(s.data(), s.length());
550   if (!pstr1.empty())
551     {
552       w += " [";
553       w += pstr1;
554       w += ']';
555     }
556   if (!pstr2.empty())
557     {
558       if (pstr1.empty())
559 	w += " []";
560       w += " [";
561       w += pstr2;
562       w += ']';
563     }
564   return w;
565 }
566