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 <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 * ec)189 recursive_directory_iterator(const path& p, directory_options options,
190                              error_code* ec)
191 : _M_options(options), _M_pending(true)
192 {
193   if (ec)
194     ec->clear();
195   if (posix::DIR* dirp = posix::opendir(p.c_str()))
196     {
197       auto sp = std::make_shared<_Dir_stack>();
198       sp->push(_Dir{ dirp, p });
199       if (sp->top().advance(ec))
200 	_M_dirs.swap(sp);
201     }
202   else
203     {
204       const int err = errno;
205       if (err == EACCES
206 	  && is_set(options, fs::directory_options::skip_permission_denied))
207 	return;
208 
209       if (!ec)
210 	_GLIBCXX_THROW_OR_ABORT(filesystem_error(
211 	      "recursive directory iterator cannot open directory", p,
212 	      std::error_code(err, std::generic_category())));
213 
214       ec->assign(err, std::generic_category());
215     }
216 }
217 
218 fs::recursive_directory_iterator::~recursive_directory_iterator() = default;
219 
220 int
depth() const221 fs::recursive_directory_iterator::depth() const
222 {
223   return int(_M_dirs->size()) - 1;
224 }
225 
226 const fs::directory_entry&
operator *() const227 fs::recursive_directory_iterator::operator*() const
228 {
229   return _M_dirs->top().entry;
230 }
231 
232 fs::recursive_directory_iterator&
233 fs::recursive_directory_iterator::
234 operator=(const recursive_directory_iterator& other) noexcept = default;
235 
236 fs::recursive_directory_iterator&
237 fs::recursive_directory_iterator::
238 operator=(recursive_directory_iterator&& other) noexcept = default;
239 
240 fs::recursive_directory_iterator&
operator ++()241 fs::recursive_directory_iterator::operator++()
242 {
243   error_code ec;
244   increment(ec);
245   if (ec.value())
246     _GLIBCXX_THROW_OR_ABORT(filesystem_error(
247 	  "cannot increment recursive directory iterator", ec));
248   return *this;
249 }
250 
251 fs::recursive_directory_iterator&
increment(error_code & ec)252 fs::recursive_directory_iterator::increment(error_code& ec) noexcept
253 {
254   if (!_M_dirs)
255     {
256       ec = std::make_error_code(errc::invalid_argument);
257       return *this;
258     }
259 
260   const bool follow
261     = is_set(_M_options, directory_options::follow_directory_symlink);
262   const bool skip_permission_denied
263     = is_set(_M_options, directory_options::skip_permission_denied);
264 
265   auto& top = _M_dirs->top();
266 
267   if (std::exchange(_M_pending, true) && top.should_recurse(follow, ec))
268     {
269       _Dir dir(top.entry.path(), skip_permission_denied, ec);
270       if (ec)
271 	{
272 	  _M_dirs.reset();
273 	  return *this;
274 	}
275       if (dir.dirp)
276 	  _M_dirs->push(std::move(dir));
277     }
278 
279   while (!_M_dirs->top().advance(skip_permission_denied, ec) && !ec)
280     {
281       _M_dirs->pop();
282       if (_M_dirs->empty())
283 	{
284 	  _M_dirs.reset();
285 	  return *this;
286 	}
287     }
288   return *this;
289 }
290 
291 void
pop(error_code & ec)292 fs::recursive_directory_iterator::pop(error_code& ec)
293 {
294   if (!_M_dirs)
295     {
296       ec = std::make_error_code(errc::invalid_argument);
297       return;
298     }
299 
300   const bool skip_permission_denied
301     = is_set(_M_options, directory_options::skip_permission_denied);
302 
303   do {
304     _M_dirs->pop();
305     if (_M_dirs->empty())
306       {
307 	_M_dirs.reset();
308 	ec.clear();
309 	return;
310       }
311   } while (!_M_dirs->top().advance(skip_permission_denied, ec));
312 }
313 
314 void
pop()315 fs::recursive_directory_iterator::pop()
316 {
317   error_code ec;
318   pop(ec);
319   if (ec)
320     _GLIBCXX_THROW_OR_ABORT(filesystem_error(_M_dirs
321 	  ? "recursive directory iterator cannot pop"
322 	  : "non-dereferenceable recursive directory iterator cannot pop",
323 	  ec));
324 }
325