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