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