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