Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions libmamba/include/mamba/core/util.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,12 @@ 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.
Expand Down
8 changes: 8 additions & 0 deletions libmamba/include/mamba/fs/filesystem.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -1139,6 +1139,14 @@ namespace mamba::fs
return std::filesystem::last_write_time(path, new_time, std::forward<OtherArgs>(args)...);
}

/* 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);
// void permissions(const path& p, perms prms, error_code& ec) noexcept;
// void permissions(const path& p, perms prms, perm_options opts, error_code& ec);
Expand Down
34 changes: 22 additions & 12 deletions libmamba/src/core/subdir_index.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1043,7 +1043,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.
Expand All @@ -1056,20 +1059,27 @@ 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'";

std::error_code ec;
fs::permissions(cache_dir, fs::perms::set_gid, fs::perm_options::add, ec);

if (!ec)
if (!fs::has_permissions(cache_dir, permissions))
{
LOG_TRACE << "Set setgid bit on cache directory " << cache_dir;
fs::permissions(cache_dir, permissions, fs::perm_options::replace);
LOG_TRACE << "Set permissions on cache directory " << cache_dir << " to 'rwxrwxr-x'";
}
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();
Expand Down
11 changes: 11 additions & 0 deletions libmamba/src/fs/filesystem.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@

#ifndef _WIN32
#include <fcntl.h>
#include <pwd.h>
#include <sys/stat.h>
#include <unistd.h>
// We can use the presence of UTIME_OMIT to detect platforms that provide
// utimensat.
#if defined(UTIME_OMIT)
Expand Down Expand Up @@ -79,4 +81,13 @@ namespace mamba::fs
std::filesystem::last_write_time(path, new_time, ec);
#endif
}

bool has_permissions(const u8path& path, const fs::perms& perm)
{
// Get path permissions
auto p = std::filesystem::status(path).permissions();

// Path perms must include wanted perms
return perm == (p & perm);
}
}
113 changes: 113 additions & 0 deletions libmamba/tests/src/core/test_filesystem.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -452,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);
}
}

}
Loading