From 5f874eb29cb052854ce98bf876af095a4eafb6e3 Mon Sep 17 00:00:00 2001 From: Pierrick Roger Date: Thu, 10 Jul 2025 13:32:20 +0200 Subject: [PATCH 1/7] Add a function to check permissions on a path. --- libmamba/include/mamba/fs/filesystem.hpp | 3 + libmamba/src/fs/filesystem.cpp | 73 ++++++++++++++++++++++++ 2 files changed, 76 insertions(+) diff --git a/libmamba/include/mamba/fs/filesystem.hpp b/libmamba/include/mamba/fs/filesystem.hpp index c1fbbd0292..8a90ce73a8 100644 --- a/libmamba/include/mamba/fs/filesystem.hpp +++ b/libmamba/include/mamba/fs/filesystem.hpp @@ -1139,6 +1139,9 @@ namespace mamba::fs return std::filesystem::last_write_time(path, new_time, std::forward(args)...); } + // Check if we have modification rights on a path + bool has_modification_rights_on(const u8path& path); + // void permissions(const path& p, perms prms, perm_options opts = perm_options::replace); // void permissions(const path& p, perms prms, error_code& ec) noexcept; // void permissions(const path& p, perms prms, perm_options opts, error_code& ec); diff --git a/libmamba/src/fs/filesystem.cpp b/libmamba/src/fs/filesystem.cpp index 40491bdb68..0fc4838fcf 100644 --- a/libmamba/src/fs/filesystem.cpp +++ b/libmamba/src/fs/filesystem.cpp @@ -12,7 +12,10 @@ #ifndef _WIN32 #include +#include +#include #include +#include // We can use the presence of UTIME_OMIT to detect platforms that provide // utimensat. #if defined(UTIME_OMIT) @@ -79,4 +82,74 @@ namespace mamba::fs std::filesystem::last_write_time(path, new_time, ec); #endif } + + + bool has_modification_rights_on(const u8path& path) { + +#if defined(_WIN32) // All Windows platforms + return true; + +#else // UNIX-like platforms + + using std::filesystem::perms; + + // Get permissions + auto perm = std::filesystem::status(path).permissions(); + + // Everybody can modify + if (perms::none != (perm & perms::others_write)) + return true; + + // Check user and/or group + if (perms::none != (perm & (perms::owner_write | perms::group_write))) { + + // Get path status + struct stat info; + if (stat(path.string().c_str(), &info)) + throw std::runtime_error("Unable to get status information for" + " path " + path.string() + ": " + + strerror(errno)); + + // Check user rights + if (perms::none != (perm & perms::owner_write) + && info.st_uid == geteuid()) + return true; + + // Check group rights + if (perms::none != (perm & perms::group_write)) { + + // Check main group + if (info.st_gid == getegid()) + return true; + + // Get user name + auto pwd = getpwuid(geteuid()); + if (! pwd) + throw std::runtime_error("Unable to get user name from user" + " ID " + + std::to_string(info.st_uid) + + ": " + strerror(errno)); + + // Get first group IDs + std::vector groups(16); + int ngroups = groups.size(); + int n = getgrouplist(pwd->pw_name, info.st_gid, + groups.data(), &ngroups); + if (n < 0) { + groups.resize(static_cast(n)); + static_cast(getgrouplist(pwd->pw_name, info.st_gid, + groups.data(), &ngroups)); + } + else + groups.resize(static_cast(ngroups)); + + // Find if file group is in list + auto it = std::find(groups.begin(), groups.end(), info.st_uid); + if (it != groups.end()) + return true; + } + } + return false; +#endif + } } From 9df64df480d8921949ba64d57a72536a2b30bd70 Mon Sep 17 00:00:00 2001 From: Pierrick Roger Date: Thu, 10 Jul 2025 14:41:50 +0200 Subject: [PATCH 2/7] Do not set permissions if already set --- libmamba/include/mamba/core/util.hpp | 8 +-- libmamba/include/mamba/fs/filesystem.hpp | 2 +- libmamba/src/core/subdir_index.cpp | 7 ++- libmamba/src/fs/filesystem.cpp | 70 ++---------------------- 4 files changed, 15 insertions(+), 72 deletions(-) diff --git a/libmamba/include/mamba/core/util.hpp b/libmamba/include/mamba/core/util.hpp index 6bc424ac4c..6f56481abc 100644 --- a/libmamba/include/mamba/core/util.hpp +++ b/libmamba/include/mamba/core/util.hpp @@ -62,10 +62,10 @@ namespace mamba inline void make_executable(const fs::u8path& p) { - fs::permissions( - p, - fs::perms::owner_all | fs::perms::group_all | fs::perms::others_read | fs::perms::others_exec - ); + const auto permissions = fs::perms::owner_all | fs::perms::group_all | + fs::perms::others_read | fs::perms::others_exec; + if (! fs::has_permissions(p, permissions)) + fs::permissions(p, permissions); } // @return `true` if `TemporaryFile` will not delete files once destroy. diff --git a/libmamba/include/mamba/fs/filesystem.hpp b/libmamba/include/mamba/fs/filesystem.hpp index 8a90ce73a8..65fcb5f158 100644 --- a/libmamba/include/mamba/fs/filesystem.hpp +++ b/libmamba/include/mamba/fs/filesystem.hpp @@ -1140,7 +1140,7 @@ namespace mamba::fs } // Check if we have modification rights on a path - bool has_modification_rights_on(const u8path& path); + bool has_permissions(const u8path&, fs::perms const&); // void permissions(const path& p, perms prms, perm_options opts = perm_options::replace); // void permissions(const path& p, perms prms, error_code& ec) noexcept; diff --git a/libmamba/src/core/subdir_index.cpp b/libmamba/src/core/subdir_index.cpp index 70d80a3a2d..3599739b55 100644 --- a/libmamba/src/core/subdir_index.cpp +++ b/libmamba/src/core/subdir_index.cpp @@ -1135,8 +1135,11 @@ namespace mamba const auto permissions = fs::perms::owner_all | fs::perms::group_all | fs::perms::others_read | fs::perms::others_exec; - fs::permissions(cache_dir, permissions, fs::perm_options::replace); - LOG_TRACE << "Set permissions on cache directory " << cache_dir << " to 'rwxrwxr-x'"; + if (! fs::has_permissions(cache_dir, permissions)) { + fs::permissions(cache_dir, permissions, fs::perm_options::replace); + LOG_TRACE << "Set permissions on cache directory " << cache_dir + << " to 'rwxrwxr-x'"; + } std::error_code ec; fs::permissions(cache_dir, fs::perms::set_gid, fs::perm_options::add, ec); diff --git a/libmamba/src/fs/filesystem.cpp b/libmamba/src/fs/filesystem.cpp index 0fc4838fcf..7b5446074e 100644 --- a/libmamba/src/fs/filesystem.cpp +++ b/libmamba/src/fs/filesystem.cpp @@ -84,72 +84,12 @@ namespace mamba::fs } - bool has_modification_rights_on(const u8path& path) { + bool has_permissions(const u8path& path, fs::perms const& perm) { -#if defined(_WIN32) // All Windows platforms - return true; + // Get path permissions + auto p = std::filesystem::status(path).permissions(); -#else // UNIX-like platforms - - using std::filesystem::perms; - - // Get permissions - auto perm = std::filesystem::status(path).permissions(); - - // Everybody can modify - if (perms::none != (perm & perms::others_write)) - return true; - - // Check user and/or group - if (perms::none != (perm & (perms::owner_write | perms::group_write))) { - - // Get path status - struct stat info; - if (stat(path.string().c_str(), &info)) - throw std::runtime_error("Unable to get status information for" - " path " + path.string() + ": " - + strerror(errno)); - - // Check user rights - if (perms::none != (perm & perms::owner_write) - && info.st_uid == geteuid()) - return true; - - // Check group rights - if (perms::none != (perm & perms::group_write)) { - - // Check main group - if (info.st_gid == getegid()) - return true; - - // Get user name - auto pwd = getpwuid(geteuid()); - if (! pwd) - throw std::runtime_error("Unable to get user name from user" - " ID " - + std::to_string(info.st_uid) - + ": " + strerror(errno)); - - // Get first group IDs - std::vector groups(16); - int ngroups = groups.size(); - int n = getgrouplist(pwd->pw_name, info.st_gid, - groups.data(), &ngroups); - if (n < 0) { - groups.resize(static_cast(n)); - static_cast(getgrouplist(pwd->pw_name, info.st_gid, - groups.data(), &ngroups)); - } - else - groups.resize(static_cast(ngroups)); - - // Find if file group is in list - auto it = std::find(groups.begin(), groups.end(), info.st_uid); - if (it != groups.end()) - return true; - } - } - return false; -#endif + // Path perms must include wanted perms + return perm == (p & perm); } } From c2279d14177fe1d456c0840ba948b18b6c095051 Mon Sep 17 00:00:00 2001 From: Pierrick Roger Date: Thu, 27 Nov 2025 11:38:00 +0100 Subject: [PATCH 3/7] Accept clang linting --- libmamba/include/mamba/core/util.hpp | 10 ++++++---- libmamba/include/mamba/fs/filesystem.hpp | 2 +- libmamba/src/core/subdir_index.cpp | 6 +++--- libmamba/src/fs/filesystem.cpp | 5 ++--- 4 files changed, 12 insertions(+), 11 deletions(-) diff --git a/libmamba/include/mamba/core/util.hpp b/libmamba/include/mamba/core/util.hpp index 6f56481abc..eb7a9c1f8c 100644 --- a/libmamba/include/mamba/core/util.hpp +++ b/libmamba/include/mamba/core/util.hpp @@ -62,10 +62,12 @@ namespace mamba inline void make_executable(const fs::u8path& p) { - const auto permissions = fs::perms::owner_all | fs::perms::group_all | - fs::perms::others_read | fs::perms::others_exec; - if (! fs::has_permissions(p, permissions)) - fs::permissions(p, permissions); + const auto permissions = fs::perms::owner_all | fs::perms::group_all + | fs::perms::others_read | fs::perms::others_exec; + if (!fs::has_permissions(p, permissions)) + { + fs::permissions(p, permissions); + } } // @return `true` if `TemporaryFile` will not delete files once destroy. diff --git a/libmamba/include/mamba/fs/filesystem.hpp b/libmamba/include/mamba/fs/filesystem.hpp index 65fcb5f158..09cb64c1a4 100644 --- a/libmamba/include/mamba/fs/filesystem.hpp +++ b/libmamba/include/mamba/fs/filesystem.hpp @@ -1140,7 +1140,7 @@ namespace mamba::fs } // Check if we have modification rights on a path - bool has_permissions(const u8path&, fs::perms const&); + bool has_permissions(const u8path&, const fs::perms&); // void permissions(const path& p, perms prms, perm_options opts = perm_options::replace); // void permissions(const path& p, perms prms, error_code& ec) noexcept; diff --git a/libmamba/src/core/subdir_index.cpp b/libmamba/src/core/subdir_index.cpp index 3599739b55..7010b952ce 100644 --- a/libmamba/src/core/subdir_index.cpp +++ b/libmamba/src/core/subdir_index.cpp @@ -1135,10 +1135,10 @@ namespace mamba const auto permissions = fs::perms::owner_all | fs::perms::group_all | fs::perms::others_read | fs::perms::others_exec; - if (! fs::has_permissions(cache_dir, permissions)) { + if (!fs::has_permissions(cache_dir, permissions)) + { fs::permissions(cache_dir, permissions, fs::perm_options::replace); - LOG_TRACE << "Set permissions on cache directory " << cache_dir - << " to 'rwxrwxr-x'"; + LOG_TRACE << "Set permissions on cache directory " << cache_dir << " to 'rwxrwxr-x'"; } std::error_code ec; diff --git a/libmamba/src/fs/filesystem.cpp b/libmamba/src/fs/filesystem.cpp index 7b5446074e..3797df1cf3 100644 --- a/libmamba/src/fs/filesystem.cpp +++ b/libmamba/src/fs/filesystem.cpp @@ -83,9 +83,8 @@ namespace mamba::fs #endif } - - bool has_permissions(const u8path& path, fs::perms const& perm) { - + bool has_permissions(const u8path& path, const fs::perms& perm) + { // Get path permissions auto p = std::filesystem::status(path).permissions(); From 34104fd80f81b4ea3b831ce70181748d54db6825 Mon Sep 17 00:00:00 2001 From: Pierrick Roger Date: Thu, 27 Nov 2025 14:46:57 +0100 Subject: [PATCH 4/7] Test has_permissions() and make_executable() --- libmamba/tests/src/core/test_filesystem.cpp | 85 +++++++++++++++++++++ 1 file changed, 85 insertions(+) diff --git a/libmamba/tests/src/core/test_filesystem.cpp b/libmamba/tests/src/core/test_filesystem.cpp index 9ba979b0e0..1da8a42446 100644 --- a/libmamba/tests/src/core/test_filesystem.cpp +++ b/libmamba/tests/src/core/test_filesystem.cpp @@ -293,6 +293,91 @@ namespace mamba } } + namespace { + TEST_CASE("has_permissions()") + { + // Create temp folder + const auto tmp_dir = fs::temp_directory_path() + / "mamba-fs-has_permissions"; + fs::create_directories(tmp_dir); + + // Create file + const auto some_file = tmp_dir / "some_file"; + { + std::ofstream ofs{ some_file.std_path(), + std::ofstream::binary + | std::ofstream::trunc }; + ofs << "ABC" << std::endl; + } + + // Set permissions + const auto perms = fs::perms::owner_read | fs::perms::owner_write + | fs::perms::group_read; + fs::permissions(some_file, perms, fs::perm_options::replace); + + // Check permissions + REQUIRE(fs::has_permissions(some_file, perms)); + REQUIRE(fs::has_permissions(some_file, fs::perms::owner_read)); + REQUIRE(fs::has_permissions(some_file, fs::perms::owner_write)); + REQUIRE(fs::has_permissions(some_file, fs::perms::group_read)); + REQUIRE(fs::has_permissions(some_file, fs::perms::owner_read + | fs::perms::owner_write)); + REQUIRE(fs::has_permissions(some_file, fs::perms::owner_read + | fs::perms::group_read)); + REQUIRE(fs::has_permissions(some_file, fs::perms::owner_write + | fs::perms::group_read)); + REQUIRE_FALSE(fs::has_permissions(some_file, + fs::perms::owner_exec)); + REQUIRE_FALSE(fs::has_permissions(some_file, + fs::perms::group_write)); + REQUIRE_FALSE(fs::has_permissions(some_file, + fs::perms::group_exec)); + REQUIRE_FALSE(fs::has_permissions(some_file, + fs::perms::others_read)); + REQUIRE_FALSE(fs::has_permissions(some_file, + fs::perms::others_write)); + REQUIRE_FALSE(fs::has_permissions(some_file, + fs::perms::others_exec)); + REQUIRE_FALSE(fs::has_permissions(some_file, fs::perms::owner_read + | fs::perms::owner_exec)); + + fs::remove_all(tmp_dir); + } + + // 2025-11-27 make_executable() bug + TEST_CASE("Bug: failure when calling make_executable()" + " on already executable file inside non-writable folder.") + { + // Create temp folder + const auto tmp_dir = fs::temp_directory_path() + / "mamba-fs-make_executable-2025-11-27-bug"; + mamba::on_scope_exit _([&] { fs::remove_all(tmp_dir); }); + const auto folder = tmp_dir / "some_folder"; + fs::create_directories(folder); + + // Create file + const auto some_file = tmp_dir / "some_file"; + { + std::ofstream ofs{ some_file.std_path(), + std::ofstream::binary + | std::ofstream::trunc }; + ofs << "ABC" << std::endl; + } + + // Make executable + make_executable(some_file); + + // Remove write permissions on folder + const auto perms = fs::perms::owner_read + | fs::perms::group_read | fs::perms::others_read; + fs::permissions(folder, perms, fs::perm_options::replace); + + // Make executable (again!) + // This should not fail + make_executable(some_file); + } + } + namespace { TEST_CASE("remove_readonly_file") From 0bc01095183d75d3bf1417d3c5ac0a3137108301 Mon Sep 17 00:00:00 2001 From: Pierrick Roger Date: Thu, 27 Nov 2025 15:52:20 +0100 Subject: [PATCH 5/7] Correct create_cache_dir() and adjust tests. --- libmamba/src/core/subdir_index.cpp | 29 +-- libmamba/tests/src/core/test_filesystem.cpp | 198 +++++++++++--------- 2 files changed, 131 insertions(+), 96 deletions(-) diff --git a/libmamba/src/core/subdir_index.cpp b/libmamba/src/core/subdir_index.cpp index 7010b952ce..a4f89f9a9d 100644 --- a/libmamba/src/core/subdir_index.cpp +++ b/libmamba/src/core/subdir_index.cpp @@ -1122,7 +1122,10 @@ namespace mamba auto create_cache_dir(const fs::u8path& cache_path) -> std::string { const auto cache_dir = cache_path / "cache"; - fs::create_directories(cache_dir); + if (!std::filesystem::is_directory(cache_dir)) + { + fs::create_directories(cache_dir); + } // Some filesystems don't support special permissions such as setgid on directories (e.g. // NFS). and fail if we try to set the setgid bit on the cache directory. @@ -1141,17 +1144,21 @@ namespace mamba LOG_TRACE << "Set permissions on cache directory " << cache_dir << " to 'rwxrwxr-x'"; } - std::error_code ec; - fs::permissions(cache_dir, fs::perms::set_gid, fs::perm_options::add, ec); - - if (!ec) - { - LOG_TRACE << "Set setgid bit on cache directory " << cache_dir; - } - else + const auto setgid_perms = fs::perms::set_gid; + if (!fs::has_permissions(cache_dir, setgid_perms)) { - LOG_TRACE << "Could not set setgid bit on cache directory " << cache_dir - << "\nReason:" << ec.message() << "; ignoring and continuing"; + std::error_code ec; + fs::permissions(cache_dir, setgid_perms, fs::perm_options::add, ec); + + if (!ec) + { + LOG_TRACE << "Set setgid bit on cache directory " << cache_dir; + } + else + { + LOG_TRACE << "Could not set setgid bit on cache directory " << cache_dir + << "\nReason:" << ec.message() << "; ignoring and continuing"; + } } return cache_dir.string(); diff --git a/libmamba/tests/src/core/test_filesystem.cpp b/libmamba/tests/src/core/test_filesystem.cpp index 1da8a42446..a84632bf56 100644 --- a/libmamba/tests/src/core/test_filesystem.cpp +++ b/libmamba/tests/src/core/test_filesystem.cpp @@ -293,91 +293,6 @@ namespace mamba } } - namespace { - TEST_CASE("has_permissions()") - { - // Create temp folder - const auto tmp_dir = fs::temp_directory_path() - / "mamba-fs-has_permissions"; - fs::create_directories(tmp_dir); - - // Create file - const auto some_file = tmp_dir / "some_file"; - { - std::ofstream ofs{ some_file.std_path(), - std::ofstream::binary - | std::ofstream::trunc }; - ofs << "ABC" << std::endl; - } - - // Set permissions - const auto perms = fs::perms::owner_read | fs::perms::owner_write - | fs::perms::group_read; - fs::permissions(some_file, perms, fs::perm_options::replace); - - // Check permissions - REQUIRE(fs::has_permissions(some_file, perms)); - REQUIRE(fs::has_permissions(some_file, fs::perms::owner_read)); - REQUIRE(fs::has_permissions(some_file, fs::perms::owner_write)); - REQUIRE(fs::has_permissions(some_file, fs::perms::group_read)); - REQUIRE(fs::has_permissions(some_file, fs::perms::owner_read - | fs::perms::owner_write)); - REQUIRE(fs::has_permissions(some_file, fs::perms::owner_read - | fs::perms::group_read)); - REQUIRE(fs::has_permissions(some_file, fs::perms::owner_write - | fs::perms::group_read)); - REQUIRE_FALSE(fs::has_permissions(some_file, - fs::perms::owner_exec)); - REQUIRE_FALSE(fs::has_permissions(some_file, - fs::perms::group_write)); - REQUIRE_FALSE(fs::has_permissions(some_file, - fs::perms::group_exec)); - REQUIRE_FALSE(fs::has_permissions(some_file, - fs::perms::others_read)); - REQUIRE_FALSE(fs::has_permissions(some_file, - fs::perms::others_write)); - REQUIRE_FALSE(fs::has_permissions(some_file, - fs::perms::others_exec)); - REQUIRE_FALSE(fs::has_permissions(some_file, fs::perms::owner_read - | fs::perms::owner_exec)); - - fs::remove_all(tmp_dir); - } - - // 2025-11-27 make_executable() bug - TEST_CASE("Bug: failure when calling make_executable()" - " on already executable file inside non-writable folder.") - { - // Create temp folder - const auto tmp_dir = fs::temp_directory_path() - / "mamba-fs-make_executable-2025-11-27-bug"; - mamba::on_scope_exit _([&] { fs::remove_all(tmp_dir); }); - const auto folder = tmp_dir / "some_folder"; - fs::create_directories(folder); - - // Create file - const auto some_file = tmp_dir / "some_file"; - { - std::ofstream ofs{ some_file.std_path(), - std::ofstream::binary - | std::ofstream::trunc }; - ofs << "ABC" << std::endl; - } - - // Make executable - make_executable(some_file); - - // Remove write permissions on folder - const auto perms = fs::perms::owner_read - | fs::perms::group_read | fs::perms::others_read; - fs::permissions(folder, perms, fs::perm_options::replace); - - // Make executable (again!) - // This should not fail - make_executable(some_file); - } - } - namespace { TEST_CASE("remove_readonly_file") @@ -537,4 +452,117 @@ namespace mamba } + namespace + { + TEST_CASE("has_permissions()") + { + // Create temp folder + const auto tmp_dir = fs::temp_directory_path() / "mamba-fs-has_permissions"; + mamba::on_scope_exit _([&] { fs::remove_all(tmp_dir); }); + fs::create_directories(tmp_dir); + + // Create file + const auto some_file = tmp_dir / "some_file"; + { + std::ofstream ofs{ some_file.std_path(), + std::ofstream::binary | std::ofstream::trunc }; + ofs << "ABC" << std::endl; + } + + // Set permissions + const auto perms = fs::perms::owner_read | fs::perms::owner_write | fs::perms::group_read; + fs::permissions(some_file, perms, fs::perm_options::replace); + + // Check permissions + REQUIRE(fs::has_permissions(some_file, perms)); + REQUIRE(fs::has_permissions(some_file, fs::perms::owner_read)); + REQUIRE(fs::has_permissions(some_file, fs::perms::owner_write)); + REQUIRE(fs::has_permissions(some_file, fs::perms::group_read)); + REQUIRE(fs::has_permissions(some_file, fs::perms::owner_read | fs::perms::owner_write)); + REQUIRE(fs::has_permissions(some_file, fs::perms::owner_read | fs::perms::group_read)); + REQUIRE(fs::has_permissions(some_file, fs::perms::owner_write | fs::perms::group_read)); + REQUIRE_FALSE(fs::has_permissions(some_file, fs::perms::owner_exec)); + REQUIRE_FALSE(fs::has_permissions(some_file, fs::perms::group_write)); + REQUIRE_FALSE(fs::has_permissions(some_file, fs::perms::group_exec)); + REQUIRE_FALSE(fs::has_permissions(some_file, fs::perms::others_read)); + REQUIRE_FALSE(fs::has_permissions(some_file, fs::perms::others_write)); + REQUIRE_FALSE(fs::has_permissions(some_file, fs::perms::others_exec)); + REQUIRE_FALSE( + fs::has_permissions(some_file, fs::perms::owner_read | fs::perms::owner_exec) + ); + } + + // 2025-11-27 make_executable() bug + TEST_CASE( + "Bug: failure when calling make_executable()" + " on already executable file inside non-writable folder." + ) + { + // Create temp folder + const auto tmp_dir = fs::temp_directory_path() + / "mamba-fs-make_executable-2025-11-27-bug"; + mamba::on_scope_exit _([&] { fs::remove_all(tmp_dir); }); + const auto folder = tmp_dir / "some_folder"; + fs::create_directories(folder); + + // Create file + const auto some_file = tmp_dir / "some_file"; + { + std::ofstream ofs{ some_file.std_path(), + std::ofstream::binary | std::ofstream::trunc }; + ofs << "ABC" << std::endl; + } + + // Make executable + make_executable(some_file); + + // Remove write permissions on parent folder + const auto perms = fs::perms::owner_read | fs::perms::owner_exec | fs::perms::group_read + | fs::perms::group_exec | fs::perms::others_read + | fs::perms::others_exec; + fs::permissions(folder, perms, fs::perm_options::replace); + + // Make executable (again!) + // This should not fail + make_executable(some_file); + + // Reset writing permission + const auto write_perms = fs::perms::owner_all; + fs::permissions(folder, write_perms, fs::perm_options::replace); + } + + // 2025-11-27 create_cache_dir() bug + TEST_CASE( + "Bug: failure when calling create_cache_dir()" + " on already existing cache directory inside" + " non-writable folder." + ) + { + // Create temp folder + const auto tmp_dir = fs::temp_directory_path() + / "mamba-fs-create_cache_dir-2025-11-27-bug"; + mamba::on_scope_exit _([&] { fs::remove_all(tmp_dir); }); + const auto folder = tmp_dir / "some_folder"; + fs::create_directories(folder); + + // Create cache folder + auto cache_dir = folder / "cache_dir"; + create_cache_dir(cache_dir); + + // Remove write permissions on parent folder + const auto perms = fs::perms::owner_read | fs::perms::owner_exec | fs::perms::group_read + | fs::perms::group_exec | fs::perms::others_read + | fs::perms::others_exec; + fs::permissions(folder, perms, fs::perm_options::replace); + + // Create cache folder (again!) + // This should not fail + create_cache_dir(cache_dir); + + // Reset writing permission + const auto write_perms = fs::perms::owner_all; + fs::permissions(folder, write_perms, fs::perm_options::replace); + } + } + } From f4db72f08b221d1c8947075b49094fb967f2d4cf Mon Sep 17 00:00:00 2001 From: Pierrick Roger Date: Tue, 17 Feb 2026 10:19:40 +0100 Subject: [PATCH 6/7] Remove useless include of grp.h --- libmamba/src/fs/filesystem.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/libmamba/src/fs/filesystem.cpp b/libmamba/src/fs/filesystem.cpp index 3797df1cf3..151354b02d 100644 --- a/libmamba/src/fs/filesystem.cpp +++ b/libmamba/src/fs/filesystem.cpp @@ -12,7 +12,6 @@ #ifndef _WIN32 #include -#include #include #include #include From b8be5f2b76f1a802d4cd93c3cb5e17206dc79a5c Mon Sep 17 00:00:00 2001 From: Pierrick Roger Date: Tue, 17 Feb 2026 10:19:59 +0100 Subject: [PATCH 7/7] Add explanations for has_permissions() --- libmamba/include/mamba/fs/filesystem.hpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/libmamba/include/mamba/fs/filesystem.hpp b/libmamba/include/mamba/fs/filesystem.hpp index 09cb64c1a4..7396409167 100644 --- a/libmamba/include/mamba/fs/filesystem.hpp +++ b/libmamba/include/mamba/fs/filesystem.hpp @@ -1139,7 +1139,12 @@ namespace mamba::fs return std::filesystem::last_write_time(path, new_time, std::forward(args)...); } - // Check if we have modification rights on a path + /* Check if we have modification rights on a path. + * This function is not a wrapping on a of std::filesystem function, but it + * uses std::filesystem::status(). + * Exceptions may be thrown by std::filesystem::status() if the path is not + * valid. + */ bool has_permissions(const u8path&, const fs::perms&); // void permissions(const path& p, perms prms, perm_options opts = perm_options::replace);