xref: /netbsd-src/external/gpl3/gcc/dist/libstdc++-v3/src/filesystem/path.cc (revision 7330f729ccf0bd976a06f95fad452fe774fc7fd1)
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&
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&
57 path::replace_filename(const path& replacement)
58 {
59   remove_filename();
60   operator/=(replacement);
61   return *this;
62 }
63 
64 path&
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>
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
115 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
141 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
152 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
170 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
194 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
213 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
227 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
237 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
253 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
267 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
286 path::has_parent_path() const
287 {
288   return _M_cmpts.size() > 1;
289 }
290 
291 bool
292 path::has_filename() const
293 {
294   return !empty();
295 }
296 
297 std::pair<const path::string_type*, std::size_t>
298 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
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   size_t pos = 0;
338   const size_t len = _M_pathname.size();
339 
340   // look for root name or root directory
341   if (_S_is_dir_sep(_M_pathname[0]))
342     {
343       // look for root name, such as "//" or "//foo"
344       if (len > 1 && _M_pathname[1] == _M_pathname[0])
345 	{
346 	  if (len == 2)
347 	    {
348 	      // entire path is just "//"
349 	      _M_type = _Type::_Root_name;
350 	      return;
351 	    }
352 
353 	  if (!_S_is_dir_sep(_M_pathname[2]))
354 	    {
355 	      // got root name, find its end
356 	      pos = 3;
357 	      while (pos < len && !_S_is_dir_sep(_M_pathname[pos]))
358 		++pos;
359 	      _M_add_root_name(pos);
360 	      if (pos < len) // also got root directory
361 		_M_add_root_dir(pos);
362 	    }
363 	  else
364 	    {
365 	      // got something like "///foo" which is just a root directory
366 	      // composed of multiple redundant directory separators
367 	      _M_add_root_dir(0);
368 	    }
369 	}
370       else // got root directory
371 	_M_add_root_dir(0);
372       ++pos;
373     }
374 #ifdef _GLIBCXX_FILESYSTEM_IS_WINDOWS
375   else if (len > 1 && _M_pathname[1] == L':')
376     {
377       // got disk designator
378       _M_add_root_name(2);
379       if (len > 2 && _S_is_dir_sep(_M_pathname[2]))
380 	_M_add_root_dir(2);
381       pos = 2;
382     }
383 #endif
384 
385   size_t back = pos;
386   while (pos < len)
387     {
388       if (_S_is_dir_sep(_M_pathname[pos]))
389 	{
390 	  if (back != pos)
391 	    _M_add_filename(back, pos - back);
392 	  back = ++pos;
393 	}
394       else
395 	++pos;
396     }
397 
398   if (back != pos)
399     _M_add_filename(back, pos - back);
400   else if (_S_is_dir_sep(_M_pathname.back()))
401     {
402       // [path.itr]/8
403       // "Dot, if one or more trailing non-root slash characters are present."
404       if (_M_cmpts.back()._M_type == _Type::_Filename)
405 	{
406 	  const auto& last = _M_cmpts.back();
407 	  pos = last._M_pos + last._M_pathname.size();
408 	  _M_cmpts.emplace_back(string_type(1, '.'), _Type::_Filename, pos);
409 	}
410     }
411 
412   _M_trim();
413 }
414 
415 void
416 path::_M_add_root_name(size_t n)
417 {
418   _M_cmpts.emplace_back(_M_pathname.substr(0, n), _Type::_Root_name, 0);
419 }
420 
421 void
422 path::_M_add_root_dir(size_t pos)
423 {
424   _M_cmpts.emplace_back(_M_pathname.substr(pos, 1), _Type::_Root_dir, pos);
425 }
426 
427 void
428 path::_M_add_filename(size_t pos, size_t n)
429 {
430   _M_cmpts.emplace_back(_M_pathname.substr(pos, n), _Type::_Filename, pos);
431 }
432 
433 void
434 path::_M_trim()
435 {
436   if (_M_cmpts.size() == 1)
437     {
438       _M_type = _M_cmpts.front()._M_type;
439       _M_cmpts.clear();
440     }
441 }
442 
443 path::string_type
444 path::_S_convert_loc(const char* __first, const char* __last,
445 		     const std::locale& __loc)
446 {
447 #if _GLIBCXX_USE_WCHAR_T
448   auto& __cvt = std::use_facet<codecvt<wchar_t, char, mbstate_t>>(__loc);
449   basic_string<wchar_t> __ws;
450   if (!__str_codecvt_in(__first, __last, __ws, __cvt))
451     _GLIBCXX_THROW_OR_ABORT(filesystem_error(
452 	  "Cannot convert character sequence",
453 	  std::make_error_code(errc::illegal_byte_sequence)));
454 #ifdef _GLIBCXX_FILESYSTEM_IS_WINDOWS
455   return __ws;
456 #else
457   return _Cvt<wchar_t>::_S_convert(__ws.data(), __ws.data() + __ws.size());
458 #endif
459 #else
460   return {__first, __last};
461 #endif
462 }
463 
464 std::size_t
465 fs::hash_value(const path& p) noexcept
466 {
467   // [path.non-member]
468   // "If for two paths, p1 == p2 then hash_value(p1) == hash_value(p2)."
469   // Equality works as if by traversing the range [begin(), end()), meaning
470   // e.g. path("a//b") == path("a/b"), so we cannot simply hash _M_pathname
471   // but need to iterate over individual elements. Use the hash_combine from
472   // http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n3876.pdf
473   size_t seed = 0;
474   for (const auto& x : p)
475     {
476       seed ^= std::hash<path::string_type>()(x.native()) + 0x9e3779b9
477 	+ (seed<<6) + (seed>>2);
478     }
479   return seed;
480 }
481 
482 namespace std
483 {
484 _GLIBCXX_BEGIN_NAMESPACE_VERSION
485 namespace filesystem
486 {
487   extern string
488   fs_err_concat(const string& __what, const string& __path1,
489 		const string& __path2);
490 } // namespace filesystem
491 
492 namespace experimental::filesystem::v1 {
493 _GLIBCXX_BEGIN_NAMESPACE_CXX11
494 
495   std::string filesystem_error::_M_gen_what()
496   {
497     using std::filesystem::fs_err_concat;
498     return fs_err_concat(system_error::what(), _M_path1.native(),
499 			 _M_path2.native());
500   }
501 
502 _GLIBCXX_END_NAMESPACE_CXX11
503 } // namespace experimental::filesystem::v1
504 
505 _GLIBCXX_END_NAMESPACE_VERSION
506 } // namespace std
507