]>
Commit | Line | Data |
---|---|---|
0ca7ba9a JW |
1 | // Class filesystem::directory_entry etc. -*- C++ -*- |
2 | ||
cbe34bb5 | 3 | // Copyright (C) 2014-2017 Free Software Foundation, Inc. |
0ca7ba9a JW |
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 | ||
8b756210 JW |
25 | #ifndef _GLIBCXX_USE_CXX11_ABI |
26 | # define _GLIBCXX_USE_CXX11_ABI 1 | |
27 | #endif | |
28 | ||
0ca7ba9a JW |
29 | #include <experimental/filesystem> |
30 | #include <utility> | |
31 | #include <stack> | |
0ca7ba9a JW |
32 | #include <string.h> |
33 | #include <errno.h> | |
34 | #ifdef _GLIBCXX_HAVE_DIRENT_H | |
35 | # ifdef _GLIBCXX_HAVE_SYS_TYPES_H | |
36 | # include <sys/types.h> | |
37 | # endif | |
38 | # include <dirent.h> | |
39 | #else | |
bf53e6a9 | 40 | # error "the <dirent.h> header is needed to build the Filesystem TS" |
0ca7ba9a | 41 | #endif |
bf53e6a9 JW |
42 | |
43 | #ifdef _GLIBCXX_FILESYSTEM_IS_WINDOWS | |
44 | # undef opendir | |
45 | # define opendir _wopendir | |
0ca7ba9a JW |
46 | #endif |
47 | ||
48 | namespace fs = std::experimental::filesystem; | |
49 | ||
0ca7ba9a JW |
50 | struct fs::_Dir |
51 | { | |
52 | _Dir() : dirp(nullptr) { } | |
53 | ||
54 | _Dir(DIR* dirp, const fs::path& path) : dirp(dirp), path(path) { } | |
55 | ||
56 | _Dir(_Dir&& d) | |
57 | : dirp(std::exchange(d.dirp, nullptr)), path(std::move(d.path)), | |
58 | entry(std::move(d.entry)), type(d.type) | |
59 | { } | |
60 | ||
61 | _Dir& operator=(_Dir&&) = delete; | |
62 | ||
63 | ~_Dir() { if (dirp) ::closedir(dirp); } | |
64 | ||
429ee11a | 65 | bool advance(std::error_code*, directory_options = directory_options::none); |
0ca7ba9a JW |
66 | |
67 | DIR* dirp; | |
68 | fs::path path; | |
69 | directory_entry entry; | |
70 | file_type type = file_type::none; | |
71 | }; | |
72 | ||
73 | namespace | |
74 | { | |
75 | template<typename Bitmask> | |
7b65155f JW |
76 | inline bool |
77 | is_set(Bitmask obj, Bitmask bits) | |
0ca7ba9a JW |
78 | { |
79 | return (obj & bits) != Bitmask::none; | |
80 | } | |
81 | ||
bb52a7e3 | 82 | // Returns {dirp, p} on success, {} on error (whether ignored or not). |
7b65155f JW |
83 | inline fs::_Dir |
84 | open_dir(const fs::path& p, fs::directory_options options, | |
85 | std::error_code* ec) | |
0ca7ba9a | 86 | { |
429ee11a JW |
87 | if (ec) |
88 | ec->clear(); | |
89 | ||
0ca7ba9a JW |
90 | if (DIR* dirp = ::opendir(p.c_str())) |
91 | return {dirp, p}; | |
92 | ||
93 | const int err = errno; | |
94 | if (err == EACCES | |
95 | && is_set(options, fs::directory_options::skip_permission_denied)) | |
96 | return {}; | |
97 | ||
98 | if (!ec) | |
99 | _GLIBCXX_THROW_OR_ABORT(fs::filesystem_error( | |
100 | "directory iterator cannot open directory", p, | |
101 | std::error_code(err, std::generic_category()))); | |
102 | ||
429ee11a | 103 | ec->assign(err, std::generic_category()); |
bb52a7e3 | 104 | return {}; |
0ca7ba9a JW |
105 | } |
106 | ||
107 | inline fs::file_type | |
7b65155f | 108 | get_file_type(const ::dirent& d __attribute__((__unused__))) |
0ca7ba9a JW |
109 | { |
110 | #ifdef _GLIBCXX_HAVE_STRUCT_DIRENT_D_TYPE | |
111 | switch (d.d_type) | |
112 | { | |
113 | case DT_BLK: | |
114 | return fs::file_type::block; | |
115 | case DT_CHR: | |
116 | return fs::file_type::character; | |
117 | case DT_DIR: | |
118 | return fs::file_type::directory; | |
119 | case DT_FIFO: | |
120 | return fs::file_type::fifo; | |
121 | case DT_LNK: | |
122 | return fs::file_type::symlink; | |
123 | case DT_REG: | |
124 | return fs::file_type::regular; | |
125 | case DT_SOCK: | |
126 | return fs::file_type::socket; | |
127 | case DT_UNKNOWN: | |
128 | return fs::file_type::unknown; | |
129 | default: | |
130 | return fs::file_type::none; | |
131 | } | |
132 | #else | |
133 | return fs::file_type::none; | |
134 | #endif | |
135 | } | |
136 | } | |
137 | ||
7b65155f | 138 | |
429ee11a JW |
139 | // Returns false when the end of the directory entries is reached. |
140 | // Reports errors by setting ec or throwing. | |
0ca7ba9a | 141 | bool |
429ee11a | 142 | fs::_Dir::advance(error_code* ec, directory_options options) |
0ca7ba9a | 143 | { |
429ee11a JW |
144 | if (ec) |
145 | ec->clear(); | |
146 | ||
7b65155f JW |
147 | int err = std::exchange(errno, 0); |
148 | const auto entp = readdir(dirp); | |
76052545 EB |
149 | // std::swap cannot be used with Bionic's errno |
150 | err = std::exchange(errno, err); | |
7b65155f JW |
151 | |
152 | if (entp) | |
153 | { | |
154 | // skip past dot and dot-dot | |
155 | if (!strcmp(entp->d_name, ".") || !strcmp(entp->d_name, "..")) | |
156 | return advance(ec, options); | |
157 | entry = fs::directory_entry{path / entp->d_name}; | |
158 | type = get_file_type(*entp); | |
159 | return true; | |
160 | } | |
161 | else if (err) | |
0ca7ba9a | 162 | { |
429ee11a JW |
163 | if (err == EACCES |
164 | && is_set(options, directory_options::skip_permission_denied)) | |
165 | return false; | |
166 | ||
0ca7ba9a JW |
167 | if (!ec) |
168 | _GLIBCXX_THROW_OR_ABORT(filesystem_error( | |
169 | "directory iterator cannot advance", | |
170 | std::error_code(err, std::generic_category()))); | |
429ee11a | 171 | ec->assign(err, std::generic_category()); |
bb52a7e3 | 172 | return false; |
0ca7ba9a | 173 | } |
0ca7ba9a JW |
174 | else |
175 | { | |
176 | // reached the end | |
177 | entry = {}; | |
178 | type = fs::file_type::none; | |
179 | return false; | |
180 | } | |
181 | } | |
182 | ||
183 | fs::directory_iterator:: | |
184 | directory_iterator(const path& p, directory_options options, error_code* ec) | |
0ca7ba9a | 185 | { |
429ee11a JW |
186 | _Dir dir = open_dir(p, options, ec); |
187 | ||
188 | if (dir.dirp) | |
189 | { | |
190 | auto sp = std::make_shared<fs::_Dir>(std::move(dir)); | |
191 | if (sp->advance(ec, options)) | |
192 | _M_dir.swap(sp); | |
193 | } | |
0ca7ba9a JW |
194 | } |
195 | ||
196 | const fs::directory_entry& | |
197 | fs::directory_iterator::operator*() const | |
198 | { | |
199 | if (!_M_dir) | |
200 | _GLIBCXX_THROW_OR_ABORT(filesystem_error( | |
201 | "non-dereferenceable directory iterator", | |
202 | std::make_error_code(errc::invalid_argument))); | |
203 | return _M_dir->entry; | |
204 | } | |
205 | ||
206 | fs::directory_iterator& | |
207 | fs::directory_iterator::operator++() | |
208 | { | |
209 | if (!_M_dir) | |
210 | _GLIBCXX_THROW_OR_ABORT(filesystem_error( | |
211 | "cannot advance non-dereferenceable directory iterator", | |
212 | std::make_error_code(errc::invalid_argument))); | |
213 | if (!_M_dir->advance(nullptr)) | |
214 | _M_dir.reset(); | |
215 | return *this; | |
216 | } | |
217 | ||
218 | fs::directory_iterator& | |
219 | fs::directory_iterator::increment(error_code& ec) noexcept | |
220 | { | |
221 | if (!_M_dir) | |
222 | { | |
223 | ec = std::make_error_code(errc::invalid_argument); | |
224 | return *this; | |
225 | } | |
226 | if (!_M_dir->advance(&ec)) | |
227 | _M_dir.reset(); | |
228 | return *this; | |
229 | } | |
230 | ||
231 | using Dir_iter_pair = std::pair<fs::_Dir, fs::directory_iterator>; | |
232 | ||
233 | struct fs::recursive_directory_iterator::_Dir_stack : std::stack<_Dir> | |
234 | { | |
235 | void clear() { c.clear(); } | |
236 | }; | |
237 | ||
238 | fs::recursive_directory_iterator:: | |
239 | recursive_directory_iterator(const path& p, directory_options options, | |
429ee11a | 240 | error_code* ec) |
0ca7ba9a JW |
241 | : _M_options(options), _M_pending(true) |
242 | { | |
243 | if (DIR* dirp = ::opendir(p.c_str())) | |
244 | { | |
7b65155f JW |
245 | auto sp = std::make_shared<_Dir_stack>(); |
246 | sp->push(_Dir{ dirp, p }); | |
247 | if (sp->top().advance(ec)) | |
248 | _M_dirs.swap(sp); | |
0ca7ba9a JW |
249 | } |
250 | else | |
251 | { | |
252 | const int err = errno; | |
253 | if (err == EACCES | |
254 | && is_set(options, fs::directory_options::skip_permission_denied)) | |
429ee11a JW |
255 | { |
256 | if (ec) | |
257 | ec->clear(); | |
258 | return; | |
259 | } | |
0ca7ba9a JW |
260 | |
261 | if (!ec) | |
262 | _GLIBCXX_THROW_OR_ABORT(filesystem_error( | |
263 | "recursive directory iterator cannot open directory", p, | |
264 | std::error_code(err, std::generic_category()))); | |
265 | ||
266 | ec->assign(err, std::generic_category()); | |
267 | } | |
268 | } | |
269 | ||
270 | fs::recursive_directory_iterator::~recursive_directory_iterator() = default; | |
271 | ||
272 | int | |
273 | fs::recursive_directory_iterator::depth() const | |
274 | { | |
275 | return int(_M_dirs->size()) - 1; | |
276 | } | |
277 | ||
278 | const fs::directory_entry& | |
279 | fs::recursive_directory_iterator::operator*() const | |
280 | { | |
281 | return _M_dirs->top().entry; | |
282 | } | |
283 | ||
284 | fs::recursive_directory_iterator& | |
285 | fs::recursive_directory_iterator:: | |
286 | operator=(const recursive_directory_iterator& other) noexcept = default; | |
287 | ||
288 | fs::recursive_directory_iterator& | |
289 | fs::recursive_directory_iterator:: | |
290 | operator=(recursive_directory_iterator&& other) noexcept = default; | |
291 | ||
292 | fs::recursive_directory_iterator& | |
293 | fs::recursive_directory_iterator::operator++() | |
294 | { | |
295 | error_code ec; | |
296 | increment(ec); | |
297 | if (ec.value()) | |
298 | _GLIBCXX_THROW_OR_ABORT(filesystem_error( | |
299 | "cannot increment recursive directory iterator", ec)); | |
300 | return *this; | |
301 | } | |
302 | ||
303 | namespace | |
304 | { | |
305 | bool | |
306 | recurse(const fs::_Dir& d, fs::directory_options options, std::error_code& ec) | |
307 | { | |
308 | bool follow_symlink | |
309 | = is_set(options, fs::directory_options::follow_directory_symlink); | |
310 | #ifdef _GLIBCXX_HAVE_STRUCT_DIRENT_D_TYPE | |
311 | if (d.type == fs::file_type::directory) | |
312 | return true; | |
313 | if (d.type == fs::file_type::symlink && follow_symlink) | |
314 | return d.entry.status().type() == fs::file_type::directory; | |
315 | if (d.type != fs::file_type::none && d.type != fs::file_type::unknown) | |
316 | return false; | |
317 | #endif | |
318 | const fs::path& path = d.entry.path(); | |
319 | auto type = fs::symlink_status(path, ec).type(); | |
320 | if (ec.value()) | |
321 | return false; | |
322 | if (type == fs::file_type::symlink) | |
323 | { | |
324 | if (!follow_symlink) | |
325 | return false; | |
326 | type = fs::status(path, ec).type(); | |
327 | } | |
328 | return type == fs::file_type::directory; | |
329 | } | |
330 | } | |
331 | ||
332 | fs::recursive_directory_iterator& | |
333 | fs::recursive_directory_iterator::increment(error_code& ec) noexcept | |
334 | { | |
335 | if (!_M_dirs) | |
336 | { | |
337 | ec = std::make_error_code(errc::invalid_argument); | |
338 | return *this; | |
339 | } | |
340 | ||
341 | auto& top = _M_dirs->top(); | |
342 | ||
343 | if (std::exchange(_M_pending, true) && recurse(top, _M_options, ec)) | |
344 | { | |
429ee11a JW |
345 | _Dir dir = open_dir(top.entry.path(), _M_options, &ec); |
346 | if (ec) | |
b4e7e6bf JW |
347 | { |
348 | _M_dirs.reset(); | |
349 | return *this; | |
350 | } | |
0ca7ba9a | 351 | if (dir.dirp) |
0ca7ba9a | 352 | _M_dirs->push(std::move(dir)); |
0ca7ba9a JW |
353 | } |
354 | ||
429ee11a | 355 | while (!_M_dirs->top().advance(&ec, _M_options) && !ec) |
0ca7ba9a JW |
356 | { |
357 | _M_dirs->pop(); | |
358 | if (_M_dirs->empty()) | |
359 | { | |
360 | _M_dirs.reset(); | |
361 | return *this; | |
362 | } | |
363 | } | |
364 | return *this; | |
365 | } | |
366 | ||
367 | void | |
ec0b1056 | 368 | fs::recursive_directory_iterator::pop(error_code& ec) |
0ca7ba9a JW |
369 | { |
370 | if (!_M_dirs) | |
ec0b1056 JW |
371 | { |
372 | ec = std::make_error_code(errc::invalid_argument); | |
373 | return; | |
374 | } | |
0ca7ba9a JW |
375 | |
376 | do { | |
377 | _M_dirs->pop(); | |
378 | if (_M_dirs->empty()) | |
379 | { | |
380 | _M_dirs.reset(); | |
ec0b1056 | 381 | ec.clear(); |
0ca7ba9a JW |
382 | return; |
383 | } | |
ec0b1056 JW |
384 | } while (!_M_dirs->top().advance(&ec, _M_options)); |
385 | } | |
386 | ||
387 | void | |
388 | fs::recursive_directory_iterator::pop() | |
389 | { | |
390 | error_code ec; | |
391 | pop(ec); | |
392 | if (ec) | |
393 | _GLIBCXX_THROW_OR_ABORT(filesystem_error(_M_dirs | |
394 | ? "recursive directory iterator cannot pop" | |
395 | : "non-dereferenceable recursive directory iterator cannot pop", | |
396 | ec)); | |
0ca7ba9a | 397 | } |