1 // Class filesystem::directory_entry etc. -*- C++ -*-
2 
3 // Copyright (C) 2014-2019 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 
39 namespace fs = std::filesystem;
40 namespace posix = std::filesystem::__gnu_posix;
41 
42 template class std::__shared_ptr<fs::_Dir>;
43 template class std::__shared_ptr<fs::recursive_directory_iterator::_Dir_stack>;
44 
45 struct fs::_Dir : _Dir_base
46 {
_Dirfs::_Dir47   _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 
_Dirfs::_Dir54   _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.
advancefs::_Dir60   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 
advancefs::_Dir83   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.
advancefs::_Dir87   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 
should_recursefs::_Dir97   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 
118 namespace
119 {
120   template<typename Bitmask>
121     inline bool
is_set(Bitmask obj,Bitmask bits)122     is_set(Bitmask obj, Bitmask bits)
123     {
124       return (obj & bits) != Bitmask::none;
125     }
126 }
127 
128 fs::directory_iterator::
directory_iterator(const path & p,directory_options options,error_code * ecptr)129 directory_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 
150 const fs::directory_entry&
operator *() const151 fs::directory_iterator::operator*() const noexcept
152 {
153   return _M_dir->entry;
154 }
155 
156 fs::directory_iterator&
operator ++()157 fs::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 
168 fs::directory_iterator&
increment(error_code & ec)169 fs::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 
181 struct fs::recursive_directory_iterator::_Dir_stack : std::stack<_Dir>
182 {
_Dir_stackfs::recursive_directory_iterator::_Dir_stack183   _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 
clearfs::recursive_directory_iterator::_Dir_stack192   void clear() { c.clear(); }
193 };
194 
195 fs::recursive_directory_iterator::
recursive_directory_iterator(const path & p,directory_options options,error_code * ecptr)196 recursive_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 
227 fs::recursive_directory_iterator::~recursive_directory_iterator() = default;
228 
229 fs::directory_options
options() const230 fs::recursive_directory_iterator::options() const noexcept
231 {
232   return _M_dirs->options;
233 }
234 
235 int
depth() const236 fs::recursive_directory_iterator::depth() const noexcept
237 {
238   return int(_M_dirs->size()) - 1;
239 }
240 
241 bool
recursion_pending() const242 fs::recursive_directory_iterator::recursion_pending() const noexcept
243 {
244   return _M_dirs->pending;
245 }
246 
247 const fs::directory_entry&
operator *() const248 fs::recursive_directory_iterator::operator*() const noexcept
249 {
250   return _M_dirs->top().entry;
251 }
252 
253 fs::recursive_directory_iterator&
254 fs::recursive_directory_iterator::
255 operator=(const recursive_directory_iterator& other) noexcept = default;
256 
257 fs::recursive_directory_iterator&
258 fs::recursive_directory_iterator::
259 operator=(recursive_directory_iterator&& other) noexcept = default;
260 
261 fs::recursive_directory_iterator&
operator ++()262 fs::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 
272 fs::recursive_directory_iterator&
increment(error_code & ec)273 fs::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   return *this;
310 }
311 
312 void
pop(error_code & ec)313 fs::recursive_directory_iterator::pop(error_code& ec)
314 {
315   if (!_M_dirs)
316     {
317       ec = std::make_error_code(errc::invalid_argument);
318       return;
319     }
320 
321   const bool skip_permission_denied
322     = is_set(_M_dirs->options, directory_options::skip_permission_denied);
323 
324   do {
325     _M_dirs->pop();
326     if (_M_dirs->empty())
327       {
328 	_M_dirs.reset();
329 	ec.clear();
330 	return;
331       }
332   } while (!_M_dirs->top().advance(skip_permission_denied, ec));
333 }
334 
335 void
pop()336 fs::recursive_directory_iterator::pop()
337 {
338   error_code ec;
339   pop(ec);
340   if (ec)
341     _GLIBCXX_THROW_OR_ABORT(filesystem_error(_M_dirs
342 	  ? "recursive directory iterator cannot pop"
343 	  : "non-dereferenceable recursive directory iterator cannot pop",
344 	  ec));
345 }
346 
347 void
disable_recursion_pending()348 fs::recursive_directory_iterator::disable_recursion_pending() noexcept
349 {
350   _M_dirs->pending = false;
351 }
352