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