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