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
31namespace fs = std::experimental::filesystem;
32using fs::path;
33
34fs::filesystem_error::~filesystem_error() = default;
35
36constexpr path::value_type path::preferred_separator [[gnu::used]];
37
38path&
39path::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
56path&
57path::replace_filename(const path& replacement)
58{
59  remove_filename();
60  operator/=(replacement);
61  return *this;
62}
63
64#ifdef _GLIBCXX_FILESYSTEM_IS_WINDOWS
65const fs::path::value_type dot = L'.';
66#else
67const fs::path::value_type dot = '.';
68#endif
69
70path&
71path::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
94namespace
95{
96  template<typename Iter1, typename Iter2>
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
120int
121path::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
146path
147path::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
157path
158path::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
175path
176path::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
199path
200path::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
218path
219path::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
232bool
233path::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
242bool
243path::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
258bool
259path::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
272bool
273path::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
291bool
292path::has_parent_path() const
293{
294  return _M_cmpts.size() > 1;
295}
296
297bool
298path::has_filename() const
299{
300  return !empty();
301}
302
303std::pair<const path::string_type*, std::size_t>
304path::_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
334void
335path::_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
468void
469path::_M_add_root_name(size_t n)
470{
471  _M_cmpts.emplace_back(_M_pathname.substr(0, n), _Type::_Root_name, 0);
472}
473
474void
475path::_M_add_root_dir(size_t pos)
476{
477  _M_cmpts.emplace_back(_M_pathname.substr(pos, 1), _Type::_Root_dir, pos);
478}
479
480void
481path::_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
486void
487path::_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
496path::string_type
497path::_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
517std::size_t
518fs::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
537std::string
538fs::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