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