From 0f4b17e51f556cd684c52f46c1218cb10e3da3f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Thalheim?= Date: Thu, 1 May 2025 09:50:53 +0200 Subject: [PATCH] add DirectoryIterator to re-throw std::filesystem::filesystem_error Co-authored-by: Sergei Zimmerman <145775305+xokdvium@users.noreply.github.com> (cherry picked from commit 7ccc0d591f3eb879af8eae95c994405a1172d210) --- src/libutil-tests/file-system.cc | 22 ++++++ src/libutil/file-system.cc | 28 ++++++++ src/libutil/include/nix/util/file-system.hh | 76 +++++++++++++++++++++ 3 files changed, 126 insertions(+) diff --git a/src/libutil-tests/file-system.cc b/src/libutil-tests/file-system.cc index 8c9eccc11..a08d825b5 100644 --- a/src/libutil-tests/file-system.cc +++ b/src/libutil-tests/file-system.cc @@ -275,4 +275,26 @@ TEST(makeParentCanonical, root) { ASSERT_EQ(makeParentCanonical("/"), "/"); } + +/* ---------------------------------------------------------------------------- + * DirectoryIterator + * --------------------------------------------------------------------------*/ + +TEST(DirectoryIterator, works) +{ + auto tmpDir = nix::createTempDir(); + nix::AutoDelete delTmpDir(tmpDir, true); + + nix::writeFile(tmpDir + "/somefile", ""); + + for (auto path : DirectoryIterator(tmpDir)) { + ASSERT_EQ(path.path().string(), tmpDir + "/somefile"); + } +} + +TEST(DirectoryIterator, nonexistent) +{ + ASSERT_THROW(DirectoryIterator("/schnitzel/darmstadt/pommes"), SysError); +} + } diff --git a/src/libutil/file-system.cc b/src/libutil/file-system.cc index 6fb797103..87a908bb7 100644 --- a/src/libutil/file-system.cc +++ b/src/libutil/file-system.cc @@ -43,6 +43,34 @@ namespace fs { } } +DirectoryIterator::DirectoryIterator(const std::filesystem::path& p) { + try { + // **Attempt to create the underlying directory_iterator** + it_ = std::filesystem::directory_iterator(p); + } catch (const std::filesystem::filesystem_error& e) { + // **Catch filesystem_error and throw SysError** + // Adapt the error message as needed for SysError + throw SysError("cannot read directory %s", p); + } +} + +DirectoryIterator& DirectoryIterator::operator++() { + // **Attempt to increment the underlying iterator** + std::error_code ec; + it_.increment(ec); + if (ec) { + // Try to get path info if possible, might fail if iterator is bad + try { + if (it_ != std::filesystem::directory_iterator{}) { + throw SysError("cannot read directory past %s: %s", it_->path(), ec.message()); + } + } catch (...) { + throw SysError("cannot read directory"); + } + } + return *this; +} + bool isAbsolute(PathView path) { return fs::path { path }.is_absolute(); diff --git a/src/libutil/include/nix/util/file-system.hh b/src/libutil/include/nix/util/file-system.hh index acae88306..e6b1cfef3 100644 --- a/src/libutil/include/nix/util/file-system.hh +++ b/src/libutil/include/nix/util/file-system.hh @@ -360,4 +360,80 @@ typedef std::function PathFilter; extern PathFilter defaultPathFilter; +/** + * Change permissions of a file only if necessary. + * + * @details + * Skip chmod call if the directory already has the requested permissions. + * This is to avoid failing when the executing user lacks permissions to change the + * directory's permissions even if it would be no-op. + * + * @param path Path to the file to change the permissions for. + * @param mode New file mode. + * @param mask Used for checking if the file already has requested permissions. + * + * @return true if permissions changed, false otherwise. + */ +bool chmodIfNeeded(const std::filesystem::path & path, mode_t mode, mode_t mask = S_IRWXU | S_IRWXG | S_IRWXO); + +/** + * @brief A directory iterator that can be used to iterate over the + * contents of a directory. It is similar to std::filesystem::directory_iterator + * but throws NixError on failure instead of std::filesystem::filesystem_error. + */ +class DirectoryIterator { +public: + // --- Iterator Traits --- + using iterator_category = std::input_iterator_tag; + using value_type = std::filesystem::directory_entry; + using difference_type = std::ptrdiff_t; + using pointer = const std::filesystem::directory_entry*; + using reference = const std::filesystem::directory_entry&; + + // Default constructor (represents end iterator) + DirectoryIterator() noexcept = default; + + // Constructor taking a path + explicit DirectoryIterator(const std::filesystem::path& p); + + reference operator*() const { + // Accessing the value itself doesn't typically throw filesystem_error + // after successful construction/increment, but underlying operations might. + // If directory_entry methods called via -> could throw, add try-catch there. + return *it_; + } + + pointer operator->() const { + return &(*it_); + } + + + DirectoryIterator& operator++(); + + // Postfix increment operator + DirectoryIterator operator++(int) { + DirectoryIterator temp = *this; + ++(*this); // Uses the prefix increment's try-catch logic + return temp; + } + + // Equality comparison + friend bool operator==(const DirectoryIterator& a, const DirectoryIterator& b) noexcept { + return a.it_ == b.it_; + } + + // Inequality comparison + friend bool operator!=(const DirectoryIterator& a, const DirectoryIterator& b) noexcept { + return !(a == b); + } + + // Allow direct use in range-based for loops if iterating over an instance + DirectoryIterator begin() const { return *this; } + DirectoryIterator end() const { return DirectoryIterator{}; } + + +private: + std::filesystem::directory_iterator it_; +}; + }