]> git.ipfire.org Git - thirdparty/gcc.git/blob - libstdc++-v3/src/filesystem/dir.cc
c8457bde102bd84eb12471ee9e005a94789a1d65
[thirdparty/gcc.git] / libstdc++-v3 / src / filesystem / dir.cc
1 // Class filesystem::directory_entry etc. -*- C++ -*-
2
3 // Copyright (C) 2014-2017 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 <experimental/filesystem>
30 #include <utility>
31 #include <stack>
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
40 # error "the <dirent.h> header is needed to build the Filesystem TS"
41 #endif
42
43 #ifdef _GLIBCXX_FILESYSTEM_IS_WINDOWS
44 # undef opendir
45 # define opendir _wopendir
46 #endif
47
48 namespace fs = std::experimental::filesystem;
49
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
65 bool advance(std::error_code*, directory_options = directory_options::none);
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>
76 inline bool
77 is_set(Bitmask obj, Bitmask bits)
78 {
79 return (obj & bits) != Bitmask::none;
80 }
81
82 // Returns {dirp, p} on success, {} on error (whether ignored or not).
83 inline fs::_Dir
84 open_dir(const fs::path& p, fs::directory_options options,
85 std::error_code* ec)
86 {
87 if (ec)
88 ec->clear();
89
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
103 ec->assign(err, std::generic_category());
104 return {};
105 }
106
107 inline fs::file_type
108 get_file_type(const ::dirent& d __attribute__((__unused__)))
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
138
139 // Returns false when the end of the directory entries is reached.
140 // Reports errors by setting ec or throwing.
141 bool
142 fs::_Dir::advance(error_code* ec, directory_options options)
143 {
144 if (ec)
145 ec->clear();
146
147 int err = std::exchange(errno, 0);
148 const auto entp = readdir(dirp);
149 std::swap(errno, err);
150
151 if (entp)
152 {
153 // skip past dot and dot-dot
154 if (!strcmp(entp->d_name, ".") || !strcmp(entp->d_name, ".."))
155 return advance(ec, options);
156 entry = fs::directory_entry{path / entp->d_name};
157 type = get_file_type(*entp);
158 return true;
159 }
160 else if (err)
161 {
162 if (err == EACCES
163 && is_set(options, directory_options::skip_permission_denied))
164 return false;
165
166 if (!ec)
167 _GLIBCXX_THROW_OR_ABORT(filesystem_error(
168 "directory iterator cannot advance",
169 std::error_code(err, std::generic_category())));
170 ec->assign(err, std::generic_category());
171 return false;
172 }
173 else
174 {
175 // reached the end
176 entry = {};
177 type = fs::file_type::none;
178 return false;
179 }
180 }
181
182 fs::directory_iterator::
183 directory_iterator(const path& p, directory_options options, error_code* ec)
184 {
185 _Dir dir = open_dir(p, options, ec);
186
187 if (dir.dirp)
188 {
189 auto sp = std::make_shared<fs::_Dir>(std::move(dir));
190 if (sp->advance(ec, options))
191 _M_dir.swap(sp);
192 }
193 }
194
195 const fs::directory_entry&
196 fs::directory_iterator::operator*() const
197 {
198 if (!_M_dir)
199 _GLIBCXX_THROW_OR_ABORT(filesystem_error(
200 "non-dereferenceable directory iterator",
201 std::make_error_code(errc::invalid_argument)));
202 return _M_dir->entry;
203 }
204
205 fs::directory_iterator&
206 fs::directory_iterator::operator++()
207 {
208 if (!_M_dir)
209 _GLIBCXX_THROW_OR_ABORT(filesystem_error(
210 "cannot advance non-dereferenceable directory iterator",
211 std::make_error_code(errc::invalid_argument)));
212 if (!_M_dir->advance(nullptr))
213 _M_dir.reset();
214 return *this;
215 }
216
217 fs::directory_iterator&
218 fs::directory_iterator::increment(error_code& ec) noexcept
219 {
220 if (!_M_dir)
221 {
222 ec = std::make_error_code(errc::invalid_argument);
223 return *this;
224 }
225 if (!_M_dir->advance(&ec))
226 _M_dir.reset();
227 return *this;
228 }
229
230 using Dir_iter_pair = std::pair<fs::_Dir, fs::directory_iterator>;
231
232 struct fs::recursive_directory_iterator::_Dir_stack : std::stack<_Dir>
233 {
234 void clear() { c.clear(); }
235 };
236
237 fs::recursive_directory_iterator::
238 recursive_directory_iterator(const path& p, directory_options options,
239 error_code* ec)
240 : _M_options(options), _M_pending(true)
241 {
242 if (DIR* dirp = ::opendir(p.c_str()))
243 {
244 auto sp = std::make_shared<_Dir_stack>();
245 sp->push(_Dir{ dirp, p });
246 if (sp->top().advance(ec))
247 _M_dirs.swap(sp);
248 }
249 else
250 {
251 const int err = errno;
252 if (err == EACCES
253 && is_set(options, fs::directory_options::skip_permission_denied))
254 {
255 if (ec)
256 ec->clear();
257 return;
258 }
259
260 if (!ec)
261 _GLIBCXX_THROW_OR_ABORT(filesystem_error(
262 "recursive directory iterator cannot open directory", p,
263 std::error_code(err, std::generic_category())));
264
265 ec->assign(err, std::generic_category());
266 }
267 }
268
269 fs::recursive_directory_iterator::~recursive_directory_iterator() = default;
270
271 int
272 fs::recursive_directory_iterator::depth() const
273 {
274 return int(_M_dirs->size()) - 1;
275 }
276
277 const fs::directory_entry&
278 fs::recursive_directory_iterator::operator*() const
279 {
280 return _M_dirs->top().entry;
281 }
282
283 fs::recursive_directory_iterator&
284 fs::recursive_directory_iterator::
285 operator=(const recursive_directory_iterator& other) noexcept = default;
286
287 fs::recursive_directory_iterator&
288 fs::recursive_directory_iterator::
289 operator=(recursive_directory_iterator&& other) noexcept = default;
290
291 fs::recursive_directory_iterator&
292 fs::recursive_directory_iterator::operator++()
293 {
294 error_code ec;
295 increment(ec);
296 if (ec.value())
297 _GLIBCXX_THROW_OR_ABORT(filesystem_error(
298 "cannot increment recursive directory iterator", ec));
299 return *this;
300 }
301
302 namespace
303 {
304 bool
305 recurse(const fs::_Dir& d, fs::directory_options options, std::error_code& ec)
306 {
307 bool follow_symlink
308 = is_set(options, fs::directory_options::follow_directory_symlink);
309 #ifdef _GLIBCXX_HAVE_STRUCT_DIRENT_D_TYPE
310 if (d.type == fs::file_type::directory)
311 return true;
312 if (d.type == fs::file_type::symlink && follow_symlink)
313 return d.entry.status().type() == fs::file_type::directory;
314 if (d.type != fs::file_type::none && d.type != fs::file_type::unknown)
315 return false;
316 #endif
317 const fs::path& path = d.entry.path();
318 auto type = fs::symlink_status(path, ec).type();
319 if (ec.value())
320 return false;
321 if (type == fs::file_type::symlink)
322 {
323 if (!follow_symlink)
324 return false;
325 type = fs::status(path, ec).type();
326 }
327 return type == fs::file_type::directory;
328 }
329 }
330
331 fs::recursive_directory_iterator&
332 fs::recursive_directory_iterator::increment(error_code& ec) noexcept
333 {
334 if (!_M_dirs)
335 {
336 ec = std::make_error_code(errc::invalid_argument);
337 return *this;
338 }
339
340 auto& top = _M_dirs->top();
341
342 if (std::exchange(_M_pending, true) && recurse(top, _M_options, ec))
343 {
344 _Dir dir = open_dir(top.entry.path(), _M_options, &ec);
345 if (ec)
346 {
347 _M_dirs.reset();
348 return *this;
349 }
350 if (dir.dirp)
351 _M_dirs->push(std::move(dir));
352 }
353
354 while (!_M_dirs->top().advance(&ec, _M_options) && !ec)
355 {
356 _M_dirs->pop();
357 if (_M_dirs->empty())
358 {
359 _M_dirs.reset();
360 return *this;
361 }
362 }
363 return *this;
364 }
365
366 void
367 fs::recursive_directory_iterator::pop(error_code& ec)
368 {
369 if (!_M_dirs)
370 {
371 ec = std::make_error_code(errc::invalid_argument);
372 return;
373 }
374
375 do {
376 _M_dirs->pop();
377 if (_M_dirs->empty())
378 {
379 _M_dirs.reset();
380 ec.clear();
381 return;
382 }
383 } while (!_M_dirs->top().advance(&ec, _M_options));
384 }
385
386 void
387 fs::recursive_directory_iterator::pop()
388 {
389 error_code ec;
390 pop(ec);
391 if (ec)
392 _GLIBCXX_THROW_OR_ABORT(filesystem_error(_M_dirs
393 ? "recursive directory iterator cannot pop"
394 : "non-dereferenceable recursive directory iterator cannot pop",
395 ec));
396 }