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