1 // Class filesystem::path -*- C++ -*-
2 
3 // Copyright (C) 2014-2016 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 using std::experimental::filesystem::path;
32 
33 std::experimental::filesystem::filesystem_error::~filesystem_error() = default;
34 
35 constexpr path::value_type path::preferred_separator;
36 
37 path&
remove_filename()38 path::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 
55 path&
replace_filename(const path & replacement)56 path::replace_filename(const path& replacement)
57 {
58   remove_filename();
59   operator/=(replacement);
60   return *this;
61 }
62 
63 path&
replace_extension(const path & replacement)64 path::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 
87 namespace
88 {
89   template<typename Iter1, typename Iter2>
do_compare(Iter1 begin1,Iter1 end1,Iter2 begin2,Iter2 end2)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 
113 int
compare(const path & p) const114 path::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 
139 path
root_name() const140 path::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 
150 path
root_directory() const151 path::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 
168 path
root_path() const169 path::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 
192 path
relative_path() const193 path::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 
211 path
parent_path() const212 path::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 
225 bool
has_root_name() const226 path::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 
235 bool
has_root_directory() const236 path::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 
251 bool
has_root_path() const252 path::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 
265 bool
has_relative_path() const266 path::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 
284 bool
has_parent_path() const285 path::has_parent_path() const
286 {
287   return _M_cmpts.size() > 1;
288 }
289 
290 bool
has_filename() const291 path::has_filename() const
292 {
293   return !empty();
294 }
295 
296 std::pair<const path::string_type*, std::size_t>
_M_find_extension() const297 path::_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 
327 void
_M_split_cmpts()328 path::_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 
414 void
_M_add_root_name(size_t n)415 path::_M_add_root_name(size_t n)
416 {
417   _M_cmpts.emplace_back(_M_pathname.substr(0, n), _Type::_Root_name, 0);
418 }
419 
420 void
_M_add_root_dir(size_t pos)421 path::_M_add_root_dir(size_t pos)
422 {
423   _M_cmpts.emplace_back(_M_pathname.substr(pos, 1), _Type::_Root_dir, pos);
424 }
425 
426 void
_M_add_filename(size_t pos,size_t n)427 path::_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 
432 void
_M_trim()433 path::_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 
442 path::string_type
_S_convert_loc(const char * __first,const char * __last,const std::locale & __loc)443 path::_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 
463 std::size_t
hash_value(const path & p)464 std::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