]> git.ipfire.org Git - thirdparty/gcc.git/blame - libstdc++-v3/src/c++17/fs_dir.cc
Update copyright years.
[thirdparty/gcc.git] / libstdc++-v3 / src / c++17 / fs_dir.cc
CommitLineData
641cb5a6
JW
1// Class filesystem::directory_entry etc. -*- C++ -*-
2
a945c346 3// Copyright (C) 2014-2024 Free Software Foundation, Inc.
641cb5a6
JW
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
2fc11587 29#include <bits/largefile-config.h>
641cb5a6 30#include <filesystem>
641cb5a6
JW
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 }
de4db54f 37#include "../filesystem/dir-common.h"
641cb5a6
JW
38
39namespace fs = std::filesystem;
9534a5e6 40namespace posix = std::filesystem::__gnu_posix;
641cb5a6 41
da29d2a3
JW
42template class std::__shared_ptr<fs::_Dir>;
43template class std::__shared_ptr<fs::recursive_directory_iterator::_Dir_stack>;
44
641cb5a6
JW
45struct fs::_Dir : _Dir_base
46{
c8bd4dc8 47 _Dir(const fs::path& p, bool skip_permission_denied, bool nofollow,
ebf61754 48 [[maybe_unused]] bool filename_only, error_code& ec)
19878114 49 : _Dir_base(p.c_str(), skip_permission_denied, nofollow, ec)
641cb5a6 50 {
835b1993 51#if _GLIBCXX_HAVE_DIRFD && _GLIBCXX_HAVE_OPENAT && _GLIBCXX_HAVE_UNLINKAT
ebf61754
JW
52 if (filename_only)
53 return; // Do not store path p when we aren't going to use it.
54#endif
55
641cb5a6
JW
56 if (!ec)
57 path = p;
58 }
59
1ef6085f 60 _Dir(_Dir_base&& d, const path& p) : _Dir_base(std::move(d)), path(p) { }
641cb5a6
JW
61
62 _Dir(_Dir&&) = default;
63
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
67 {
68 if (const auto entp = _Dir_base::advance(skip_permission_denied, ec))
69 {
8d531548
JW
70 auto name = path;
71 name /= entp->d_name;
5ed5c0da
JW
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};
641cb5a6
JW
79 return true;
80 }
81 else if (!ec)
82 {
83 // reached the end
84 entry = {};
85 }
86 return false;
87 }
88
89 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.
93 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
103 bool should_recurse(bool follow_symlink, error_code& ec) const
104 {
105 file_type type = entry._M_type;
5ed5c0da 106 if (type == file_type::none)
641cb5a6
JW
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
19878114
JW
120 // Return a pathname for the current directory entry, as an _At_path.
121 _Dir_base::_At_path
122 current() const noexcept
ebf61754
JW
123 {
124 const fs::path& p = entry.path();
19878114
JW
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 }
ebf61754 131#endif
19878114 132 return p.c_str();
ebf61754
JW
133 }
134
135 // Create a new _Dir for the directory this->entry.path().
136 _Dir
137 open_subdir(bool skip_permission_denied, bool nofollow,
138 error_code& ec) const noexcept
139 {
19878114 140 _Dir_base d(current(), skip_permission_denied, nofollow, ec);
ebf61754
JW
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();
1ef6085f 143 return _Dir(std::move(d), p);
ebf61754
JW
144 }
145
146 bool
147 do_unlink(bool is_directory, error_code& ec) const noexcept
148 {
149#if _GLIBCXX_HAVE_UNLINKAT
19878114
JW
150 const auto atp = current();
151 if (::unlinkat(atp.dir(), atp.path_at_dir(),
152 is_directory ? AT_REMOVEDIR : 0) == -1)
ebf61754
JW
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
169 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
174 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.
641cb5a6
JW
178 directory_entry entry;
179};
180
181namespace
182{
183 template<typename Bitmask>
184 inline bool
185 is_set(Bitmask obj, Bitmask bits)
186 {
187 return (obj & bits) != Bitmask::none;
188 }
ebf61754
JW
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.
196constexpr 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.
202constexpr fs::directory_options __directory_iterator_filename_only{128};
641cb5a6
JW
203}
204
205fs::directory_iterator::
206directory_iterator(const path& p, directory_options options, error_code* ecptr)
207{
c8bd4dc8 208 // Do not report an error for permission denied errors.
641cb5a6
JW
209 const bool skip_permission_denied
210 = is_set(options, directory_options::skip_permission_denied);
ebf61754
JW
211 // Do not allow opening a symlink.
212 const bool nofollow = is_set(options, __directory_iterator_nofollow);
641cb5a6
JW
213
214 error_code ec;
ebf61754 215 _Dir dir(p, skip_permission_denied, nofollow, /*filename only*/false, ec);
641cb5a6
JW
216
217 if (dir.dirp)
218 {
da29d2a3 219 auto sp = std::__make_shared<fs::_Dir>(std::move(dir));
641cb5a6
JW
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
230const fs::directory_entry&
044b0434 231fs::directory_iterator::operator*() const noexcept
641cb5a6 232{
641cb5a6
JW
233 return _M_dir->entry;
234}
235
236fs::directory_iterator&
237fs::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
248fs::directory_iterator&
29453a9f 249fs::directory_iterator::increment(error_code& ec)
641cb5a6
JW
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
261struct fs::recursive_directory_iterator::_Dir_stack : std::stack<_Dir>
262{
ebf61754 263 _Dir_stack(directory_options opts, _Dir&& dir)
c7dde4a9
JW
264 : options(opts), pending(true)
265 {
ebf61754 266 this->push(std::move(dir));
c7dde4a9
JW
267 }
268
ebf61754 269 path::string_type orig;
c7dde4a9
JW
270 const directory_options options;
271 bool pending;
272
641cb5a6 273 void clear() { c.clear(); }
ebf61754
JW
274
275 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 }
641cb5a6
JW
289};
290
291fs::recursive_directory_iterator::
292recursive_directory_iterator(const path& p, directory_options options,
293 error_code* ecptr)
641cb5a6 294{
ebf61754
JW
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)
641cb5a6 308 {
ebf61754
JW
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))
641cb5a6 312 {
ebf61754
JW
313 _M_dirs.swap(sp);
314 if (filename_only) // Need to save original path for error reporting.
315 _M_dirs->orig = p.native();
641cb5a6 316 }
641cb5a6 317 }
ebf61754
JW
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));
641cb5a6
JW
323}
324
325fs::recursive_directory_iterator::~recursive_directory_iterator() = default;
326
c7dde4a9
JW
327fs::directory_options
328fs::recursive_directory_iterator::options() const noexcept
329{
330 return _M_dirs->options;
331}
332
641cb5a6 333int
c7dde4a9 334fs::recursive_directory_iterator::depth() const noexcept
641cb5a6
JW
335{
336 return int(_M_dirs->size()) - 1;
337}
338
c7dde4a9
JW
339bool
340fs::recursive_directory_iterator::recursion_pending() const noexcept
341{
342 return _M_dirs->pending;
343}
344
641cb5a6 345const fs::directory_entry&
c7dde4a9 346fs::recursive_directory_iterator::operator*() const noexcept
641cb5a6
JW
347{
348 return _M_dirs->top().entry;
349}
350
351fs::recursive_directory_iterator&
352fs::recursive_directory_iterator::
353operator=(const recursive_directory_iterator& other) noexcept = default;
354
355fs::recursive_directory_iterator&
356fs::recursive_directory_iterator::
357operator=(recursive_directory_iterator&& other) noexcept = default;
358
359fs::recursive_directory_iterator&
360fs::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
370fs::recursive_directory_iterator&
29453a9f 371fs::recursive_directory_iterator::increment(error_code& ec)
641cb5a6
JW
372{
373 if (!_M_dirs)
374 {
375 ec = std::make_error_code(errc::invalid_argument);
376 return *this;
377 }
378
379 const bool follow
c7dde4a9 380 = is_set(_M_dirs->options, directory_options::follow_directory_symlink);
641cb5a6 381 const bool skip_permission_denied
c7dde4a9 382 = is_set(_M_dirs->options, directory_options::skip_permission_denied);
641cb5a6
JW
383
384 auto& top = _M_dirs->top();
385
c7dde4a9 386 if (std::exchange(_M_dirs->pending, true) && top.should_recurse(follow, ec))
641cb5a6 387 {
ebf61754 388 _Dir dir = top.open_subdir(skip_permission_denied, !follow, ec);
641cb5a6
JW
389 if (ec)
390 {
391 _M_dirs.reset();
392 return *this;
393 }
394 if (dir.dirp)
ebf61754 395 _M_dirs->push(std::move(dir));
641cb5a6
JW
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 }
ec09a533
JW
407
408 if (ec)
409 _M_dirs.reset();
410
641cb5a6
JW
411 return *this;
412}
413
414void
415fs::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
c7dde4a9 424 = is_set(_M_dirs->options, directory_options::skip_permission_denied);
641cb5a6
JW
425
426 do {
427 _M_dirs->pop();
428 if (_M_dirs->empty())
429 {
430 _M_dirs.reset();
431 ec.clear();
432 return;
433 }
ec09a533
JW
434 } while (!_M_dirs->top().advance(skip_permission_denied, ec) && !ec);
435
436 if (ec)
437 _M_dirs.reset();
641cb5a6
JW
438}
439
440void
441fs::recursive_directory_iterator::pop()
442{
2905e1af 443 [[maybe_unused]] const bool dereferenceable = _M_dirs != nullptr;
641cb5a6
JW
444 error_code ec;
445 pop(ec);
446 if (ec)
ec09a533 447 _GLIBCXX_THROW_OR_ABORT(filesystem_error(dereferenceable
641cb5a6
JW
448 ? "recursive directory iterator cannot pop"
449 : "non-dereferenceable recursive directory iterator cannot pop",
450 ec));
451}
c7dde4a9
JW
452
453void
454fs::recursive_directory_iterator::disable_recursion_pending() noexcept
455{
456 _M_dirs->pending = false;
457}
ebf61754
JW
458
459// Used to implement filesystem::remove_all.
460fs::recursive_directory_iterator&
461fs::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
5750952b
JW
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
ebf61754
JW
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
5750952b 512#if ! _GLIBCXX_FILESYSTEM_IS_WINDOWS
ebf61754
JW
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
5750952b 518 // POSIX.1-2017 says unlink on a directory returns EPERM,
ebf61754
JW
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 }
5750952b 531#endif
ebf61754
JW
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}