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