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