xref: /netbsd-src/external/gpl3/gcc.old/dist/libstdc++-v3/src/filesystem/dir.cc (revision 76c7fc5f6b13ed0b1508e6b313e88e59977ed78e)
1 // Class filesystem::directory_entry etc. -*- C++ -*-
2 
3 // Copyright (C) 2014-2017 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 <experimental/filesystem>
30 #include <utility>
31 #include <stack>
32 #include <string.h>
33 #include <errno.h>
34 #ifdef _GLIBCXX_HAVE_DIRENT_H
35 # ifdef _GLIBCXX_HAVE_SYS_TYPES_H
36 #  include <sys/types.h>
37 # endif
38 # include <dirent.h>
39 #else
40 # error "the <dirent.h> header is needed to build the Filesystem TS"
41 #endif
42 
43 #ifdef _GLIBCXX_FILESYSTEM_IS_WINDOWS
44 # undef opendir
45 # define opendir _wopendir
46 #endif
47 
48 namespace fs = std::experimental::filesystem;
49 
50 struct fs::_Dir
51 {
52   _Dir() : dirp(nullptr) { }
53 
54   _Dir(DIR* dirp, const fs::path& path) : dirp(dirp), path(path) { }
55 
56   _Dir(_Dir&& d)
57   : dirp(std::exchange(d.dirp, nullptr)), path(std::move(d.path)),
58     entry(std::move(d.entry)), type(d.type)
59   { }
60 
61   _Dir& operator=(_Dir&&) = delete;
62 
63   ~_Dir() { if (dirp) ::closedir(dirp); }
64 
65   bool advance(std::error_code*, directory_options = directory_options::none);
66 
67   DIR*			dirp;
68   fs::path		path;
69   directory_entry	entry;
70   file_type		type = file_type::none;
71 };
72 
73 namespace
74 {
75   template<typename Bitmask>
76     inline bool
77     is_set(Bitmask obj, Bitmask bits)
78     {
79       return (obj & bits) != Bitmask::none;
80     }
81 
82   // Returns {dirp, p} on success, {} on error (whether ignored or not).
83   inline fs::_Dir
84   open_dir(const fs::path& p, fs::directory_options options,
85 	   std::error_code* ec)
86   {
87     if (ec)
88       ec->clear();
89 
90     if (DIR* dirp = ::opendir(p.c_str()))
91       return {dirp, p};
92 
93     const int err = errno;
94     if (err == EACCES
95         && is_set(options, fs::directory_options::skip_permission_denied))
96       return {};
97 
98     if (!ec)
99       _GLIBCXX_THROW_OR_ABORT(fs::filesystem_error(
100             "directory iterator cannot open directory", p,
101             std::error_code(err, std::generic_category())));
102 
103     ec->assign(err, std::generic_category());
104     return {};
105   }
106 
107   inline fs::file_type
108   get_file_type(const ::dirent& d __attribute__((__unused__)))
109   {
110 #ifdef _GLIBCXX_HAVE_STRUCT_DIRENT_D_TYPE
111     switch (d.d_type)
112     {
113     case DT_BLK:
114       return fs::file_type::block;
115     case DT_CHR:
116       return fs::file_type::character;
117     case DT_DIR:
118       return fs::file_type::directory;
119     case DT_FIFO:
120       return fs::file_type::fifo;
121     case DT_LNK:
122       return fs::file_type::symlink;
123     case DT_REG:
124       return fs::file_type::regular;
125     case DT_SOCK:
126       return fs::file_type::socket;
127     case DT_UNKNOWN:
128       return fs::file_type::unknown;
129     default:
130       return fs::file_type::none;
131     }
132 #else
133     return fs::file_type::none;
134 #endif
135   }
136 }
137 
138 
139 // Returns false when the end of the directory entries is reached.
140 // Reports errors by setting ec or throwing.
141 bool
142 fs::_Dir::advance(error_code* ec, directory_options options)
143 {
144   if (ec)
145     ec->clear();
146 
147   int err = std::exchange(errno, 0);
148   const auto entp = readdir(dirp);
149   std::swap(errno, err);
150 
151   if (entp)
152     {
153       // skip past dot and dot-dot
154       if (!strcmp(entp->d_name, ".") || !strcmp(entp->d_name, ".."))
155 	return advance(ec, options);
156       entry = fs::directory_entry{path / entp->d_name};
157       type = get_file_type(*entp);
158       return true;
159     }
160   else if (err)
161     {
162       if (err == EACCES
163         && is_set(options, directory_options::skip_permission_denied))
164 	return false;
165 
166       if (!ec)
167 	_GLIBCXX_THROW_OR_ABORT(filesystem_error(
168 	      "directory iterator cannot advance",
169 	      std::error_code(err, std::generic_category())));
170       ec->assign(err, std::generic_category());
171       return false;
172     }
173   else
174     {
175       // reached the end
176       entry = {};
177       type = fs::file_type::none;
178       return false;
179     }
180 }
181 
182 fs::directory_iterator::
183 directory_iterator(const path& p, directory_options options, error_code* ec)
184 {
185   _Dir dir = open_dir(p, options, ec);
186 
187   if (dir.dirp)
188     {
189       auto sp = std::make_shared<fs::_Dir>(std::move(dir));
190       if (sp->advance(ec, options))
191 	_M_dir.swap(sp);
192     }
193 }
194 
195 const fs::directory_entry&
196 fs::directory_iterator::operator*() const
197 {
198   if (!_M_dir)
199     _GLIBCXX_THROW_OR_ABORT(filesystem_error(
200 	  "non-dereferenceable directory iterator",
201 	  std::make_error_code(errc::invalid_argument)));
202   return _M_dir->entry;
203 }
204 
205 fs::directory_iterator&
206 fs::directory_iterator::operator++()
207 {
208   if (!_M_dir)
209     _GLIBCXX_THROW_OR_ABORT(filesystem_error(
210 	  "cannot advance non-dereferenceable directory iterator",
211 	  std::make_error_code(errc::invalid_argument)));
212   if (!_M_dir->advance(nullptr))
213     _M_dir.reset();
214   return *this;
215 }
216 
217 fs::directory_iterator&
218 fs::directory_iterator::increment(error_code& ec) noexcept
219 {
220   if (!_M_dir)
221     {
222       ec = std::make_error_code(errc::invalid_argument);
223       return *this;
224     }
225   if (!_M_dir->advance(&ec))
226     _M_dir.reset();
227   return *this;
228 }
229 
230 using Dir_iter_pair = std::pair<fs::_Dir, fs::directory_iterator>;
231 
232 struct fs::recursive_directory_iterator::_Dir_stack : std::stack<_Dir>
233 {
234   void clear() { c.clear(); }
235 };
236 
237 fs::recursive_directory_iterator::
238 recursive_directory_iterator(const path& p, directory_options options,
239                              error_code* ec)
240 : _M_options(options), _M_pending(true)
241 {
242   if (DIR* dirp = ::opendir(p.c_str()))
243     {
244       auto sp = std::make_shared<_Dir_stack>();
245       sp->push(_Dir{ dirp, p });
246       if (sp->top().advance(ec))
247 	_M_dirs.swap(sp);
248     }
249   else
250     {
251       const int err = errno;
252       if (err == EACCES
253 	  && is_set(options, fs::directory_options::skip_permission_denied))
254 	{
255 	  if (ec)
256 	    ec->clear();
257 	  return;
258 	}
259 
260       if (!ec)
261 	_GLIBCXX_THROW_OR_ABORT(filesystem_error(
262 	      "recursive directory iterator cannot open directory", p,
263 	      std::error_code(err, std::generic_category())));
264 
265       ec->assign(err, std::generic_category());
266     }
267 }
268 
269 fs::recursive_directory_iterator::~recursive_directory_iterator() = default;
270 
271 int
272 fs::recursive_directory_iterator::depth() const
273 {
274   return int(_M_dirs->size()) - 1;
275 }
276 
277 const fs::directory_entry&
278 fs::recursive_directory_iterator::operator*() const
279 {
280   return _M_dirs->top().entry;
281 }
282 
283 fs::recursive_directory_iterator&
284 fs::recursive_directory_iterator::
285 operator=(const recursive_directory_iterator& other) noexcept = default;
286 
287 fs::recursive_directory_iterator&
288 fs::recursive_directory_iterator::
289 operator=(recursive_directory_iterator&& other) noexcept = default;
290 
291 fs::recursive_directory_iterator&
292 fs::recursive_directory_iterator::operator++()
293 {
294   error_code ec;
295   increment(ec);
296   if (ec.value())
297     _GLIBCXX_THROW_OR_ABORT(filesystem_error(
298 	  "cannot increment recursive directory iterator", ec));
299   return *this;
300 }
301 
302 namespace
303 {
304   bool
305   recurse(const fs::_Dir& d, fs::directory_options options, std::error_code& ec)
306   {
307     bool follow_symlink
308       = is_set(options, fs::directory_options::follow_directory_symlink);
309 #ifdef _GLIBCXX_HAVE_STRUCT_DIRENT_D_TYPE
310     if (d.type == fs::file_type::directory)
311       return true;
312     if (d.type == fs::file_type::symlink && follow_symlink)
313       return d.entry.status().type() == fs::file_type::directory;
314     if (d.type != fs::file_type::none && d.type != fs::file_type::unknown)
315       return false;
316 #endif
317     const fs::path& path = d.entry.path();
318     auto type = fs::symlink_status(path, ec).type();
319     if (ec.value())
320       return false;
321     if (type == fs::file_type::symlink)
322       {
323 	if (!follow_symlink)
324 	  return false;
325 	type = fs::status(path, ec).type();
326       }
327     return type == fs::file_type::directory;
328   }
329 }
330 
331 fs::recursive_directory_iterator&
332 fs::recursive_directory_iterator::increment(error_code& ec) noexcept
333 {
334   if (!_M_dirs)
335     {
336       ec = std::make_error_code(errc::invalid_argument);
337       return *this;
338     }
339 
340   auto& top = _M_dirs->top();
341 
342   if (std::exchange(_M_pending, true) && recurse(top, _M_options, ec))
343     {
344       _Dir dir = open_dir(top.entry.path(), _M_options, &ec);
345       if (ec)
346 	{
347 	  _M_dirs.reset();
348 	  return *this;
349 	}
350       if (dir.dirp)
351 	  _M_dirs->push(std::move(dir));
352     }
353 
354   while (!_M_dirs->top().advance(&ec, _M_options) && !ec)
355     {
356       _M_dirs->pop();
357       if (_M_dirs->empty())
358 	{
359 	  _M_dirs.reset();
360 	  return *this;
361 	}
362     }
363   return *this;
364 }
365 
366 void
367 fs::recursive_directory_iterator::pop(error_code& ec)
368 {
369   if (!_M_dirs)
370     {
371       ec = std::make_error_code(errc::invalid_argument);
372       return;
373     }
374 
375   do {
376     _M_dirs->pop();
377     if (_M_dirs->empty())
378       {
379 	_M_dirs.reset();
380 	ec.clear();
381 	return;
382       }
383   } while (!_M_dirs->top().advance(&ec, _M_options));
384 }
385 
386 void
387 fs::recursive_directory_iterator::pop()
388 {
389   error_code ec;
390   pop(ec);
391   if (ec)
392     _GLIBCXX_THROW_OR_ABORT(filesystem_error(_M_dirs
393 	  ? "recursive directory iterator cannot pop"
394 	  : "non-dereferenceable recursive directory iterator cannot pop",
395 	  ec));
396 }
397