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