xref: /netbsd-src/external/gpl3/gcc.old/dist/libstdc++-v3/src/filesystem/dir.cc (revision 181254a7b1bdde6873432bffef2d2decc4b5c22f)
1 // Class filesystem::directory_entry etc. -*- C++ -*-
2 
3 // Copyright (C) 2014-2018 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 <experimental/filesystem>
31 #include <utility>
32 #include <stack>
33 #include <string.h>
34 #include <errno.h>
35 #define _GLIBCXX_BEGIN_NAMESPACE_FILESYSTEM \
36   namespace experimental { namespace filesystem {
37 #define _GLIBCXX_END_NAMESPACE_FILESYSTEM } }
38 #include "dir-common.h"
39 
40 namespace fs = std::experimental::filesystem;
41 
42 struct fs::_Dir : std::filesystem::_Dir_base
43 {
44   _Dir(const fs::path& p, bool skip_permission_denied, error_code& ec)
45   : _Dir_base(p.c_str(), skip_permission_denied, ec)
46   {
47     if (!ec)
48       path = p;
49   }
50 
51   _Dir(DIR* dirp, const path& p) : _Dir_base(dirp), path(p) { }
52 
53   _Dir(_Dir&&) = default;
54 
55   // Returns false when the end of the directory entries is reached.
56   // Reports errors by setting ec.
57   bool advance(bool skip_permission_denied, error_code& ec) noexcept
58   {
59     if (const auto entp = _Dir_base::advance(skip_permission_denied, ec))
60       {
61 	entry = fs::directory_entry{path / entp->d_name};
62 	type = get_file_type(*entp);
63 	return true;
64       }
65     else if (!ec)
66       {
67 	// reached the end
68 	entry = {};
69 	type = file_type::none;
70       }
71     return false;
72   }
73 
74   bool advance(error_code& ec) noexcept { return advance(false, ec); }
75 
76   // Returns false when the end of the directory entries is reached.
77   // Reports errors by throwing.
78   bool advance(bool skip_permission_denied = false)
79   {
80     error_code ec;
81     const bool ok = advance(skip_permission_denied, ec);
82     if (ec)
83       _GLIBCXX_THROW_OR_ABORT(filesystem_error(
84 	      "directory iterator cannot advance", ec));
85     return ok;
86   }
87 
88   bool should_recurse(bool follow_symlink, error_code& ec) const
89   {
90     file_type type = this->type;
91     if (type == file_type::none || type == file_type::unknown)
92     {
93       type = entry.symlink_status(ec).type();
94       if (ec)
95 	return false;
96     }
97 
98     if (type == file_type::directory)
99       return true;
100     if (type == file_type::symlink)
101       return follow_symlink && is_directory(entry.status(ec));
102     return false;
103   }
104 
105   fs::path		path;
106   directory_entry	entry;
107   file_type		type = file_type::none;
108 };
109 
110 namespace
111 {
112   template<typename Bitmask>
113     inline bool
114     is_set(Bitmask obj, Bitmask bits)
115     {
116       return (obj & bits) != Bitmask::none;
117     }
118 }
119 
120 fs::directory_iterator::
121 directory_iterator(const path& p, directory_options options, error_code* ecptr)
122 {
123   const bool skip_permission_denied
124     = is_set(options, directory_options::skip_permission_denied);
125 
126   error_code ec;
127   _Dir dir(p, skip_permission_denied, ec);
128 
129   if (dir.dirp)
130     {
131       auto sp = std::make_shared<fs::_Dir>(std::move(dir));
132       if (sp->advance(skip_permission_denied, ec))
133 	_M_dir.swap(sp);
134     }
135   if (ecptr)
136     *ecptr = ec;
137   else if (ec)
138     _GLIBCXX_THROW_OR_ABORT(fs::filesystem_error(
139 	  "directory iterator cannot open directory", p, ec));
140 }
141 
142 const fs::directory_entry&
143 fs::directory_iterator::operator*() const
144 {
145   if (!_M_dir)
146     _GLIBCXX_THROW_OR_ABORT(filesystem_error(
147 	  "non-dereferenceable directory iterator",
148 	  std::make_error_code(errc::invalid_argument)));
149   return _M_dir->entry;
150 }
151 
152 fs::directory_iterator&
153 fs::directory_iterator::operator++()
154 {
155   if (!_M_dir)
156     _GLIBCXX_THROW_OR_ABORT(filesystem_error(
157 	  "cannot advance non-dereferenceable directory iterator",
158 	  std::make_error_code(errc::invalid_argument)));
159   if (!_M_dir->advance())
160     _M_dir.reset();
161   return *this;
162 }
163 
164 fs::directory_iterator&
165 fs::directory_iterator::increment(error_code& ec) noexcept
166 {
167   if (!_M_dir)
168     {
169       ec = std::make_error_code(errc::invalid_argument);
170       return *this;
171     }
172   if (!_M_dir->advance(ec))
173     _M_dir.reset();
174   return *this;
175 }
176 
177 struct fs::recursive_directory_iterator::_Dir_stack : std::stack<_Dir>
178 {
179   void clear() { c.clear(); }
180 };
181 
182 fs::recursive_directory_iterator::
183 recursive_directory_iterator(const path& p, directory_options options,
184                              error_code* ec)
185 : _M_options(options), _M_pending(true)
186 {
187   if (ec)
188     ec->clear();
189   if (DIR* dirp = ::opendir(p.c_str()))
190     {
191       auto sp = std::make_shared<_Dir_stack>();
192       sp->push(_Dir{ dirp, p });
193       if (sp->top().advance(ec))
194 	_M_dirs.swap(sp);
195     }
196   else
197     {
198       const int err = errno;
199       if (err == EACCES
200 	  && is_set(options, fs::directory_options::skip_permission_denied))
201 	return;
202 
203       if (!ec)
204 	_GLIBCXX_THROW_OR_ABORT(filesystem_error(
205 	      "recursive directory iterator cannot open directory", p,
206 	      std::error_code(err, std::generic_category())));
207 
208       ec->assign(err, std::generic_category());
209     }
210 }
211 
212 fs::recursive_directory_iterator::~recursive_directory_iterator() = default;
213 
214 int
215 fs::recursive_directory_iterator::depth() const
216 {
217   return int(_M_dirs->size()) - 1;
218 }
219 
220 const fs::directory_entry&
221 fs::recursive_directory_iterator::operator*() const
222 {
223   return _M_dirs->top().entry;
224 }
225 
226 fs::recursive_directory_iterator&
227 fs::recursive_directory_iterator::
228 operator=(const recursive_directory_iterator& other) noexcept = default;
229 
230 fs::recursive_directory_iterator&
231 fs::recursive_directory_iterator::
232 operator=(recursive_directory_iterator&& other) noexcept = default;
233 
234 fs::recursive_directory_iterator&
235 fs::recursive_directory_iterator::operator++()
236 {
237   error_code ec;
238   increment(ec);
239   if (ec.value())
240     _GLIBCXX_THROW_OR_ABORT(filesystem_error(
241 	  "cannot increment recursive directory iterator", ec));
242   return *this;
243 }
244 
245 fs::recursive_directory_iterator&
246 fs::recursive_directory_iterator::increment(error_code& ec) noexcept
247 {
248   if (!_M_dirs)
249     {
250       ec = std::make_error_code(errc::invalid_argument);
251       return *this;
252     }
253 
254   const bool follow
255     = is_set(_M_options, directory_options::follow_directory_symlink);
256   const bool skip_permission_denied
257     = is_set(_M_options, directory_options::skip_permission_denied);
258 
259   auto& top = _M_dirs->top();
260 
261   if (std::exchange(_M_pending, true) && top.should_recurse(follow, ec))
262     {
263       _Dir dir(top.entry.path(), skip_permission_denied, ec);
264       if (ec)
265 	{
266 	  _M_dirs.reset();
267 	  return *this;
268 	}
269       if (dir.dirp)
270 	  _M_dirs->push(std::move(dir));
271     }
272 
273   while (!_M_dirs->top().advance(skip_permission_denied, ec) && !ec)
274     {
275       _M_dirs->pop();
276       if (_M_dirs->empty())
277 	{
278 	  _M_dirs.reset();
279 	  return *this;
280 	}
281     }
282   return *this;
283 }
284 
285 void
286 fs::recursive_directory_iterator::pop(error_code& ec)
287 {
288   if (!_M_dirs)
289     {
290       ec = std::make_error_code(errc::invalid_argument);
291       return;
292     }
293 
294   const bool skip_permission_denied
295     = is_set(_M_options, directory_options::skip_permission_denied);
296 
297   do {
298     _M_dirs->pop();
299     if (_M_dirs->empty())
300       {
301 	_M_dirs.reset();
302 	ec.clear();
303 	return;
304       }
305   } while (!_M_dirs->top().advance(skip_permission_denied, ec));
306 }
307 
308 void
309 fs::recursive_directory_iterator::pop()
310 {
311   error_code ec;
312   pop(ec);
313   if (ec)
314     _GLIBCXX_THROW_OR_ABORT(filesystem_error(_M_dirs
315 	  ? "recursive directory iterator cannot pop"
316 	  : "non-dereferenceable recursive directory iterator cannot pop",
317 	  ec));
318 }
319