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