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