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 
40 namespace fs = std::filesystem;
41 
42 struct fs::_Dir : _Dir_base
43 {
_Dirfs::_Dir44   _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 
_Dirfs::_Dir51   _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.
advancefs::_Dir57   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 
advancefs::_Dir78   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.
advancefs::_Dir82   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 
should_recursefs::_Dir92   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 
113 namespace
114 {
115   template<typename Bitmask>
116     inline bool
is_set(Bitmask obj,Bitmask bits)117     is_set(Bitmask obj, Bitmask bits)
118     {
119       return (obj & bits) != Bitmask::none;
120     }
121 }
122 
123 fs::directory_iterator::
directory_iterator(const path & p,directory_options options,error_code * ecptr)124 directory_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 
145 const fs::directory_entry&
operator *() const146 fs::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 
155 fs::directory_iterator&
operator ++()156 fs::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 
167 fs::directory_iterator&
increment(error_code & ec)168 fs::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 
180 struct fs::recursive_directory_iterator::_Dir_stack : std::stack<_Dir>
181 {
clearfs::recursive_directory_iterator::_Dir_stack182   void clear() { c.clear(); }
183 };
184 
185 fs::recursive_directory_iterator::
recursive_directory_iterator(const path & p,directory_options options,error_code * ecptr)186 recursive_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 
219 fs::recursive_directory_iterator::~recursive_directory_iterator() = default;
220 
221 int
depth() const222 fs::recursive_directory_iterator::depth() const
223 {
224   return int(_M_dirs->size()) - 1;
225 }
226 
227 const fs::directory_entry&
operator *() const228 fs::recursive_directory_iterator::operator*() const
229 {
230   return _M_dirs->top().entry;
231 }
232 
233 fs::recursive_directory_iterator&
234 fs::recursive_directory_iterator::
235 operator=(const recursive_directory_iterator& other) noexcept = default;
236 
237 fs::recursive_directory_iterator&
238 fs::recursive_directory_iterator::
239 operator=(recursive_directory_iterator&& other) noexcept = default;
240 
241 fs::recursive_directory_iterator&
operator ++()242 fs::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 
252 fs::recursive_directory_iterator&
increment(error_code & ec)253 fs::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 
292 void
pop(error_code & ec)293 fs::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 
315 void
pop()316 fs::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