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