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