1 // Class filesystem::directory_entry etc. -*- C++ -*-
3 // Copyright (C) 2014-2024 Free Software Foundation, Inc.
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)
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.
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.
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/>.
25 #ifndef _GLIBCXX_USE_CXX11_ABI
26 # define _GLIBCXX_USE_CXX11_ABI 1
29 #include <bits/largefile-config.h>
35 #define _GLIBCXX_BEGIN_NAMESPACE_FILESYSTEM namespace filesystem {
36 #define _GLIBCXX_END_NAMESPACE_FILESYSTEM }
37 #include "../filesystem/dir-common.h"
39 namespace fs
= std::filesystem
;
40 namespace posix
= std::filesystem::__gnu_posix
;
42 template class std::__shared_ptr
<fs::_Dir
>;
43 template class std::__shared_ptr
<fs::recursive_directory_iterator::_Dir_stack
>;
45 struct fs::_Dir
: _Dir_base
47 _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
)
51 #if _GLIBCXX_HAVE_DIRFD && _GLIBCXX_HAVE_OPENAT && _GLIBCXX_HAVE_UNLINKAT
53 return; // Do not store path p when we aren't going to use it.
60 _Dir(_Dir_base
&& d
, const path
& p
) : _Dir_base(std::move(d
)), path(p
) { }
62 _Dir(_Dir
&&) = default;
64 // Returns false when the end of the directory entries is reached.
65 // Reports errors by setting ec.
66 bool advance(bool skip_permission_denied
, error_code
& ec
) noexcept
68 if (const auto entp
= _Dir_base::advance(skip_permission_denied
, ec
))
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
);
78 entry
= fs::directory_entry
{std::move(name
), type
};
89 bool advance(error_code
& ec
) noexcept
{ return advance(false, ec
); }
91 // Returns false when the end of the directory entries is reached.
92 // Reports errors by throwing.
93 bool advance(bool skip_permission_denied
= false)
96 const bool ok
= advance(skip_permission_denied
, ec
);
98 _GLIBCXX_THROW_OR_ABORT(filesystem_error(
99 "directory iterator cannot advance", ec
));
103 bool should_recurse(bool follow_symlink
, error_code
& ec
) const
105 file_type type
= entry
._M_type
;
106 if (type
== file_type::none
)
108 type
= entry
.symlink_status(ec
).type();
113 if (type
== file_type::directory
)
115 if (type
== file_type::symlink
)
116 return follow_symlink
&& is_directory(entry
.status(ec
));
120 // Return a pathname for the current directory entry, as an _At_path.
122 current() const noexcept
124 const fs::path
& p
= entry
.path();
125 #if _GLIBCXX_HAVE_DIRFD
126 if (!p
.empty()) [[__likely__
]]
128 auto len
= std::prev(p
.end())->native().size();
129 return {::dirfd(this->dirp
), p
.c_str(), p
.native().size() - len
};
135 // Create a new _Dir for the directory this->entry.path().
137 open_subdir(bool skip_permission_denied
, bool nofollow
,
138 error_code
& ec
) const noexcept
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
);
147 do_unlink(bool is_directory
, error_code
& ec
) const noexcept
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)
154 ec
.assign(errno
, std::generic_category());
163 return fs::remove(entry
.path(), ec
);
167 // Remove the non-directory that this->entry refers to.
169 unlink(error_code
& ec
) const noexcept
170 { return do_unlink(/* is_directory*/ false, ec
); }
172 // Remove the directory that this->entry refers to.
174 rmdir(error_code
& ec
) const noexcept
175 { return do_unlink(/* is_directory*/ true, ec
); }
177 fs::path path
; // Empty if only using unlinkat with file descr.
178 directory_entry entry
;
183 template<typename Bitmask
>
185 is_set(Bitmask obj
, Bitmask bits
)
187 return (obj
& bits
) != Bitmask::none
;
190 // Non-standard directory option flags, currently only for internal use:
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};
205 fs::directory_iterator::
206 directory_iterator(const path
& p
, directory_options options
, error_code
* ecptr
)
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
);
215 _Dir
dir(p
, skip_permission_denied
, nofollow
, /*filename only*/false, ec
);
219 auto sp
= std::__make_shared
<fs::_Dir
>(std::move(dir
));
220 if (sp
->advance(skip_permission_denied
, ec
))
226 _GLIBCXX_THROW_OR_ABORT(fs::filesystem_error(
227 "directory iterator cannot open directory", p
, ec
));
230 const fs::directory_entry
&
231 fs::directory_iterator::operator*() const noexcept
233 return _M_dir
->entry
;
236 fs::directory_iterator
&
237 fs::directory_iterator::operator++()
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())
248 fs::directory_iterator
&
249 fs::directory_iterator::increment(error_code
& ec
)
253 ec
= std::make_error_code(errc::invalid_argument
);
256 if (!_M_dir
->advance(ec
))
261 struct fs::recursive_directory_iterator::_Dir_stack
: std::stack
<_Dir
>
263 _Dir_stack(directory_options opts
, _Dir
&& dir
)
264 : options(opts
), pending(true)
266 this->push(std::move(dir
));
269 path::string_type orig
;
270 const directory_options options
;
273 void clear() { c
.clear(); }
275 path
current_path() const
278 if (top().path
.empty())
280 // Reconstruct path that failed from dir stack.
282 for (auto& d
: this->c
)
286 p
= top().entry
.path();
291 fs::recursive_directory_iterator::
292 recursive_directory_iterator(const path
& p
, directory_options options
,
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
);
305 _Dir
dir(p
, skip_permission_denied
, nofollow
, filename_only
, ec
);
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
))
314 if (filename_only
) // Need to save original path for error reporting.
315 _M_dirs
->orig
= p
.native();
321 _GLIBCXX_THROW_OR_ABORT(fs::filesystem_error(
322 "recursive directory iterator cannot open directory", p
, ec
));
325 fs::recursive_directory_iterator::~recursive_directory_iterator() = default;
327 fs::directory_options
328 fs::recursive_directory_iterator::options() const noexcept
330 return _M_dirs
->options
;
334 fs::recursive_directory_iterator::depth() const noexcept
336 return int(_M_dirs
->size()) - 1;
340 fs::recursive_directory_iterator::recursion_pending() const noexcept
342 return _M_dirs
->pending
;
345 const fs::directory_entry
&
346 fs::recursive_directory_iterator::operator*() const noexcept
348 return _M_dirs
->top().entry
;
351 fs::recursive_directory_iterator
&
352 fs::recursive_directory_iterator::
353 operator=(const recursive_directory_iterator
& other
) noexcept
= default;
355 fs::recursive_directory_iterator
&
356 fs::recursive_directory_iterator::
357 operator=(recursive_directory_iterator
&& other
) noexcept
= default;
359 fs::recursive_directory_iterator
&
360 fs::recursive_directory_iterator::operator++()
365 _GLIBCXX_THROW_OR_ABORT(filesystem_error(
366 "cannot increment recursive directory iterator", ec
));
370 fs::recursive_directory_iterator
&
371 fs::recursive_directory_iterator::increment(error_code
& ec
)
375 ec
= std::make_error_code(errc::invalid_argument
);
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
);
384 auto& top
= _M_dirs
->top();
386 if (std::exchange(_M_dirs
->pending
, true) && top
.should_recurse(follow
, ec
))
388 _Dir dir
= top
.open_subdir(skip_permission_denied
, !follow
, ec
);
395 _M_dirs
->push(std::move(dir
));
398 while (!_M_dirs
->top().advance(skip_permission_denied
, ec
) && !ec
)
401 if (_M_dirs
->empty())
415 fs::recursive_directory_iterator::pop(error_code
& ec
)
419 ec
= std::make_error_code(errc::invalid_argument
);
423 const bool skip_permission_denied
424 = is_set(_M_dirs
->options
, directory_options::skip_permission_denied
);
428 if (_M_dirs
->empty())
434 } while (!_M_dirs
->top().advance(skip_permission_denied
, ec
) && !ec
);
441 fs::recursive_directory_iterator::pop()
443 [[maybe_unused
]] const bool dereferenceable
= _M_dirs
!= nullptr;
447 _GLIBCXX_THROW_OR_ABORT(filesystem_error(dereferenceable
448 ? "recursive directory iterator cannot pop"
449 : "non-dereferenceable recursive directory iterator cannot pop",
454 fs::recursive_directory_iterator::disable_recursion_pending() noexcept
456 _M_dirs
->pending
= false;
459 // Used to implement filesystem::remove_all.
460 fs::recursive_directory_iterator
&
461 fs::recursive_directory_iterator::__erase(error_code
* ecptr
)
466 ec
= std::make_error_code(errc::invalid_argument
);
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;
475 // Loop until we find something we can remove.
478 auto& top
= _M_dirs
->top();
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
)
490 if (top
.entry
._M_type
== file_type::directory
)
492 _Dir dir
= top
.open_subdir(skip_permission_denied
, nofollow
, ec
);
495 __glibcxx_assert(dir
.dirp
!= nullptr);
496 if (dir
.advance(skip_permission_denied
, ec
))
498 // Non-empty directory, recurse into it.
499 _M_dirs
->push(std::move(dir
));
504 // Directory is empty so we can remove it.
510 else if (top
.unlink(ec
))
512 #if ! _GLIBCXX_FILESYSTEM_IS_WINDOWS
513 else if (top
.entry
._M_type
== file_type::none
)
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.
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
)
522 if (ec
.value() == EISDIR
)
525 // Retry, treating it as a directory.
526 top
.entry
._M_type
= file_type::directory
;
536 // We successfully removed the current entry, so advance to the next one.
537 if (_M_dirs
->top().advance(skip_permission_denied
, ec
))
541 // Reached the end of the current directory.
543 if (_M_dirs
->empty())
549 // Reset _M_dirs to empty.
550 auto dirs
= std::move(_M_dirs
);
552 // Need to report an error
556 _GLIBCXX_THROW_OR_ABORT(fs::filesystem_error("cannot remove all",
558 dirs
->current_path(),