1 //===----------------------------------------------------------------------===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8
9 // REQUIRES: can-create-symlinks
10 // UNSUPPORTED: c++03, c++11, c++14
11 // UNSUPPORTED: no-filesystem
12 // UNSUPPORTED: availability-filesystem-missing
13
14 // On Android L, ~scoped_test_env() is unable to delete the temp dir using
15 // chmod+rm because chmod is too broken.
16 // XFAIL: LIBCXX-ANDROID-FIXME && android-device-api={{21|22}}
17
18 // <filesystem>
19
20 // class recursive_directory_iterator
21
22 // recursive_directory_iterator& operator++();
23 // recursive_directory_iterator& increment(error_code& ec) noexcept;
24
25 #include <filesystem>
26 #include <type_traits>
27 #include <set>
28 #include <cassert>
29
30 #include "assert_macros.h"
31 #include "test_macros.h"
32 #include "filesystem_test_helper.h"
33 namespace fs = std::filesystem;
34 using namespace fs;
35
test_increment_signatures()36 static void test_increment_signatures()
37 {
38 recursive_directory_iterator d; ((void)d);
39 std::error_code ec; ((void)ec);
40
41 ASSERT_SAME_TYPE(decltype(++d), recursive_directory_iterator&);
42 ASSERT_NOT_NOEXCEPT(++d);
43
44 ASSERT_SAME_TYPE(decltype(d.increment(ec)), recursive_directory_iterator&);
45 ASSERT_NOT_NOEXCEPT(d.increment(ec));
46 }
47
test_prefix_increment()48 static void test_prefix_increment()
49 {
50 static_test_env static_env;
51 const path testDir = static_env.Dir;
52 const std::set<path> dir_contents(static_env.RecDirIterationList.begin(),
53 static_env.RecDirIterationList.end());
54 const recursive_directory_iterator endIt{};
55
56 std::error_code ec;
57 recursive_directory_iterator it(testDir, ec);
58 assert(!ec);
59
60 std::set<path> unseen_entries = dir_contents;
61 while (!unseen_entries.empty()) {
62 assert(it != endIt);
63 const path entry = *it;
64 assert(unseen_entries.erase(entry) == 1);
65 recursive_directory_iterator& it_ref = ++it;
66 assert(&it_ref == &it);
67 }
68
69 assert(it == endIt);
70 }
71
test_postfix_increment()72 static void test_postfix_increment()
73 {
74 static_test_env static_env;
75 const path testDir = static_env.Dir;
76 const std::set<path> dir_contents(static_env.RecDirIterationList.begin(),
77 static_env.RecDirIterationList.end());
78 const recursive_directory_iterator endIt{};
79
80 std::error_code ec;
81 recursive_directory_iterator it(testDir, ec);
82 assert(!ec);
83
84 std::set<path> unseen_entries = dir_contents;
85 while (!unseen_entries.empty()) {
86 assert(it != endIt);
87 const path entry = *it;
88 assert(unseen_entries.erase(entry) == 1);
89 const path entry2 = *it++;
90 assert(entry2 == entry);
91 }
92 assert(it == endIt);
93 }
94
95
test_increment_method()96 static void test_increment_method()
97 {
98 static_test_env static_env;
99 const path testDir = static_env.Dir;
100 const std::set<path> dir_contents(static_env.RecDirIterationList.begin(),
101 static_env.RecDirIterationList.end());
102 const recursive_directory_iterator endIt{};
103
104 std::error_code ec;
105 recursive_directory_iterator it(testDir, ec);
106 assert(!ec);
107
108 std::set<path> unseen_entries = dir_contents;
109 while (!unseen_entries.empty()) {
110 assert(it != endIt);
111 const path entry = *it;
112 assert(unseen_entries.erase(entry) == 1);
113 recursive_directory_iterator& it_ref = it.increment(ec);
114 assert(!ec);
115 assert(&it_ref == &it);
116 }
117
118 assert(it == endIt);
119 }
120
test_follow_symlinks()121 static void test_follow_symlinks()
122 {
123 static_test_env static_env;
124 const path testDir = static_env.Dir;
125 auto const& IterList = static_env.RecDirFollowSymlinksIterationList;
126
127 const std::set<path> dir_contents(IterList.begin(), IterList.end());
128 const recursive_directory_iterator endIt{};
129
130 std::error_code ec;
131 recursive_directory_iterator it(testDir,
132 directory_options::follow_directory_symlink, ec);
133 assert(!ec);
134
135 std::set<path> unseen_entries = dir_contents;
136 while (!unseen_entries.empty()) {
137 assert(it != endIt);
138 const path entry = *it;
139
140 assert(unseen_entries.erase(entry) == 1);
141 recursive_directory_iterator& it_ref = it.increment(ec);
142 assert(!ec);
143 assert(&it_ref == &it);
144 }
145 assert(it == endIt);
146 }
147
148 // Windows doesn't support setting perms::none to trigger failures
149 // reading directories.
150 #ifndef TEST_WIN_NO_FILESYSTEM_PERMS_NONE
access_denied_on_recursion_test_case()151 static void access_denied_on_recursion_test_case()
152 {
153 using namespace fs;
154 scoped_test_env env;
155 const path testFiles[] = {
156 env.create_dir("dir1"),
157 env.create_dir("dir1/dir2"),
158 env.create_file("dir1/dir2/file1"),
159 env.create_file("dir1/file2")
160 };
161 const path startDir = testFiles[0];
162 const path permDeniedDir = testFiles[1];
163 const path otherFile = testFiles[3];
164 auto SkipEPerm = directory_options::skip_permission_denied;
165
166 // Change the permissions so we can no longer iterate
167 permissions(permDeniedDir, perms::none);
168
169 const recursive_directory_iterator endIt;
170
171 // Test that recursion resulting in a "EACCESS" error is not ignored
172 // by default.
173 {
174 std::error_code ec = GetTestEC();
175 recursive_directory_iterator it(startDir, ec);
176 assert(ec != GetTestEC());
177 assert(!ec);
178 while (it != endIt && it->path() != permDeniedDir)
179 ++it;
180 assert(it != endIt);
181 assert(*it == permDeniedDir);
182
183 it.increment(ec);
184 assert(ec);
185 assert(it == endIt);
186 }
187 // Same as above but test operator++().
188 {
189 std::error_code ec = GetTestEC();
190 recursive_directory_iterator it(startDir, ec);
191 assert(!ec);
192 while (it != endIt && it->path() != permDeniedDir)
193 ++it;
194 assert(it != endIt);
195 assert(*it == permDeniedDir);
196
197 TEST_THROWS_TYPE(filesystem_error, ++it);
198 }
199 // Test that recursion resulting in a "EACCESS" error is ignored when the
200 // correct options are given to the constructor.
201 {
202 std::error_code ec = GetTestEC();
203 recursive_directory_iterator it(startDir, SkipEPerm, ec);
204 assert(!ec);
205 assert(it != endIt);
206
207 bool seenOtherFile = false;
208 if (*it == otherFile) {
209 ++it;
210 seenOtherFile = true;
211 assert (it != endIt);
212 }
213 assert(*it == permDeniedDir);
214
215 ec = GetTestEC();
216 it.increment(ec);
217 assert(!ec);
218
219 if (seenOtherFile) {
220 assert(it == endIt);
221 } else {
222 assert(it != endIt);
223 assert(*it == otherFile);
224 }
225 }
226 // Test that construction resulting in a "EACCESS" error is not ignored
227 // by default.
228 {
229 std::error_code ec;
230 recursive_directory_iterator it(permDeniedDir, ec);
231 assert(ec);
232 assert(it == endIt);
233 }
234 // Same as above but testing the throwing constructors
235 {
236 TEST_THROWS_TYPE(filesystem_error,
237 recursive_directory_iterator(permDeniedDir));
238 }
239 // Test that construction resulting in a "EACCESS" error constructs the
240 // end iterator when the correct options are given.
241 {
242 std::error_code ec = GetTestEC();
243 recursive_directory_iterator it(permDeniedDir, SkipEPerm, ec);
244 assert(!ec);
245 assert(it == endIt);
246 }
247 }
248
249 // See llvm.org/PR35078
test_PR35078()250 static void test_PR35078()
251 {
252 using namespace fs;
253 scoped_test_env env;
254 const path testFiles[] = {
255 env.create_dir("dir1"),
256 env.create_dir("dir1/dir2"),
257 env.create_dir("dir1/dir2/dir3"),
258 env.create_file("dir1/file1"),
259 env.create_file("dir1/dir2/dir3/file2")
260 };
261 const path startDir = testFiles[0];
262 const path permDeniedDir = testFiles[1];
263 const path nestedDir = testFiles[2];
264 const path nestedFile = testFiles[3];
265
266 // Change the permissions so we can no longer iterate
267 permissions(permDeniedDir,
268 perms::group_exec|perms::owner_exec|perms::others_exec,
269 perm_options::remove);
270
271 const std::errc eacess = std::errc::permission_denied;
272 std::error_code ec = GetTestEC();
273
274 const recursive_directory_iterator endIt;
275
276 auto SetupState = [&](bool AllowEAccess, bool& SeenFile3) {
277 SeenFile3 = false;
278 auto Opts = AllowEAccess ? directory_options::skip_permission_denied
279 : directory_options::none;
280 recursive_directory_iterator it(startDir, Opts, ec);
281 while (!ec && it != endIt && *it != nestedDir) {
282 if (*it == nestedFile)
283 SeenFile3 = true;
284 it.increment(ec);
285 }
286 return it;
287 };
288
289 {
290 bool SeenNestedFile = false;
291 recursive_directory_iterator it = SetupState(false, SeenNestedFile);
292 assert(it != endIt);
293 assert(*it == nestedDir);
294 ec = GetTestEC();
295 it.increment(ec);
296 assert(ec);
297 assert(ErrorIs(ec, eacess));
298 assert(it == endIt);
299 }
300 {
301 bool SeenNestedFile = false;
302 recursive_directory_iterator it = SetupState(true, SeenNestedFile);
303 assert(it != endIt);
304 assert(*it == nestedDir);
305 ec = GetTestEC();
306 it.increment(ec);
307 assert(!ec);
308 if (SeenNestedFile) {
309 assert(it == endIt);
310 } else {
311 assert(it != endIt);
312 assert(*it == nestedFile);
313 }
314 }
315 {
316 bool SeenNestedFile = false;
317 recursive_directory_iterator it = SetupState(false, SeenNestedFile);
318 assert(it != endIt);
319 assert(*it == nestedDir);
320
321 ExceptionChecker Checker(std::errc::permission_denied,
322 "recursive_directory_iterator::operator++()",
323 format_string("attempting recursion into \"%s\"",
324 nestedDir.string().c_str()));
325 TEST_VALIDATE_EXCEPTION(filesystem_error, Checker, ++it);
326 }
327 }
328
329
330 // See llvm.org/PR35078
test_PR35078_with_symlink()331 static void test_PR35078_with_symlink()
332 {
333 using namespace fs;
334 scoped_test_env env;
335 const path testFiles[] = {
336 env.create_dir("dir1"),
337 env.create_file("dir1/file1"),
338 env.create_dir("sym_dir"),
339 env.create_dir("sym_dir/nested_sym_dir"),
340 env.create_directory_symlink("sym_dir/nested_sym_dir", "dir1/dir2"),
341 env.create_dir("sym_dir/dir1"),
342 env.create_dir("sym_dir/dir1/dir2"),
343
344 };
345 // const unsigned TestFilesSize = sizeof(testFiles) / sizeof(testFiles[0]);
346 const path startDir = testFiles[0];
347 const path nestedFile = testFiles[1];
348 const path permDeniedDir = testFiles[2];
349 const path symDir = testFiles[4];
350
351 // Change the permissions so we can no longer iterate
352 permissions(permDeniedDir,
353 perms::group_exec|perms::owner_exec|perms::others_exec,
354 perm_options::remove);
355
356 const std::errc eacess = std::errc::permission_denied;
357 std::error_code ec = GetTestEC();
358
359 const recursive_directory_iterator endIt;
360
361 auto SetupState = [&](bool AllowEAccess, bool FollowSym, bool& SeenFile3) {
362 SeenFile3 = false;
363 auto Opts = AllowEAccess ? directory_options::skip_permission_denied
364 : directory_options::none;
365 if (FollowSym)
366 Opts |= directory_options::follow_directory_symlink;
367 recursive_directory_iterator it(startDir, Opts, ec);
368 while (!ec && it != endIt && *it != symDir) {
369 if (*it == nestedFile)
370 SeenFile3 = true;
371 it.increment(ec);
372 }
373 return it;
374 };
375
376 struct {
377 bool SkipPermDenied;
378 bool FollowSymlinks;
379 bool ExpectSuccess;
380 } TestCases[] = {
381 // Passing cases
382 {false, false, true}, {true, true, true}, {true, false, true},
383 // Failing cases
384 {false, true, false}
385 };
386 for (auto TC : TestCases) {
387 bool SeenNestedFile = false;
388 recursive_directory_iterator it = SetupState(TC.SkipPermDenied,
389 TC.FollowSymlinks,
390 SeenNestedFile);
391 assert(!ec);
392 assert(it != endIt);
393 assert(*it == symDir);
394 ec = GetTestEC();
395 it.increment(ec);
396 if (TC.ExpectSuccess) {
397 assert(!ec);
398 if (SeenNestedFile) {
399 assert(it == endIt);
400 } else {
401 assert(it != endIt);
402 assert(*it == nestedFile);
403 }
404 } else {
405 assert(ec);
406 assert(ErrorIs(ec, eacess));
407 assert(it == endIt);
408 }
409 }
410 }
411
412
413 // See llvm.org/PR35078
test_PR35078_with_symlink_file()414 static void test_PR35078_with_symlink_file()
415 {
416 using namespace fs;
417 scoped_test_env env;
418 const path testFiles[] = {
419 env.create_dir("dir1"),
420 env.create_dir("dir1/dir2"),
421 env.create_file("dir1/file2"),
422 env.create_dir("sym_dir"),
423 env.create_dir("sym_dir/sdir1"),
424 env.create_file("sym_dir/sdir1/sfile1"),
425 env.create_symlink("sym_dir/sdir1/sfile1", "dir1/dir2/file1")
426 };
427 const unsigned TestFilesSize = sizeof(testFiles) / sizeof(testFiles[0]);
428 const path startDir = testFiles[0];
429 const path nestedDir = testFiles[1];
430 const path nestedFile = testFiles[2];
431 const path permDeniedDir = testFiles[3];
432 const path symFile = testFiles[TestFilesSize - 1];
433
434 // Change the permissions so we can no longer iterate
435 permissions(permDeniedDir,
436 perms::group_exec|perms::owner_exec|perms::others_exec,
437 perm_options::remove);
438
439 const std::errc eacess = std::errc::permission_denied;
440 std::error_code ec = GetTestEC();
441
442 const recursive_directory_iterator EndIt;
443
444 auto SetupState = [&](bool AllowEAccess, bool FollowSym, bool& SeenNestedFile) {
445 SeenNestedFile = false;
446 auto Opts = AllowEAccess ? directory_options::skip_permission_denied
447 : directory_options::none;
448 if (FollowSym)
449 Opts |= directory_options::follow_directory_symlink;
450 recursive_directory_iterator it(startDir, Opts, ec);
451 while (!ec && it != EndIt && *it != nestedDir) {
452 if (*it == nestedFile)
453 SeenNestedFile = true;
454 it.increment(ec);
455 }
456 return it;
457 };
458
459 struct {
460 bool SkipPermDenied;
461 bool FollowSymlinks;
462 bool ExpectSuccess;
463 } TestCases[] = {
464 // Passing cases
465 {false, false, true}, {true, true, true}, {true, false, true},
466 // Failing cases
467 {false, true, false}
468 };
469 for (auto TC : TestCases){
470 bool SeenNestedFile = false;
471 recursive_directory_iterator it = SetupState(TC.SkipPermDenied,
472 TC.FollowSymlinks,
473 SeenNestedFile);
474 assert(!ec);
475 assert(it != EndIt);
476 assert(*it == nestedDir);
477 ec = GetTestEC();
478 it.increment(ec);
479 assert(it != EndIt);
480 assert(!ec);
481 assert(*it == symFile);
482 ec = GetTestEC();
483 it.increment(ec);
484 if (TC.ExpectSuccess) {
485 if (!SeenNestedFile) {
486 assert(!ec);
487 assert(it != EndIt);
488 assert(*it == nestedFile);
489 ec = GetTestEC();
490 it.increment(ec);
491 }
492 assert(!ec);
493 assert(it == EndIt);
494 } else {
495 assert(ec);
496 assert(ErrorIs(ec, eacess));
497 assert(it == EndIt);
498 }
499 }
500 }
501 #endif // TEST_WIN_NO_FILESYSTEM_PERMS_NONE
502
main(int,char **)503 int main(int, char**) {
504 test_increment_signatures();
505 test_prefix_increment();
506 test_postfix_increment();
507 test_increment_method();
508 test_follow_symlinks();
509 #ifndef TEST_WIN_NO_FILESYSTEM_PERMS_NONE
510 access_denied_on_recursion_test_case();
511 test_PR35078();
512 test_PR35078_with_symlink();
513 test_PR35078_with_symlink_file();
514 #endif
515
516 return 0;
517 }
518