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
48namespace fs = std::experimental::filesystem;
49
50struct 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
73namespace
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.
142bool
143fs::_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
183fs::directory_iterator::
184directory_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
202const fs::directory_entry&
203fs::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
212fs::directory_iterator&
213fs::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
224fs::directory_iterator&
225fs::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
237using Dir_iter_pair = std::pair<fs::_Dir, fs::directory_iterator>;
238
239struct fs::recursive_directory_iterator::_Dir_stack : std::stack<_Dir>
240{
241  void clear() { c.clear(); }
242};
243
244fs::recursive_directory_iterator::
245recursive_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
280fs::recursive_directory_iterator::~recursive_directory_iterator() = default;
281
282int
283fs::recursive_directory_iterator::depth() const
284{
285  return int(_M_dirs->size()) - 1;
286}
287
288const fs::directory_entry&
289fs::recursive_directory_iterator::operator*() const
290{
291  return _M_dirs->top().entry;
292}
293
294fs::recursive_directory_iterator&
295fs::recursive_directory_iterator::
296operator=(const recursive_directory_iterator& other) noexcept = default;
297
298fs::recursive_directory_iterator&
299fs::recursive_directory_iterator::
300operator=(recursive_directory_iterator&& other) noexcept = default;
301
302fs::recursive_directory_iterator&
303fs::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
313namespace
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
342fs::recursive_directory_iterator&
343fs::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
374void
375fs::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