]> git.ipfire.org Git - thirdparty/ccache.git/commitdiff
refactor: Move Util::traverse to util
authorJoel Rosdahl <joel@rosdahl.net>
Thu, 27 Jul 2023 13:37:10 +0000 (15:37 +0200)
committerJoel Rosdahl <joel@rosdahl.net>
Wed, 2 Aug 2023 11:36:34 +0000 (13:36 +0200)
src/Util.cpp
src/Util.hpp
src/core/mainoptions.cpp
src/storage/local/LocalStorage.cpp
src/storage/local/util.cpp
src/util/file.cpp
src/util/file.hpp
unittest/test_Util.cpp
unittest/test_util_file.cpp

index d2c8b5cc83f127764b7d215c1bfcbe044a65f656..ec0a17a7d995d18d6897ce4f8f4518f55d87282e 100644 (file)
 #include <util/path.hpp>
 #include <util/string.hpp>
 
-#ifdef HAVE_DIRENT_H
-#  include <dirent.h>
-#endif
-
 #ifdef HAVE_UNISTD_H
 #  include <unistd.h>
 #endif
@@ -385,79 +381,4 @@ remove_extension(std::string_view path)
   return path.substr(0, path.length() - get_extension(path).length());
 }
 
-#ifdef HAVE_DIRENT_H
-
-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 core::Error(FMT("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 core::Error(
-      FMT("failed to open directory {}: {}", path, strerror(errno)));
-  }
-}
-
-#else // If not available, use the C++17 std::filesystem implementation.
-
-void
-traverse(const std::string& path, const TraverseVisitor& visitor)
-{
-  if (fs::is_directory(path)) {
-    for (auto&& p : fs::directory_iterator(path)) {
-      std::string entry = p.path().string();
-
-      if (p.is_directory()) {
-        traverse(entry, visitor);
-      } else {
-        visitor(entry, false);
-      }
-    }
-    visitor(path, true);
-  } else if (fs::exists(path)) {
-    visitor(path, false);
-  } else {
-    throw core::Error(
-      FMT("failed to open directory {}: {}", path, strerror(errno)));
-  }
-}
-
-#endif
-
 } // namespace Util
index 717b371b574557a229c2cb3c8d5e9903aa90d0d4..6648cb7d6fbc71c6de0a05784433f765770f71be 100644 (file)
@@ -35,9 +35,6 @@ class Context;
 
 namespace Util {
 
-using TraverseVisitor =
-  std::function<void(const std::string& path, bool is_dir)>;
-
 // Get base name of path.
 std::string_view base_name(std::string_view path);
 
@@ -123,10 +120,4 @@ std::string normalize_concrete_absolute_path(const std::string& path);
 // extension as determined by `get_extension()`.
 std::string_view remove_extension(std::string_view path);
 
-// Traverse `path` recursively (postorder, i.e. files are visited before their
-// parent directory).
-//
-// Throws core::Error on error.
-void traverse(const std::string& path, const TraverseVisitor& visitor);
-
 } // namespace Util
index 46370a41e5bb45fb77a8fc259f6b4d1779ff7f3a..2cfc8e50fb944337c8a3deddae34c640290c329c 100644 (file)
@@ -300,23 +300,24 @@ trim_dir(const std::string& dir,
   std::vector<Stat> files;
   uint64_t initial_size = 0;
 
-  Util::traverse(dir, [&](const std::string& path, const bool is_dir) {
-    if (is_dir || TemporaryFile::is_tmp_file(path)) {
-      return;
-    }
-    auto stat = Stat::lstat(path);
-    if (!stat) {
-      // Probably some race, ignore.
-      return;
-    }
-    initial_size += stat.size_on_disk();
-    const auto name = Util::base_name(path);
-    if (name == "ccache.conf" || name == "stats") {
-      throw Fatal(
-        FMT("this looks like a local cache directory (found {})", path));
-    }
-    files.emplace_back(std::move(stat));
-  });
+  util::throw_on_error<core::Error>(util::traverse_directory(
+    dir, [&](const std::string& path, const bool is_dir) {
+      if (is_dir || TemporaryFile::is_tmp_file(path)) {
+        return;
+      }
+      auto stat = Stat::lstat(path);
+      if (!stat) {
+        // Probably some race, ignore.
+        return;
+      }
+      initial_size += stat.size_on_disk();
+      const auto name = Util::base_name(path);
+      if (name == "ccache.conf" || name == "stats") {
+        throw Fatal(
+          FMT("this looks like a local cache directory (found {})", path));
+      }
+      files.emplace_back(std::move(stat));
+    }));
 
   std::sort(files.begin(), files.end(), [&](const auto& f1, const auto& f2) {
     return trim_lru_mtime ? f1.mtime() < f2.mtime() : f1.atime() < f2.atime();
index d0a904e5532c0702b54cec0705e6820a28d9fbcf..96963c35b8b9cd3d776314a86d11205ad9e92c50 100644 (file)
@@ -1418,16 +1418,20 @@ LocalStorage::clean_internal_tempdir()
 
   LOG("Cleaning up {}", m_config.temporary_dir());
   core::ensure_dir_exists(m_config.temporary_dir());
-  Util::traverse(m_config.temporary_dir(),
-                 [now](const std::string& path, bool is_dir) {
-                   if (is_dir) {
-                     return;
-                   }
-                   const auto st = Stat::lstat(path, Stat::OnError::log);
-                   if (st && st.mtime() + k_tempdir_cleanup_interval < now) {
-                     util::remove(path);
-                   }
-                 });
+  util::traverse_directory(
+    m_config.temporary_dir(),
+    [now](const std::string& path, bool is_dir) {
+      if (is_dir) {
+        return;
+      }
+      const auto st = Stat::lstat(path, Stat::OnError::log);
+      if (st && st.mtime() + k_tempdir_cleanup_interval < now) {
+        util::remove(path);
+      }
+    })
+    .or_else([&](const auto& error) {
+      LOG("Failed to clean up {}: {}", m_config.temporary_dir(), error);
+    });
 
   util::write_file(cleaned_stamp, "");
 }
index f994daef872d42c9e288c9fc4afc5527b34b17b2..6649b1f11a747efd13435fc6d43fc8d66b2283d4 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2021-2022 Joel Rosdahl and other contributors
+// Copyright (C) 2021-2023 Joel Rosdahl and other contributors
 //
 // See doc/AUTHORS.adoc for a complete list of contributors.
 //
 #include "util.hpp"
 
 #include <Util.hpp>
+#include <core/exceptions.hpp>
 #include <fmtmacros.hpp>
+#include <util/expected.hpp>
+#include <util/file.hpp>
 #include <util/string.hpp>
 
 namespace storage::local {
@@ -67,18 +70,18 @@ get_cache_dir_files(const std::string& dir)
   if (!Stat::stat(dir)) {
     return files;
   }
+  util::throw_on_error<core::Error>(
+    util::traverse_directory(dir, [&](const std::string& path, bool is_dir) {
+      auto name = Util::base_name(path);
+      if (name == "CACHEDIR.TAG" || name == "stats"
+          || util::starts_with(std::string(name), ".nfs")) {
+        return;
+      }
 
-  Util::traverse(dir, [&](const std::string& path, bool is_dir) {
-    auto name = Util::base_name(path);
-    if (name == "CACHEDIR.TAG" || name == "stats"
-        || util::starts_with(name, ".nfs")) {
-      return;
-    }
-
-    if (!is_dir) {
-      files.emplace_back(Stat::lstat(path));
-    }
-  });
+      if (!is_dir) {
+        files.emplace_back(Stat::lstat(path));
+      }
+    }));
 
   return files;
 }
index 4ae2c00a6e24a9a2b855de4f52571567c4f15a85..604ec28a1095063c86792dd645165998fd745c4c 100644 (file)
 #include <sys/stat.h>
 #include <sys/types.h>
 
+#ifdef HAVE_DIRENT_H
+#  include <dirent.h>
+#endif
+
 #include <cerrno>
 #include <cstring>
-#include <filesystem>
 #include <fstream>
 #include <locale>
 #include <type_traits>
@@ -467,6 +470,91 @@ set_timestamps(const std::string& path,
 #endif
 }
 
+#ifdef HAVE_DIRENT_H
+
+tl::expected<void, std::string>
+traverse_directory(const std::string& directory,
+                   const TraverseDirectoryVisitor& visitor)
+{
+  DIR* dir = opendir(directory.c_str());
+  if (!dir) {
+    return tl::unexpected(
+      FMT("Failed to traverse {}: {}", directory, strerror(errno)));
+  }
+
+  Finalizer dir_closer([&] { closedir(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 = directory + "/" + 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;
+        }
+        return tl::unexpected(FMT(
+          "Failed to lstat {}: {}", entry_path, strerror(stat.error_number())));
+      }
+      is_dir = stat.is_directory();
+    }
+    if (is_dir) {
+      traverse_directory(entry_path, visitor);
+    } else {
+      visitor(entry_path, false);
+    }
+  }
+  visitor(directory, true);
+
+  return {};
+}
+
+#else // If not available, use the C++17 std::filesystem implementation.
+
+tl::expected<void, std::string>
+traverse_directory(const std::string& directory,
+                   const TraverseDirectoryVisitor& visitor)
+{
+  // Note: Intentionally not using std::filesystem::recursive_directory_iterator
+  // since it visits directories in preorder.
+
+  auto stat = Stat::lstat(directory);
+  if (!stat.is_directory()) {
+    return tl::unexpected(
+      FMT("Failed to traverse {}: {}",
+          directory,
+          stat ? "Not a directory" : "No such file or directory"));
+  }
+
+  try {
+    for (const auto& entry : std::filesystem::directory_iterator(directory)) {
+      if (entry.is_directory()) {
+        traverse_directory(entry.path().string(), visitor);
+      } else {
+        visitor(entry.path().string(), entry.is_directory());
+      }
+    }
+    visitor(directory, true);
+  } catch (const std::filesystem::filesystem_error& e) {
+    return tl::unexpected(e.what());
+  }
+
+  return {};
+}
+
+#endif
+
 tl::expected<void, std::string>
 write_fd(int fd, const void* data, size_t size)
 {
index 54fd574d5266f5e5fc403f61e4f56e07f134b2b9..50e3afb299c6dbd2a8ca3d58b57524d71d18300b 100644 (file)
@@ -28,6 +28,7 @@
 #include <cstddef>
 #include <cstdint>
 #include <ctime>
+#include <functional>
 #include <optional>
 #include <string>
 #include <string_view>
@@ -41,6 +42,9 @@ enum class InPlace { yes, no };
 enum class LogFailure { yes, no };
 enum class ViaTmpFile { yes, no };
 
+using TraverseDirectoryVisitor =
+  std::function<void(const std::string& path, bool is_dir)>;
+
 // Copy a file from `src` to `dest`. If `via_tmp_file` is yes, `src` is copied
 // to a temporary file and then renamed to dest.
 tl::expected<void, std::string>
@@ -117,6 +121,12 @@ void set_timestamps(const std::string& path,
                     std::optional<TimePoint> mtime = std::nullopt,
                     std::optional<TimePoint> atime = std::nullopt);
 
+// Traverse `path` recursively in postorder (directory entries are visited
+// before their parent directory).
+tl::expected<void, std::string>
+traverse_directory(const std::string& directory,
+                   const TraverseDirectoryVisitor& visitor);
+
 // Write `size` bytes from binary `data` to `fd`.
 tl::expected<void, std::string> write_fd(int fd, const void* data, size_t size);
 
index 43a64870047c02e0d3e8bfc6d954f360b6f3113e..c7f27265559fbef0f3a2d7dae7ed5cbd66b5c480 100644 (file)
@@ -128,16 +128,6 @@ TEST_CASE("Util::get_extension")
   CHECK(Util::get_extension("/foo/bar/f.abc.txt") == ".txt");
 }
 
-static inline std::string
-os_path(std::string path)
-{
-#if defined(_WIN32) && !defined(HAVE_DIRENT_H)
-  std::replace(path.begin(), path.end(), '/', '\\');
-#endif
-
-  return path;
-}
-
 TEST_CASE("Util::get_relative_path")
 {
 #ifdef _WIN32
@@ -357,62 +347,4 @@ TEST_CASE("Util::remove_extension")
   CHECK(Util::remove_extension("/foo/bar/f.abc.txt") == "/foo/bar/f.abc");
 }
 
-TEST_CASE("Util::traverse")
-{
-  TestContext test_context;
-
-  REQUIRE(fs::create_directories("dir-with-subdir-and-file/subdir"));
-  util::write_file("dir-with-subdir-and-file/subdir/f", "");
-  REQUIRE(fs::create_directory("dir-with-files"));
-  util::write_file("dir-with-files/f1", "");
-  util::write_file("dir-with-files/f2", "");
-  REQUIRE(fs::create_directory("empty-dir"));
-
-  std::vector<std::string> visited;
-  auto visitor = [&visited](const std::string& path, bool is_dir) {
-    visited.push_back(FMT("[{}] {}", is_dir ? 'd' : 'f', path));
-  };
-
-  SUBCASE("traverse nonexistent path")
-  {
-    CHECK_THROWS_WITH(
-      Util::traverse("nonexistent", visitor),
-      "failed to open directory nonexistent: No such file or directory");
-  }
-
-  SUBCASE("traverse file")
-  {
-    CHECK_NOTHROW(Util::traverse("dir-with-subdir-and-file/subdir/f", visitor));
-    REQUIRE(visited.size() == 1);
-    CHECK(visited[0] == "[f] dir-with-subdir-and-file/subdir/f");
-  }
-
-  SUBCASE("traverse empty directory")
-  {
-    CHECK_NOTHROW(Util::traverse("empty-dir", visitor));
-    REQUIRE(visited.size() == 1);
-    CHECK(visited[0] == "[d] empty-dir");
-  }
-
-  SUBCASE("traverse directory with files")
-  {
-    CHECK_NOTHROW(Util::traverse("dir-with-files", visitor));
-    REQUIRE(visited.size() == 3);
-    std::string f1 = os_path("[f] dir-with-files/f1");
-    std::string f2 = os_path("[f] dir-with-files/f2");
-    CHECK(((visited[0] == f1 && visited[1] == f2)
-           || (visited[0] == f2 && visited[1] == f1)));
-    CHECK(visited[2] == "[d] dir-with-files");
-  }
-
-  SUBCASE("traverse directory hierarchy")
-  {
-    CHECK_NOTHROW(Util::traverse("dir-with-subdir-and-file", visitor));
-    REQUIRE(visited.size() == 3);
-    CHECK(visited[0] == os_path("[f] dir-with-subdir-and-file/subdir/f"));
-    CHECK(visited[1] == os_path("[d] dir-with-subdir-and-file/subdir"));
-    CHECK(visited[2] == "[d] dir-with-subdir-and-file");
-  }
-}
-
 TEST_SUITE_END();
index d864681d8fcb4acb8ccc1597b2126e5e35d7e6ef..9e85441c7098cb8175ae4ac06f3497311c5b147c 100644 (file)
 
 #include <Fd.hpp>
 #include <Stat.hpp>
+#include <fmtmacros.hpp>
 #include <util/Bytes.hpp>
 #include <util/file.hpp>
+#include <util/filesystem.hpp>
 #include <util/string.hpp>
 
 #include <third_party/doctest.h>
 #include <string_view>
 #include <vector>
 
+namespace fs = util::filesystem;
+
 using TestUtil::TestContext;
 
+namespace {
+
+std::string
+os_path(std::string path)
+{
+#if defined(_WIN32) && !defined(HAVE_DIRENT_H)
+  std::replace(path.begin(), path.end(), '/', '\\');
+#endif
+
+  return path;
+}
+
+} // namespace
+
 TEST_CASE("util::fallocate")
 {
   TestContext test_context;
@@ -198,3 +216,63 @@ TEST_CASE("util::read_file_part")
     CHECK(*data == "an");
   }
 }
+
+TEST_CASE("util::traverse_directory")
+{
+  TestContext test_context;
+
+  REQUIRE(fs::create_directories("dir-with-subdir-and-file/subdir"));
+  util::write_file("dir-with-subdir-and-file/subdir/f", "");
+  REQUIRE(fs::create_directory("dir-with-files"));
+  util::write_file("dir-with-files/f1", "");
+  util::write_file("dir-with-files/f2", "");
+  REQUIRE(fs::create_directory("empty-dir"));
+
+  std::vector<std::string> visited;
+  auto visitor = [&visited](const std::string& path, bool is_dir) {
+    visited.push_back(FMT("[{}] {}", is_dir ? 'd' : 'f', path));
+  };
+
+  SUBCASE("traverse nonexistent path")
+  {
+    CHECK(util::traverse_directory("nonexistent", visitor).error()
+          == "Failed to traverse nonexistent: No such file or directory");
+    CHECK(visited.size() == 0);
+  }
+
+  SUBCASE("traverse file")
+  {
+    CHECK(util::traverse_directory("dir-with-subdir-and-file/subdir/f", visitor)
+            .error()
+          == "Failed to traverse dir-with-subdir-and-file/subdir/f: Not a directory");
+    CHECK(visited.size() == 0);
+  }
+
+  SUBCASE("traverse empty directory")
+  {
+    CHECK_NOTHROW(util::traverse_directory("empty-dir", visitor));
+    REQUIRE(visited.size() == 1);
+    CHECK(visited[0] == "[d] empty-dir");
+  }
+
+  SUBCASE("traverse directory with files")
+  {
+    CHECK_NOTHROW(util::traverse_directory("dir-with-files", visitor));
+    REQUIRE(visited.size() == 3);
+    std::string f1 = os_path("[f] dir-with-files/f1");
+    std::string f2 = os_path("[f] dir-with-files/f2");
+    CHECK(((visited[0] == f1 && visited[1] == f2)
+           || (visited[0] == f2 && visited[1] == f1)));
+    CHECK(visited[2] == "[d] dir-with-files");
+  }
+
+  SUBCASE("traverse directory hierarchy")
+  {
+    CHECK_NOTHROW(
+      util::traverse_directory("dir-with-subdir-and-file", visitor));
+    REQUIRE(visited.size() == 3);
+    CHECK(visited[0] == os_path("[f] dir-with-subdir-and-file/subdir/f"));
+    CHECK(visited[1] == os_path("[d] dir-with-subdir-and-file/subdir"));
+    CHECK(visited[2] == "[d] dir-with-subdir-and-file");
+  }
+}