]> git.ipfire.org Git - thirdparty/ccache.git/commitdiff
Implement Util::traverse
authorJoel Rosdahl <joel@rosdahl.net>
Sat, 9 May 2020 18:56:44 +0000 (20:56 +0200)
committerJoel Rosdahl <joel@rosdahl.net>
Tue, 12 May 2020 19:13:49 +0000 (21:13 +0200)
src/Util.cpp
src/Util.hpp
unittest/test_Util.cpp

index 20e462c1842ed29e9781511e11c6e625ef8c2c38..b9987b7a59ca901c7497fcf84d3cce8b881caf21 100644 (file)
@@ -666,7 +666,53 @@ to_lowercase(const std::string& string)
   return result;
 }
 
-// Write file data from a string.
+void
+traverse(const std::string& path, const TraverseVisitor& visitor)
+{
+  DIR* dir = opendir(path.c_str());
+  if (dir) {
+    struct dirent* entry;
+    while ((entry = readdir(dir))) {
+      if (strcmp(entry->d_name, "") == 0 || strcmp(entry->d_name, ".") == 0
+          || strcmp(entry->d_name, "..") == 0) {
+        continue;
+      }
+
+      std::string entry_path = path + "/" + entry->d_name;
+      bool is_dir;
+#ifdef _DIRENT_HAVE_D_TYPE
+      if (entry->d_type != DT_UNKNOWN) {
+        is_dir = entry->d_type == DT_DIR;
+      } else
+#endif
+      {
+        auto stat = Stat::lstat(entry_path);
+        if (!stat) {
+          if (stat.error_number() == ENOENT || stat.error_number() == ESTALE) {
+            continue;
+          }
+          throw Error(fmt::format("failed to lstat {}: {}",
+                                  entry_path,
+                                  strerror(stat.error_number())));
+        }
+        is_dir = stat.is_directory();
+      }
+      if (is_dir) {
+        traverse(entry_path, visitor);
+      } else {
+        visitor(entry_path, false);
+      }
+    }
+    closedir(dir);
+    visitor(path, true);
+  } else if (errno == ENOTDIR) {
+    visitor(path, false);
+  } else {
+    throw Error(
+      fmt::format("failed to open directory {}: {}", path, strerror(errno)));
+  }
+}
+
 void
 write_file(const std::string& path, const std::string& data, bool binary)
 {
index 17220b80298fdf019db2985909f92b2cf12dd37e..c6069d51dbcfe54cf96f90aeaacdda3232dd51e6 100644 (file)
@@ -38,6 +38,8 @@ using CacheFileVisitor = std::function<void(std::shared_ptr<CacheFile>)>;
 using SubdirVisitor =
   std::function<void(const std::string& /*dir_path*/,
                      const ProgressReceiver& /*progress_receiver*/)>;
+using TraverseVisitor =
+  std::function<void(const std::string& path, bool is_dir)>;
 
 // Get base name of path.
 nonstd::string_view base_name(nonstd::string_view path);
@@ -280,6 +282,12 @@ strip_whitespace(const std::string& string);
 // Convert a string to lowercase.
 [[gnu::warn_unused_result]] std::string to_lowercase(const std::string& string);
 
+// Traverse `path` recursively (postorder, i.e. files are visited before their
+// parent directory).
+//
+// Throws Error on error.
+void traverse(const std::string& path, const TraverseVisitor& visitor);
+
 // Write file data from a string.
 //
 // Throws `Error` on error. The description contains the error message without
index 6320d93bd150ec725f28d7f3141b4a9d779e97db..f508cf54d26465f1235a182731f296b214a41fef 100644 (file)
@@ -614,3 +614,60 @@ TEST_CASE("Util::to_lowercase")
   CHECK(Util::to_lowercase("X") == "x");
   CHECK(Util::to_lowercase(" x_X@") == " x_x@");
 }
+
+TEST_CASE("Util::traverse")
+{
+  REQUIRE(Util::create_dir("traverse/dir-with-subdir-and-file/subdir"));
+  Util::write_file("traverse/dir-with-subdir-and-file/subdir/f", "");
+  REQUIRE(Util::create_dir("traverse/dir-with-files"));
+  Util::write_file("traverse/dir-with-files/f1", "");
+  Util::write_file("traverse/dir-with-files/f2", "");
+  REQUIRE(Util::create_dir("traverse/empty-dir"));
+
+  std::vector<std::string> visited;
+  auto visitor = [&visited](const std::string& path, bool is_dir) {
+    visited.push_back(fmt::format("[{}] {}", is_dir ? 'd' : 'f', path));
+  };
+
+  SECTION("traverse nonexistent path")
+  {
+    CHECK_THROWS_WITH(
+      Util::traverse("nonexistent", visitor),
+      "failed to open directory nonexistent: No such file or directory");
+  }
+
+  SECTION("traverse file")
+  {
+    CHECK_NOTHROW(
+      Util::traverse("traverse/dir-with-subdir-and-file/subdir/f", visitor));
+    REQUIRE(visited.size() == 1);
+    CHECK(visited[0] == "[f] traverse/dir-with-subdir-and-file/subdir/f");
+  }
+
+  SECTION("traverse empty directory")
+  {
+    CHECK_NOTHROW(Util::traverse("traverse/empty-dir", visitor));
+    REQUIRE(visited.size() == 1);
+    CHECK(visited[0] == "[d] traverse/empty-dir");
+  }
+
+  SECTION("traverse directory with files")
+  {
+    CHECK_NOTHROW(Util::traverse("traverse/dir-with-files", visitor));
+    REQUIRE(visited.size() == 3);
+    std::string f1 = "[f] traverse/dir-with-files/f1";
+    std::string f2 = "[f] traverse/dir-with-files/f2";
+    CHECK(((visited[0] == f1 && visited[1] == f2)
+           || (visited[0] == f2 && visited[1] == f1)));
+    CHECK(visited[2] == "[d] traverse/dir-with-files");
+  }
+
+  SECTION("traverse directory hierarchy")
+  {
+    CHECK_NOTHROW(Util::traverse("traverse/dir-with-subdir-and-file", visitor));
+    REQUIRE(visited.size() == 3);
+    CHECK(visited[0] == "[f] traverse/dir-with-subdir-and-file/subdir/f");
+    CHECK(visited[1] == "[d] traverse/dir-with-subdir-and-file/subdir");
+    CHECK(visited[2] == "[d] traverse/dir-with-subdir-and-file");
+  }
+}