]> git.ipfire.org Git - thirdparty/gcc.git/blob - libstdc++-v3/src/c++17/fs_dir.cc
Update copyright years.
[thirdparty/gcc.git] / libstdc++-v3 / src / c++17 / fs_dir.cc
1 // Class filesystem::directory_entry etc. -*- C++ -*-
2
3 // Copyright (C) 2014-2023 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 {
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)
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
60 _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.
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 {
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
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;
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
122 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
137 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
147 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
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.
178 directory_entry entry;
179 };
180
181 namespace
182 {
183 template<typename Bitmask>
184 inline bool
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::
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&
231 fs::directory_iterator::operator*() const noexcept
232 {
233 return _M_dir->entry;
234 }
235
236 fs::directory_iterator&
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&
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 {
263 _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
273 void clear() { c.clear(); }
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 }
289 };
290
291 fs::recursive_directory_iterator::
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
328 fs::recursive_directory_iterator::options() const noexcept
329 {
330 return _M_dirs->options;
331 }
332
333 int
334 fs::recursive_directory_iterator::depth() const noexcept
335 {
336 return int(_M_dirs->size()) - 1;
337 }
338
339 bool
340 fs::recursive_directory_iterator::recursion_pending() const noexcept
341 {
342 return _M_dirs->pending;
343 }
344
345 const fs::directory_entry&
346 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&
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&
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
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
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
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&
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 }