xref: /netbsd-src/external/gpl3/gcc/dist/libstdc++-v3/src/c++17/fs_dir.cc (revision b1e838363e3c6fc78a55519254d99869742dd33c)
1 // Class filesystem::directory_entry etc. -*- C++ -*-
2 
3 // Copyright (C) 2014-2022 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 <bits/largefile-config.h>
30 #include <filesystem>
31 #include <utility>
32 #include <stack>
33 #include <string.h>
34 #include <errno.h>
35 #define _GLIBCXX_BEGIN_NAMESPACE_FILESYSTEM namespace filesystem {
36 #define _GLIBCXX_END_NAMESPACE_FILESYSTEM }
37 #include "../filesystem/dir-common.h"
38 
39 namespace fs = std::filesystem;
40 namespace posix = std::filesystem::__gnu_posix;
41 
42 template class std::__shared_ptr<fs::_Dir>;
43 template class std::__shared_ptr<fs::recursive_directory_iterator::_Dir_stack>;
44 
45 struct fs::_Dir : _Dir_base
46 {
_Dirfs::_Dir47   _Dir(const fs::path& p, bool skip_permission_denied, bool nofollow,
48        [[maybe_unused]] bool filename_only, error_code& ec)
49   : _Dir_base(p.c_str(), skip_permission_denied, nofollow, ec)
50   {
51 #if _GLIBCXX_HAVE_DIRFD && _GLIBCXX_HAVE_OPENAT && _GLIBCXX_HAVE_UNLINKAT
52     if (filename_only)
53       return; // Do not store path p when we aren't going to use it.
54 #endif
55 
56     if (!ec)
57       path = p;
58   }
59 
_Dirfs::_Dir60   _Dir(_Dir_base&& d, const path& p) : _Dir_base(std::move(d)), path(p) { }
61 
62   _Dir(_Dir&&) = default;
63 
64   // Returns false when the end of the directory entries is reached.
65   // Reports errors by setting ec.
advancefs::_Dir66   bool advance(bool skip_permission_denied, error_code& ec) noexcept
67   {
68     if (const auto entp = _Dir_base::advance(skip_permission_denied, ec))
69       {
70 	auto name = path;
71 	name /= entp->d_name;
72 	file_type type = file_type::none;
73 #ifdef _GLIBCXX_HAVE_STRUCT_DIRENT_D_TYPE
74 	// Even if the OS supports dirent::d_type the filesystem might not:
75 	if (entp->d_type != DT_UNKNOWN)
76 	  type = get_file_type(*entp);
77 #endif
78 	entry = fs::directory_entry{std::move(name), type};
79 	return true;
80       }
81     else if (!ec)
82       {
83 	// reached the end
84 	entry = {};
85       }
86     return false;
87   }
88 
advancefs::_Dir89   bool advance(error_code& ec) noexcept { return advance(false, ec); }
90 
91   // Returns false when the end of the directory entries is reached.
92   // Reports errors by throwing.
advancefs::_Dir93   bool advance(bool skip_permission_denied = false)
94   {
95     error_code ec;
96     const bool ok = advance(skip_permission_denied, ec);
97     if (ec)
98       _GLIBCXX_THROW_OR_ABORT(filesystem_error(
99 	      "directory iterator cannot advance", ec));
100     return ok;
101   }
102 
should_recursefs::_Dir103   bool should_recurse(bool follow_symlink, error_code& ec) const
104   {
105     file_type type = entry._M_type;
106     if (type == file_type::none)
107     {
108       type = entry.symlink_status(ec).type();
109       if (ec)
110 	return false;
111     }
112 
113     if (type == file_type::directory)
114       return true;
115     if (type == file_type::symlink)
116       return follow_symlink && is_directory(entry.status(ec));
117     return false;
118   }
119 
120   // Return a pathname for the current directory entry, as an _At_path.
121   _Dir_base::_At_path
currentfs::_Dir122   current() const noexcept
123   {
124     const fs::path& p = entry.path();
125 #if _GLIBCXX_HAVE_DIRFD
126     if (!p.empty()) [[__likely__]]
127       {
128 	auto len = std::prev(p.end())->native().size();
129 	return {::dirfd(this->dirp), p.c_str(), p.native().size() - len};
130       }
131 #endif
132     return p.c_str();
133   }
134 
135   // Create a new _Dir for the directory this->entry.path().
136   _Dir
open_subdirfs::_Dir137   open_subdir(bool skip_permission_denied, bool nofollow,
138 	      error_code& ec) const noexcept
139   {
140     _Dir_base d(current(), skip_permission_denied, nofollow, ec);
141     // If this->path is empty, the new _Dir should have an empty path too.
142     const fs::path& p = this->path.empty() ? this->path : this->entry.path();
143     return _Dir(std::move(d), p);
144   }
145 
146   bool
do_unlinkfs::_Dir147   do_unlink(bool is_directory, error_code& ec) const noexcept
148   {
149 #if _GLIBCXX_HAVE_UNLINKAT
150     const auto atp = current();
151     if (::unlinkat(atp.dir(), atp.path_at_dir(),
152 		   is_directory ? AT_REMOVEDIR : 0) == -1)
153       {
154 	ec.assign(errno, std::generic_category());
155 	return false;
156       }
157     else
158       {
159 	ec.clear();
160 	return true;
161       }
162 #else
163     return fs::remove(entry.path(), ec);
164 #endif
165   }
166 
167   // Remove the non-directory that this->entry refers to.
168   bool
unlinkfs::_Dir169   unlink(error_code& ec) const noexcept
170   { return do_unlink(/* is_directory*/ false, ec); }
171 
172   // Remove the directory that this->entry refers to.
173   bool
rmdirfs::_Dir174   rmdir(error_code& ec) const noexcept
175   { return do_unlink(/* is_directory*/ true, ec); }
176 
177   fs::path		path; // Empty if only using unlinkat with file descr.
178   directory_entry	entry;
179 };
180 
181 namespace
182 {
183   template<typename Bitmask>
184     inline bool
is_set(Bitmask obj,Bitmask bits)185     is_set(Bitmask obj, Bitmask bits)
186     {
187       return (obj & bits) != Bitmask::none;
188     }
189 
190 // Non-standard directory option flags, currently only for internal use:
191 //
192 // Do not allow directory iterator to open a symlink.
193 // This might seem redundant given directory_options::follow_directory_symlink
194 // but that is only checked for recursing into sub-directories, and we need
195 // something that controls the initial opendir() call in the constructor.
196 constexpr fs::directory_options __directory_iterator_nofollow{64};
197 // Do not store full paths in std::filesystem::recursive_directory_iterator.
198 // When fs::remove_all uses recursive_directory_iterator::__erase and unlinkat
199 // is available in libc, we do not need the parent directory's path, only the
200 // filenames of the directory entries (and a file descriptor for the parent).
201 // This flag avoids allocating memory for full paths that won't be needed.
202 constexpr fs::directory_options __directory_iterator_filename_only{128};
203 }
204 
205 fs::directory_iterator::
directory_iterator(const path & p,directory_options options,error_code * ecptr)206 directory_iterator(const path& p, directory_options options, error_code* ecptr)
207 {
208   // Do not report an error for permission denied errors.
209   const bool skip_permission_denied
210     = is_set(options, directory_options::skip_permission_denied);
211   // Do not allow opening a symlink.
212   const bool nofollow = is_set(options, __directory_iterator_nofollow);
213 
214   error_code ec;
215   _Dir dir(p, skip_permission_denied, nofollow, /*filename only*/false, ec);
216 
217   if (dir.dirp)
218     {
219       auto sp = std::__make_shared<fs::_Dir>(std::move(dir));
220       if (sp->advance(skip_permission_denied, ec))
221 	_M_dir.swap(sp);
222     }
223   if (ecptr)
224     *ecptr = ec;
225   else if (ec)
226     _GLIBCXX_THROW_OR_ABORT(fs::filesystem_error(
227 	  "directory iterator cannot open directory", p, ec));
228 }
229 
230 const fs::directory_entry&
operator *() const231 fs::directory_iterator::operator*() const noexcept
232 {
233   return _M_dir->entry;
234 }
235 
236 fs::directory_iterator&
operator ++()237 fs::directory_iterator::operator++()
238 {
239   if (!_M_dir)
240     _GLIBCXX_THROW_OR_ABORT(filesystem_error(
241 	  "cannot advance non-dereferenceable directory iterator",
242 	  std::make_error_code(errc::invalid_argument)));
243   if (!_M_dir->advance())
244     _M_dir.reset();
245   return *this;
246 }
247 
248 fs::directory_iterator&
increment(error_code & ec)249 fs::directory_iterator::increment(error_code& ec)
250 {
251   if (!_M_dir)
252     {
253       ec = std::make_error_code(errc::invalid_argument);
254       return *this;
255     }
256   if (!_M_dir->advance(ec))
257     _M_dir.reset();
258   return *this;
259 }
260 
261 struct fs::recursive_directory_iterator::_Dir_stack : std::stack<_Dir>
262 {
_Dir_stackfs::recursive_directory_iterator::_Dir_stack263   _Dir_stack(directory_options opts, _Dir&& dir)
264   : options(opts), pending(true)
265   {
266     this->push(std::move(dir));
267   }
268 
269   path::string_type orig;
270   const directory_options options;
271   bool pending;
272 
clearfs::recursive_directory_iterator::_Dir_stack273   void clear() { c.clear(); }
274 
current_pathfs::recursive_directory_iterator::_Dir_stack275   path current_path() const
276   {
277     path p;
278     if (top().path.empty())
279       {
280 	// Reconstruct path that failed from dir stack.
281 	p = orig;
282 	for (auto& d : this->c)
283 	  p /= d.entry.path();
284       }
285     else
286       p = top().entry.path();
287     return p;
288   }
289 };
290 
291 fs::recursive_directory_iterator::
recursive_directory_iterator(const path & p,directory_options options,error_code * ecptr)292 recursive_directory_iterator(const path& p, directory_options options,
293                              error_code* ecptr)
294 {
295   // Do not report an error for permission denied errors.
296   const bool skip_permission_denied
297     = is_set(options, directory_options::skip_permission_denied);
298   // Do not allow opening a symlink as the starting directory.
299   const bool nofollow = is_set(options, __directory_iterator_nofollow);
300   // Prefer to store only filenames (not full paths) in directory_entry values.
301   const bool filename_only
302      = is_set(options, __directory_iterator_filename_only);
303 
304   error_code ec;
305   _Dir dir(p, skip_permission_denied, nofollow, filename_only, ec);
306 
307   if (dir.dirp)
308     {
309       auto sp = std::__make_shared<_Dir_stack>(options, std::move(dir));
310       if (ecptr ? sp->top().advance(skip_permission_denied, *ecptr)
311 		: sp->top().advance(skip_permission_denied))
312 	{
313 	  _M_dirs.swap(sp);
314 	  if (filename_only) // Need to save original path for error reporting.
315 	    _M_dirs->orig = p.native();
316 	}
317     }
318   else if (ecptr)
319     *ecptr = ec;
320   else if (ec)
321     _GLIBCXX_THROW_OR_ABORT(fs::filesystem_error(
322 	  "recursive directory iterator cannot open directory", p, ec));
323 }
324 
325 fs::recursive_directory_iterator::~recursive_directory_iterator() = default;
326 
327 fs::directory_options
options() const328 fs::recursive_directory_iterator::options() const noexcept
329 {
330   return _M_dirs->options;
331 }
332 
333 int
depth() const334 fs::recursive_directory_iterator::depth() const noexcept
335 {
336   return int(_M_dirs->size()) - 1;
337 }
338 
339 bool
recursion_pending() const340 fs::recursive_directory_iterator::recursion_pending() const noexcept
341 {
342   return _M_dirs->pending;
343 }
344 
345 const fs::directory_entry&
operator *() const346 fs::recursive_directory_iterator::operator*() const noexcept
347 {
348   return _M_dirs->top().entry;
349 }
350 
351 fs::recursive_directory_iterator&
352 fs::recursive_directory_iterator::
353 operator=(const recursive_directory_iterator& other) noexcept = default;
354 
355 fs::recursive_directory_iterator&
356 fs::recursive_directory_iterator::
357 operator=(recursive_directory_iterator&& other) noexcept = default;
358 
359 fs::recursive_directory_iterator&
operator ++()360 fs::recursive_directory_iterator::operator++()
361 {
362   error_code ec;
363   increment(ec);
364   if (ec)
365     _GLIBCXX_THROW_OR_ABORT(filesystem_error(
366 	  "cannot increment recursive directory iterator", ec));
367   return *this;
368 }
369 
370 fs::recursive_directory_iterator&
increment(error_code & ec)371 fs::recursive_directory_iterator::increment(error_code& ec)
372 {
373   if (!_M_dirs)
374     {
375       ec = std::make_error_code(errc::invalid_argument);
376       return *this;
377     }
378 
379   const bool follow
380     = is_set(_M_dirs->options, directory_options::follow_directory_symlink);
381   const bool skip_permission_denied
382     = is_set(_M_dirs->options, directory_options::skip_permission_denied);
383 
384   auto& top = _M_dirs->top();
385 
386   if (std::exchange(_M_dirs->pending, true) && top.should_recurse(follow, ec))
387     {
388       _Dir dir = top.open_subdir(skip_permission_denied, !follow, ec);
389       if (ec)
390 	{
391 	  _M_dirs.reset();
392 	  return *this;
393 	}
394       if (dir.dirp)
395 	_M_dirs->push(std::move(dir));
396     }
397 
398   while (!_M_dirs->top().advance(skip_permission_denied, ec) && !ec)
399     {
400       _M_dirs->pop();
401       if (_M_dirs->empty())
402 	{
403 	  _M_dirs.reset();
404 	  return *this;
405 	}
406     }
407 
408   if (ec)
409     _M_dirs.reset();
410 
411   return *this;
412 }
413 
414 void
pop(error_code & ec)415 fs::recursive_directory_iterator::pop(error_code& ec)
416 {
417   if (!_M_dirs)
418     {
419       ec = std::make_error_code(errc::invalid_argument);
420       return;
421     }
422 
423   const bool skip_permission_denied
424     = is_set(_M_dirs->options, directory_options::skip_permission_denied);
425 
426   do {
427     _M_dirs->pop();
428     if (_M_dirs->empty())
429       {
430 	_M_dirs.reset();
431 	ec.clear();
432 	return;
433       }
434   } while (!_M_dirs->top().advance(skip_permission_denied, ec) && !ec);
435 
436   if (ec)
437     _M_dirs.reset();
438 }
439 
440 void
pop()441 fs::recursive_directory_iterator::pop()
442 {
443   [[maybe_unused]] const bool dereferenceable = _M_dirs != nullptr;
444   error_code ec;
445   pop(ec);
446   if (ec)
447     _GLIBCXX_THROW_OR_ABORT(filesystem_error(dereferenceable
448 	  ? "recursive directory iterator cannot pop"
449 	  : "non-dereferenceable recursive directory iterator cannot pop",
450 	  ec));
451 }
452 
453 void
disable_recursion_pending()454 fs::recursive_directory_iterator::disable_recursion_pending() noexcept
455 {
456   _M_dirs->pending = false;
457 }
458 
459 // Used to implement filesystem::remove_all.
460 fs::recursive_directory_iterator&
__erase(error_code * ecptr)461 fs::recursive_directory_iterator::__erase(error_code* ecptr)
462 {
463   error_code ec;
464   if (!_M_dirs)
465     {
466       ec = std::make_error_code(errc::invalid_argument);
467       return *this;
468     }
469 
470   // We never want to skip permission denied when removing files.
471   const bool skip_permission_denied = false;
472   // We never want to follow directory symlinks when removing files.
473   const bool nofollow = true;
474 
475   // Loop until we find something we can remove.
476   while (!ec)
477     {
478       auto& top = _M_dirs->top();
479 
480 #if _GLIBCXX_FILESYSTEM_IS_WINDOWS
481       // _Dir::unlink uses fs::remove which uses std::system_category() for
482       // Windows errror codes, so we can't just check for EPERM and EISDIR.
483       // Use directory_entry::refresh() here to check if we have a directory.
484       // This can be a TOCTTOU race, but we don't have openat or unlinkat to
485       // solve that on Windows, and generally don't support symlinks anyway.
486       if (top.entry._M_type == file_type::none)
487 	top.entry.refresh();
488 #endif
489 
490       if (top.entry._M_type == file_type::directory)
491 	{
492 	  _Dir dir = top.open_subdir(skip_permission_denied, nofollow, ec);
493 	  if (!ec)
494 	    {
495 	      __glibcxx_assert(dir.dirp != nullptr);
496 	      if (dir.advance(skip_permission_denied, ec))
497 		{
498 		  // Non-empty directory, recurse into it.
499 		  _M_dirs->push(std::move(dir));
500 		  continue;
501 		}
502 	      if (!ec)
503 		{
504 		  // Directory is empty so we can remove it.
505 		  if (top.rmdir(ec))
506 		    break; // Success
507 		}
508 	    }
509 	}
510       else if (top.unlink(ec))
511 	break; // Success
512 #if ! _GLIBCXX_FILESYSTEM_IS_WINDOWS
513       else if (top.entry._M_type == file_type::none)
514 	{
515 	  // We did not have a cached type, so it's possible that top.entry
516 	  // is actually a directory, and that's why the unlink above failed.
517 #ifdef EPERM
518 	  // POSIX.1-2017 says unlink on a directory returns EPERM,
519 	  // but LSB allows EISDIR too. Some targets don't even define EPERM.
520 	  if (ec.value() == EPERM || ec.value() == EISDIR)
521 #else
522 	  if (ec.value() == EISDIR)
523 #endif
524 	    {
525 	      // Retry, treating it as a directory.
526 	      top.entry._M_type = file_type::directory;
527 	      ec.clear();
528 	      continue;
529 	    }
530 	}
531 #endif
532     }
533 
534   if (!ec)
535     {
536       // We successfully removed the current entry, so advance to the next one.
537       if (_M_dirs->top().advance(skip_permission_denied, ec))
538 	return *this;
539       else if (!ec)
540 	{
541 	  // Reached the end of the current directory.
542 	  _M_dirs->pop();
543 	  if (_M_dirs->empty())
544 	    _M_dirs.reset();
545 	  return *this;
546 	}
547     }
548 
549   // Reset _M_dirs to empty.
550   auto dirs = std::move(_M_dirs);
551 
552   // Need to report an error
553   if (ecptr)
554     *ecptr = ec;
555   else
556     _GLIBCXX_THROW_OR_ABORT(fs::filesystem_error("cannot remove all",
557 						 dirs->orig,
558 						 dirs->current_path(),
559 						 ec));
560 
561   return *this;
562 }
563