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