xref: /netbsd-src/external/gpl3/gcc/dist/libstdc++-v3/src/filesystem/dir.cc (revision 3117ece4fc4a4ca4489ba793710b60b0d26bab6c)
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 #ifndef _GNU_SOURCE
29 // Cygwin needs this for secure_getenv
30 # define _GNU_SOURCE 1
31 #endif
32 
33 #include <bits/largefile-config.h>
34 #include <experimental/filesystem>
35 
36 #ifndef _GLIBCXX_HAVE_DIRENT_H
37 # error "the <dirent.h> header is needed to build the Filesystem TS"
38 #endif
39 
40 #include <utility>
41 #include <stack>
42 #include <string.h>
43 #include <errno.h>
44 #define _GLIBCXX_BEGIN_NAMESPACE_FILESYSTEM \
45   namespace experimental { namespace filesystem {
46 #define _GLIBCXX_END_NAMESPACE_FILESYSTEM } }
47 #include "dir-common.h"
48 
49 namespace fs = std::experimental::filesystem;
50 namespace posix = std::filesystem::__gnu_posix;
51 
52 struct fs::_Dir : std::filesystem::_Dir_base
53 {
54   _Dir(const fs::path& p, bool skip_permission_denied, bool nofollow,
55        error_code& ec)
56   : _Dir_base(p.c_str(), skip_permission_denied, nofollow, ec)
57   {
58     if (!ec)
59       path = p;
60   }
61 
62   _Dir(_Dir_base&& d, const path& p) : _Dir_base(std::move(d)), path(p) { }
63 
64   _Dir(_Dir&&) = default;
65 
66   // Returns false when the end of the directory entries is reached.
67   // Reports errors by setting ec.
68   bool advance(bool skip_permission_denied, error_code& ec) noexcept
69   {
70     if (const auto entp = _Dir_base::advance(skip_permission_denied, ec))
71       {
72 	entry = fs::directory_entry{path / entp->d_name};
73 	type = get_file_type(*entp);
74 	return true;
75       }
76     else if (!ec)
77       {
78 	// reached the end
79 	entry = {};
80 	type = file_type::none;
81       }
82     return false;
83   }
84 
85   bool advance(error_code& ec) noexcept { return advance(false, ec); }
86 
87   // Returns false when the end of the directory entries is reached.
88   // Reports errors by throwing.
89   bool advance(bool skip_permission_denied = false)
90   {
91     error_code ec;
92     const bool ok = advance(skip_permission_denied, ec);
93     if (ec)
94       _GLIBCXX_THROW_OR_ABORT(filesystem_error(
95 	      "directory iterator cannot advance", ec));
96     return ok;
97   }
98 
99   bool should_recurse(bool follow_symlink, error_code& ec) const
100   {
101     file_type type = this->type;
102     if (type == file_type::none || type == file_type::unknown)
103     {
104       type = entry.symlink_status(ec).type();
105       if (ec)
106 	return false;
107     }
108 
109     if (type == file_type::directory)
110       return true;
111     if (type == file_type::symlink)
112       return follow_symlink && is_directory(entry.status(ec));
113     return false;
114   }
115 
116   // Return a pathname for the current directory entry, as an _At_path.
117   _Dir_base::_At_path
118   current() const noexcept
119   {
120     const fs::path& p = entry.path();
121 #if _GLIBCXX_HAVE_DIRFD
122     auto len = std::prev(p.end())->native().size();
123     return {::dirfd(this->dirp), p.c_str(), p.native().size() - len};
124 #else
125     return p.c_str();
126 #endif
127   }
128 
129   // Create a new _Dir for the directory this->entry.path().
130   _Dir
131   open_subdir(bool skip_permission_denied, bool nofollow,
132 	      error_code& ec) noexcept
133   {
134     _Dir_base d(current(), skip_permission_denied, nofollow, ec);
135     return _Dir(std::move(d), entry.path());
136   }
137 
138   fs::path		path;
139   directory_entry	entry;
140   file_type		type = file_type::none;
141 };
142 
143 namespace
144 {
145   template<typename Bitmask>
146     inline bool
147     is_set(Bitmask obj, Bitmask bits)
148     {
149       return (obj & bits) != Bitmask::none;
150     }
151 }
152 
153 fs::directory_iterator::
154 directory_iterator(const path& p, directory_options options, error_code* ecptr)
155 {
156   // Do not report an error for permission denied errors.
157   const bool skip_permission_denied
158     = is_set(options, directory_options::skip_permission_denied);
159 
160   error_code ec;
161   _Dir dir(p, skip_permission_denied, /*nofollow*/false, ec);
162 
163   if (dir.dirp)
164     {
165       auto sp = std::make_shared<fs::_Dir>(std::move(dir));
166       if (sp->advance(skip_permission_denied, ec))
167 	_M_dir.swap(sp);
168     }
169   if (ecptr)
170     *ecptr = ec;
171   else if (ec)
172     _GLIBCXX_THROW_OR_ABORT(fs::filesystem_error(
173 	  "directory iterator cannot open directory", p, ec));
174 }
175 
176 const fs::directory_entry&
177 fs::directory_iterator::operator*() const
178 {
179   if (!_M_dir)
180     _GLIBCXX_THROW_OR_ABORT(filesystem_error(
181 	  "non-dereferenceable directory iterator",
182 	  std::make_error_code(errc::invalid_argument)));
183   return _M_dir->entry;
184 }
185 
186 fs::directory_iterator&
187 fs::directory_iterator::operator++()
188 {
189   if (!_M_dir)
190     _GLIBCXX_THROW_OR_ABORT(filesystem_error(
191 	  "cannot advance non-dereferenceable directory iterator",
192 	  std::make_error_code(errc::invalid_argument)));
193   if (!_M_dir->advance())
194     _M_dir.reset();
195   return *this;
196 }
197 
198 fs::directory_iterator&
199 fs::directory_iterator::increment(error_code& ec) noexcept
200 {
201   if (!_M_dir)
202     {
203       ec = std::make_error_code(errc::invalid_argument);
204       return *this;
205     }
206   if (!_M_dir->advance(ec))
207     _M_dir.reset();
208   return *this;
209 }
210 
211 struct fs::recursive_directory_iterator::_Dir_stack : std::stack<_Dir>
212 {
213   _Dir_stack(_Dir&& dir)
214   {
215     this->push(std::move(dir));
216   }
217 
218   void clear() { c.clear(); }
219 };
220 
221 fs::recursive_directory_iterator::
222 recursive_directory_iterator(const path& p, directory_options options,
223                              error_code* ecptr)
224 : _M_options(options), _M_pending(true)
225 {
226   // Do not report an error for permission denied errors.
227   const bool skip_permission_denied
228     = is_set(options, directory_options::skip_permission_denied);
229 
230   error_code ec;
231   _Dir dir(p, skip_permission_denied, /*nofollow*/false, ec);
232 
233   if (dir.dirp)
234     {
235       auto sp = std::__make_shared<_Dir_stack>(std::move(dir));
236       if (ecptr ? sp->top().advance(skip_permission_denied, *ecptr)
237 		: sp->top().advance(skip_permission_denied))
238 	{
239 	  _M_dirs.swap(sp);
240 	}
241     }
242   else if (ecptr)
243     *ecptr = ec;
244   else if (ec)
245     _GLIBCXX_THROW_OR_ABORT(fs::filesystem_error(
246 	  "recursive directory iterator cannot open directory", p, ec));
247 }
248 
249 fs::recursive_directory_iterator::~recursive_directory_iterator() = default;
250 
251 int
252 fs::recursive_directory_iterator::depth() const
253 {
254   return int(_M_dirs->size()) - 1;
255 }
256 
257 const fs::directory_entry&
258 fs::recursive_directory_iterator::operator*() const
259 {
260   return _M_dirs->top().entry;
261 }
262 
263 fs::recursive_directory_iterator&
264 fs::recursive_directory_iterator::
265 operator=(const recursive_directory_iterator& other) noexcept = default;
266 
267 fs::recursive_directory_iterator&
268 fs::recursive_directory_iterator::
269 operator=(recursive_directory_iterator&& other) noexcept = default;
270 
271 fs::recursive_directory_iterator&
272 fs::recursive_directory_iterator::operator++()
273 {
274   error_code ec;
275   increment(ec);
276   if (ec.value())
277     _GLIBCXX_THROW_OR_ABORT(filesystem_error(
278 	  "cannot increment recursive directory iterator", ec));
279   return *this;
280 }
281 
282 fs::recursive_directory_iterator&
283 fs::recursive_directory_iterator::increment(error_code& ec) noexcept
284 {
285   if (!_M_dirs)
286     {
287       ec = std::make_error_code(errc::invalid_argument);
288       return *this;
289     }
290 
291   const bool follow
292     = is_set(_M_options, directory_options::follow_directory_symlink);
293   const bool skip_permission_denied
294     = is_set(_M_options, directory_options::skip_permission_denied);
295 
296   auto& top = _M_dirs->top();
297 
298   if (std::exchange(_M_pending, true) && top.should_recurse(follow, ec))
299     {
300       _Dir dir = top.open_subdir(skip_permission_denied, !follow, ec);
301       if (ec)
302 	{
303 	  _M_dirs.reset();
304 	  return *this;
305 	}
306       if (dir.dirp)
307 	  _M_dirs->push(std::move(dir));
308     }
309 
310   while (!_M_dirs->top().advance(skip_permission_denied, ec) && !ec)
311     {
312       _M_dirs->pop();
313       if (_M_dirs->empty())
314 	{
315 	  _M_dirs.reset();
316 	  return *this;
317 	}
318     }
319 
320   if (ec)
321     _M_dirs.reset();
322 
323   return *this;
324 }
325 
326 void
327 fs::recursive_directory_iterator::pop(error_code& ec)
328 {
329   if (!_M_dirs)
330     {
331       ec = std::make_error_code(errc::invalid_argument);
332       return;
333     }
334 
335   const bool skip_permission_denied
336     = is_set(_M_options, directory_options::skip_permission_denied);
337 
338   do {
339     _M_dirs->pop();
340     if (_M_dirs->empty())
341       {
342 	_M_dirs.reset();
343 	ec.clear();
344 	return;
345       }
346   } while (!_M_dirs->top().advance(skip_permission_denied, ec) && !ec);
347 
348   if (ec)
349     _M_dirs.reset();
350 }
351 
352 void
353 fs::recursive_directory_iterator::pop()
354 {
355   [[maybe_unused]] const bool dereferenceable = _M_dirs != nullptr;
356   error_code ec;
357   pop(ec);
358   if (ec)
359     _GLIBCXX_THROW_OR_ABORT(filesystem_error(dereferenceable
360 	  ? "recursive directory iterator cannot pop"
361 	  : "non-dereferenceable recursive directory iterator cannot pop",
362 	  ec));
363 }
364